万一您错过了它,在小米手机上预装了《福布斯》关于Mi Browser Pro和Mint Browser的文章。该文章指责小米泄露了所有访问过的网站的历史记录。另一方面,小米指责福布斯歪曲事实。他们声称数据收集遵循最佳做法,数据本身经过汇总和匿名处理,与用户身份没有任何关系。
如果您关注我的博客已有一段时间,那么您可能会发现这种说法很熟悉。在发现用户从事间谍活动之后,浏览器供应商从附加商店中提取了扩展程序,这几乎与Avast的通信方式完全相同。最后,我得到了证明,如果您允许我轻描淡写,他们的数据匿名化尝试只会取得一定程度的成功。
鉴于《福布斯》的文章和所涉及的安全研究人员似乎都没有提供任何技术细节,所以我想自己看看。我反编译了Mint Browser 3.4.0并寻找了线索。这不是最新版本,以防万一小米已经对《福布斯》的文章进行了修改以编写代码。更新(2020-05-08):如果不需要技术说明,则较新的文章将概述此问题。
免责声明:我认为这是我第一次分析较大的Android应用程序,因此请耐心等待。即使全局看起来似乎很清楚,我也可能会误解一件事或另一件事。另外,我的结论完全基于代码分析,我从未见过这种浏览器的实际应用。
《福布斯》(Forbes)文章解释说,数据正在传输到Sensors Analytics后端。然后,小米文章提供了重要的线索:sa.api.intl.miui.com是此后端的主机名。然后,他们继续说明小米所拥有的服务器而不是第三方。但是他们只是在试图分散我们的注意力:如果将来自浏览器的敏感数据发送到该服务器,我为什么要关心谁拥有它?
我们在类miui.globalbrowser.common_business.g.i中找到了该服务器名称(是的,某些软件包和类名称已被修改)。在一些初始化代码中使用了它:
查找A.e,原来是国家/地区代码。因此,这里的i.a静态成员最终保留了端点URL,并填写了用户的国家/地区代码。该类在类的初始化函数中使用:
公共无效a(最终上下文c){SensorsDataAPI。 sharedInstance(this。c = c,i。a,this。d); SensorsDataAPI。 sharedInstance()。标识(com.xiaomi.misatistic.sdk.e.a(this.c));这 。 C ();这 。 d();这 。 e();这 。 b(); }
Sensors Analytics API是公共的,因此我们可以查找SensorsDataAPI类,并了解到第一个sharedInstance()调用创建了一个实例并设置了其服务器URL。接下来的行调用identify()为此实例设置一个“匿名ID”,该匿名ID将与每个数据点一起发送,稍后再介绍。
对this.c()的调用也值得一提,因为这将设置一系列附加属性,每个请求都将发送这些附加属性:
public void c(){final JSONObject jsonObject = new JSONObject(); jsonObject。 put(" uuid",(Object)com。xiaomi。misatistic。sdk e.a(this.c)); int n; if(H.f(miui.globalbrowser.common.a.a())){n = 1; } else {n = 0; } jsonObject。放置(" internet_status",n); jsonObject。放(" platform",(Object)" AndroidApp"); jsonObject。 put(" miui_version",(Object)Build $ VERSION。INCREMENTAL);最终字符串e = A。 e; a(e); jsonObject。放(" miui_region",(Object)e); jsonObject。放(" system_language",(Object)A。b); SensorsDataAPI。 sharedInstance(this .c)。 registerSuperProperties(jsonObject); }
为了以防万一,我们有作为uuid参数发送的相同“匿名ID”。此外,正在发送常规版本,区域,语言数据。
对我来说,弄清楚该类是从哪里初始化的并不是很简单。结果来自类miui.globalbrowser.common_business.g.b:
公共静态无效a(final String s,final Map< String,String> map){a(s,map,true); } public static void a(final String s,final Map< String,String> map,final boolean b){if(b){i。一种 ()。 a(s,map); } miui。全球浏览器。 common_business。 G 。 d。一种 ()。 a(s,map); }
因此,miui.globalbrowser.common_business.g.b.a()调用将默认将第三个参数设置为true。此调用访问单例miui.globalbrowser.common_business.gi实例(如果不存在,将创建该实例),并使其实际跟踪事件(s是此处的事件名称,map是除了默认值之外还发送的参数)那些)。额外的miui.globalbrowser.common_business.g.d.a()调用触发了我未调查的MiStatistics分析框架。
就是这样。现在,我们必须找到在代码miui.globalbrowser.common_business.g.b类中使用的位置以及接收的数据。所有这些数据都将定期发送到Sensors后端。
查找com.xiaomi.mistatistic.sdk.e.a()最终会产生与小米博客文章中引用的代码非常接近的ID生成代码:
公共静态字符串d(最终Context上下文){if(!TextUtils.isEmpty((CharSequence)y.g)){返回y。 G ;最终的long currentTimeMillis = System。 currentTimeMillis();最终字符串a = L。 a(context," anonymous_id","");最后的长a2 = L。 a(context," aigt",0 L);最后的长a3 = L。 a(context," anonymous_ei",7776000000 L); if(!TextUtils.isEmpty((CharSequence)a)&& currentTimeMillis-a2< a3){y。 g = a; }其他{L。 b(context," anonymous_id",y。g = UUID。randomUUID()。toString()); } L。 c(context," aigt",currentTimeMillis);返回y。 G ; }
L.a()调用正在从带有fallback的context.getSharedPreferences()中检索一个值。 L.b()和L.c()调用将在其中存储一个值。因此,小米试图告诉我们:“看,ID是随机生成的,与用户没有任何关系。并且每90天更新一次!”
即使对于随机生成的ID,现在90天也是一个相当长的时间间隔。有了足够的数据点,应该很容易从中推断出用户的身份。但是还有另一个问题。看到那个喜好了吗?它的价值是什么?
这里的意图似乎是aigt是生成ID的时间戳。因此,如果该时间戳记与当前时间偏离的时间超过了77.76亿毫秒(90天),那么将生成一个新的ID。但是,此实现有很多问题,它将在每次调用时更新aigt,而不是仅在生成新ID时更新。因此,唯一会生成新ID的情况是:90天未调用此方法,这意味着浏览器在90天未启动。而且这种情况不太可能发生,因此必须考虑将此ID永久化。
如果还不够,那就另当别论了。如果您再次查看SensorsDataAPI类,您将看到当登录ID不可用时,“匿名ID”仅是一个备用。此处的登录ID是什么?我们会在miui.globalbrowser.common_business.g.i类中找到它:
public void b(){最终帐户a = miui。全球浏览器。常见的 。 C 。 b。 a(this。c); if(a!= null&!TextUtils.isEmpty((CharSequence)a.name)){SensorsDataAPI。 sharedInstance()。登录名(。名称); }}
这就是它的样子:一个小米帐户ID。因此,如果用户登录到浏览器,则跟踪数据将连接到其小米帐户。而且该链接至少链接到用户的电子邮件地址,也可能链接到其他标识参数。
如上所述,我们需要查看调用miui.globalbrowser.common_business.g.b类方法的位置。通常,这些对于产品分析来说是非常典型的,例如:
最终HashMap<字符串,字符串> hashMap =新的HashMap<字符串,字符串>(); if(例如。getCause()!= null){hashMap。 put(" cause&#34 ;,例如,getCause()。toString()); } miui。全球浏览器。 common_business。 G 。 b。一个(" rv_crashed",hashMap);
因此发生了崩溃,并通知了供应商有关此问题的信息。在其他地方,数据表明已打开用户界面的特定元素,这对于改进产品也是非常有用的信息。然后在com.miui.org.chromium.chrome.browser.webview.k类中有以下内容:
public void onPageFinished(最终WebView WebView,最终String d){... if(!this.c&!TextUtils.isEmpty((CharSequence)d)){miui。全球浏览器。 common_business。 G 。 b。 a(" page_load_event_finish"," url&#34 ;,这个。a(d)); } ...} public void onPageStarted(最终的WebView webView,最终的String e,最终的位图位图){... if(!this.b&&!TextUtils.isEmpty((CharSequence)e)){miui。全球浏览器。 common_business。 G 。 b。 a(" page_load_event_start"," url&#34 ;,这个。a(e)); } ...}
这就是将所有访问过的网站发送到分析服务器的代码。在页面开始加载时一次,在页面加载完成时一次。小米博客文章解释了为什么存在此代码:“收集URL来识别加载缓慢的Web网页;这使我们能够洞悉如何最好地改善整体浏览性能。”
您相信这个解释吗?因为我不是。如果这一切都是关于慢速网站的原因,为什么不在本地计算页面加载时间而仅发送慢速网站呢?对于隐私而言,这仍然不是很好,但是比小米实际实施的要好一个数量级。如果我们要承担无能而不是恶意的事情,小米确实需要加倍努力。如何确定发送所有访问的地址是一个很好的妥协?那个决定中甚至考虑过隐私吗?他们今天还会做出同样的决定吗?如果没有,他们如何调整流程以反映这一点?
但是,在更多情况下,他们的分析代码会收集过多的数据。在com.miui.org.chromium.chrome.browser.omnibox.NavigationBar类中,我们将看到:
因此,从导航栏中进行搜索不仅会跟踪所使用的搜索引擎,还会跟踪您搜索的内容。在类miui.globalbrowser.download.J中,我们看到例如:
最终HashMap<字符串,字符串> hashMap =新的HashMap<字符串,字符串>(); hashMap。放(" op",s); hashMap。 put(" suffix",s2); hashMap。放(" url",s3);如果(d。c(s4)){s =" privacy" ; } else {s =" general" ; } hashMap。放(" type",s); b。一个(" download_files",hashMap);
这不仅是在跟踪文件已下载的事实,而且还跟踪URL的下载情况。小米可能在这里有什么样的合法权益?
然后,该浏览器似乎为YouTube视频提供了一些自定义用户界面。几乎所有内容都在此跟踪,例如在miui.globalbrowser.news.YMTSearchActivity类中:
小米为什么需要知道人们在YouTube上搜索了什么?不仅如此,他们似乎在其他地方收集有关人们观看哪些视频以及花费多少时间的数据。小米似乎也知道人们在其快速拨号中配置了哪些网站,以及何时单击它们。这不会给人留下好印象,毕竟它可以是监视功能吗?
如果您使用Mint Browser(大概是Mi Browser Pro),则小米不仅会知道您访问了哪些网站,而且还知道您搜索的内容,观看的视频,下载的内容以及添加到“快速拨号”页面的网站。哎呀,他们甚至跟踪哪个色情网站触发了提醒,切换到隐身模式!是的,如果小米希望任何人相信这不是恶意的,他们还有很多事情要做。
也不能维持该数据被匿名化的说法。即使使用随机的用户ID(错误地认为是永久性的)来推断用户的身份也应该很容易,但我们之前已经看到过。但是,如果知道的话,他们还会传输用户的小米帐户ID,该ID直接与用户的身份相关联。
小米现在宣布,他们将以隐身模式关闭已访问网站的集合。这是朝着正确方向迈出的一步,尽管这一步很小。他们还会以隐身模式收集所有其他数据吗?即使没有,为什么还要在常规浏览期间收集这么多数据呢?有什么原因可以证明所有这些侵犯隐私行为是合理的?
更新(2020-05-07):我研究了Mint Browser 3.4.3中与隐私相关的更改。这是一个比听起来更大的改进,可以完全禁用“统计”收集功能。但是,您必须确保已打开“隐身模式”和“增强隐身模式”,这是唯一可以保护您隐私的配置。