隐私合规综合实践
目录介绍
01.整体概述介绍
- 1.1 遇到问题说明
- 1.2 项目背景
- 1.3 设计目标
- 1.4 产生收益分析
02.隐私合规测什么
- 2.1 隐私合规是什么
- 2.2 为何做隐私合规
- 2.3 隐私合规政策案例
- 2.4 为何做权限合规
04.隐私合规检测
- 4.1 违规收集个人信息
- 4.2 超范围收集个人信息
- 4.3 违规使用个人信息
- 4.4 过度索取权限
- 4.5 自启动和关联启动
05.隐私合规实践
- 5.1 整体合规思路
- 5.2 工具检测隐私API
- 5.3 工具检测权限
- 5.4 敏感信息控频
- 5.5 隐私协议声明
- 5.6 敏感权限实践
- 5.7 底层依赖库权限说明
06.合规测试检查重点
- 6.1 合规处理优先级
- 6.2 QA测试检查重点
- 6.3 交互层面合规
- 6.4 服务端敏感收集
- 6.5 隐私协议筛查
07.其他说明介绍
- 7.1 参考博客链接
- 7.2 相关demo链接
01.整体概述介绍
1.1 遇到问题说明
国内对应用程序安全隐私问题监管变的越来越严格。各个应用市场对APP上架也有比较严格的检查。出现隐私合规安全问题主要有哪些呢?
- 在用户同意隐私协议之前,不能有收集用户隐私数据的行为。例如,在用户同意之前不能去获取 Android ID、Device ID、MAC 等隐私数据
- 在用户同意隐私协议之后,搜集用户隐私数据的行为不能超出实现服务场景所必需的最低频率。例如,某些应用会在每次网络请求时将当前设备的 Android ID 作为 header 一起上报,如果没有对 Android ID 进行缓存处理的话,搜集该数据的行为频率就会非常高,此时一样存在隐私合规问题。
工信部隐私合规说明
- 具体可以看一下这篇文档:工信部隐私合规文档
1.2 项目背景
- 最关键的问题是用户同意隐私协议之前,不能有收集用户隐私信息的行为,例如获取deviceId、androidId等信息,除此之外,对于频繁申请权限、超范围申请权限也是需要注意的。
- 除了开迭代针对性整改,从技术角度思考,有没有一劳永逸的办法,杜绝隐私调用不合规问题呢?
1.3 设计目标
针对提前收集用户隐私数据。
- 需要统计出整个项目中所有涉及到隐私行为的相关代码,根据业务流程来判断该隐私行为是否合理、以及是否会在用户同意隐私协议之前被触发。这就需要对整个项目进行静态扫描。
针对收集隐私数据哪里调用。
- 需要在应用运行时动态记录每次触发隐私行为的时间点和调用链信息,根据触发时间来判断该隐私行为是否过量执行,根据调用链信息来辅助判断具体是哪一块业务在获取隐私数据。这就需要对应用进行动态记录。
1.4 产生收益分析
编码排查耗时大
- 如果单纯靠开发人员来肉眼识别代码和编码统计的话,工作量非常大而且也很不现实,因为一个大型项目往往都会引入多个依赖库和第三方 SDK,可以规范自有代码,但没法修改和有效约束外部依赖,也很难理清楚依赖库的内部逻辑和调用链关系。
提高合规隐私检测效率
- 当检测有调用隐私数据时,在控制台打印输出提示,给出堆栈信息让开发快速定位调用链路;当检测到隐私行为后,输出相对应的记录报告,以便开发人员能够在开发阶段排查问题。
02.隐私合规测什么
2.1 隐私合规是什么
- 对客户端而言,权限隐私可分为 权限 和 隐私 两个大的方面。
权限为用户通过app内弹窗设置或者手机设置内对应app的权限设置方式给予对应app相应的权限
- 如电话权限,定位权限,相机权限,浮窗权限,读写权限等。在每个申请危险权限前,都需要弹窗说明权限解释说明。
隐私为app使用过程中与用户个人相关的个人信息
- 如所在位置,Mac地址,设备id等。就Android端而言,多数隐私信息需要对应授权后才能获取,但目前仍存在部分隐私信息无需授权就可以拿到的。
2.2 为何做隐私合规
- 大众隐私意识觉醒,权限隐私安全性差会直接导致用户不愿使用;日趋严格的权限治理和隐私安全治理,工信部和市场的严格管控;客户端作为与用户最直接的交互信息收集入口,有义务合规化的收集和使用用户信息。
2.3 隐私合规政策案例
隐私合规案例
- 比如获取设备信息:获取设备id,sim,androidId等;比如获取危险权限信息:获取读写存储卡权限,获取电话权限等。
需要有文案描述
- 收集设备id,为了帮助开发者在进行消息推送时识别最终用户设备,保障开发者及最终用户正常使用消息推送服务,提升消息推送服务的效率以及准确率。
- 获取读写权限,帮助开发者进行最终用户设备识别,保障开发者及最终用户正常使用消息推送服务,提升消息推送服务的效率以及准确率;更准确定位并解决产品和服务使用问题,改进及优化产品和服务体验。
2.4 为何做权限合规
首先权限合规有两大点
- 第一点:那里使用到了权限就在那里申请;第二点:使用权限的时候需要弹窗说明该权限的用途。
列举一下我实践案例中的权限合规梳理
04.隐私合规检测
4.1 违规收集个人信息
场景说明:
- 未经用户同意,存在收集IMEI、设备id,设备MAC地址和软件安装列表、通讯录和短信的行为。
整改建议:
- 隐私政策隐私弹窗必须使用明确的“同意\拒绝”按钮;只有当用户点击“同意”后,APP和SDK才能调用系统接口和读取收集用户的信息。
客户端如何做?
- ①用户在点击隐私政策协议“同意”按钮前,APP和SDK不能调用系统的敏感权限接口,特别是能获取IMEI、IMSI、MAC、IP、Android、已安装应用列表、硬件序列表、手机号码、位置等等信息的系统接口。
- ②集成的第三方SDK建议升级到最新版本,用户在点击隐私政策协议“同意”按钮后,SDK再进行初始化。
- ③技术同学可在“同意”按钮上加入判定函数,当用户点击“同意”后,APP和SDK再执行调用系统接口的相关函数行为。
4.2 超范围收集个人信息
场景说明:
- 1.APP未见向用户告知且未经用户同意,在某功能中,存在收集通讯录、短信、通话记录、相机等信息的行为,非服务所必需且无合理应用场景,超出与收集个人信息时所声称的目的具有直接或合理关联的范围。
- 2.未经用户同意,SDK存在按照一定频次读取位置信息等个人信息行为,非服务所必需且无合理应用场景,超出实现产品或服务的业务功能所必需的最低频率。
整改建议:
- 针对1,当用户点击“同意”后,APP和SDK再执行调用系统接口的相关函数行为。然后APP隐私政策内需要补充收集(运行中的进程、【广点通SDK】收集IMSI)信息的规则说明。
- 针对2,App或者Sdk收集用户信息频率超过合规范围,尽可能保证全局只收集1次(最多不超过3次),收集频次不要超过1次/秒。
客户端如何做?
- 举个例子,App在同意隐私政策前,不会收集android_id。在同意隐私政策后,有采集行为,但是4分钟采集了12次,则会认为过于频繁。
- App修复该问题,可以统一管理敏感信息采集入口,缓存敏感信息数据,可以设定缓存过期时间(建议设置超过5分钟)。获取android_id,缓存下来,下次调用先拿缓存,避免频繁调用系统api。
4.3 违规使用个人信息
场景说明:
- 1.APP未见向用户告知且未经用户同意,存在将IMEI/设备MAC地址/软件安装列表等个人信息发送给友盟/极光/个推等第三方SDK的行为。
- 2.APP未见向用户明示分享的第三方名称、目的及个人信息类型,用户同意隐私政策后,存在将IMEI/设备MAC地址/软件安装列表等个人信息发送给友盟/极光/个推等第三方SDK的行为。
整改建议:
- 针对1,APP和集成的SDK在用户“同意”隐私政策,然后在调用初始化sdk操作。
- 针对2,在隐私政策中补充第三方SDK收集(比如声网sdk收集IMSI)信息的规则说明。
- 敏感个人信息范围参考《信息安全技术个人信息安全规范》
4.4 过度索取权限
场景说明:
- 1.APP首次启动时,向用户索取电话、通讯录、定位、短信、录音、相机、存储、日历等权限,用户拒绝授权后,应用退出或关闭(应用陷入弹窗循环,无法正常使用)。
- 2.向用户索取危险权限(电话、通讯录、定位、短信、录音、相机、存储、日历等权限),没有添加申请权限目的告知用户。
- 3.APP运行时,向用户频繁弹窗申请开启与当前服务场景无关的权限,影响用户正常使用。
- 4.未见使用权限对应的相关产品或服务时,提前向用户弹窗申请开启通讯录/定位/短信/录音/相机/XXX等权限。
整改建议:
- 针对1场景,举例说明APP向用户索取(电话)权限,用户拒绝后,APP不能退出或关闭,必须保证APP可以继续正常运行。
- 针对2场景,APP需要先通过弹窗向用户说明申请(电话)权限的目的,用户同意后再申请权限。用户拒绝后,APP不能退出或关闭,必须保证APP可以继续正常运行。
- 针对3场景,APP向用户索取(电话)权限,用户拒绝后,APP不能重复向用户申请权限。(权限申请弹窗的“禁止后不再询问”是系统提供的功能,属于管理功能,不是APP自身机制,APP要能做到拒绝后不再触发申请权限弹窗)。
- 针对4场景,比如需要用到电话的时候,先通过弹窗向用户说明申请(电话)权限的目的,用户同意后再申请权限。不要在没有使用到电话功能页面,去申请电话权限。
4.5 自启动和关联启动
场景说明:
- 1.APP未向用户明示未经用户同意,且无合理的使用场景,存在频繁自启动或关联启动的行为。
- 2.APP虽然有向用户明示并经用户同意环节,但频繁自启动或关联启动发生在用户同意前。
- 3.APP非服务所必需或无合理应用场景,超范围频繁自启动或关联启动第三方APP。
整改建议:
- 针对1,2,3场景。建议删除相关自启动函数代码。如APP必须使用(自启动)能力,请在隐私政策协议中清楚说明自启动的规则说明,并且取得用户同意后执行。
客户端如何做?
- App没有自启动场景和服务,则删除相关自启动的函数调用代码。
- App有自启动场景和服务,则在隐私政策中做好完整规则说明,在用户同意隐私政策前不要执行自启动代码,在同意隐私后才可以执行自启动代码。
4.5 隐私数据注意项
遇到的问题,每次排查隐私数据很麻烦
- 因为随着项目更迭,随时可能有新的隐私安全问题被引入进来,而如果每次发版前都要重新走一遍上述流程来排查是否存在问题的话,也是很麻烦。
如何保证隐私合规绝对安全呢
- 一般都是会通过一个标记位来记录用户是否已经同意过隐私协议,我们可以在每次获取敏感数据前均先判断该标记位,如果用户还未同意隐私协议的话就直接返回空数据,否则才去真正执行操作。
05.隐私合规检测库实践
5.1 整体合规思路
开发了一个针对 Android APK 的敏感方法调用的静态检查工具。
- 检查关键字,对于一些敏感 API 调用,例如 oaid、androidId、imei 相关的调用。其实只要能检测到这些相关 API 里的一些关键字,找出整个 APP 里面有哪些地方直接调用了这些方法就可以了。
针对的上述的一些场景,这个工具具有两个方向的工作:
- APK 包的扫描,检查出整个APK中,哪些地方有对包含上面这些 API 关键字的直接调用。
- 运行时检查。针对运行时频繁调用这个场景,还是需要在运行时辅助检查特定API的调用情况。
5.2 工具检测隐私API
方案1:Xposed
- 如果你对Xposed比较熟悉,并且手头有个root的设备安装了Xposed框架,那么直接开发一个Xposed模块来hook指定方法就可以了。缺点是需要root权限……
方案2:VirtualXposed
- VirtualXposed 是基于VirtualApp 和 epic 在非ROOT环境下运行Xposed模块的实现(支持5.0~10.0)。
- VirtualXposed其实就是一个支持Xposed的虚拟机,我们把开发好的Xposed模块和对应需要hook的App安装上去就能实现hook功能。
方案3:epic
- 如果不想折腾 Xposed 或者 VirtualXposed,只要在应用内接入epic,就可以实现应用内Xposed hook功能,满足运行hook需求。
- epic 存在兼容性问题,例如Android 11 只支持64位App,所以建议只在debug环境使用。
最终选择:方案3
- 它可以拦截本进程内部几乎任意的 Java 方法调用,可用于实现 AOP 编程、运行时插桩、性能分析、安全审计等。
使用起来也非常简单:提前设置需要 hook 哪个 java 方,比如,我要 hook TelephonyManager 的 getDeviceId 方法:
//核心方法 DexposedBridge.findAndHookMethod(TelephonyManager.class, "getDeviceId", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); String className = param.method.getDeclaringClass().getName(); String methodName = param.method.getName(); Log.i(PrivacyHelper.TAG, "检测到风险函数被调用: " + className + "#" + methodName); Log.d(PrivacyHelper.TAG, StackTraceUtils.getMethodStack()); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); Log.d(PrivacyHelper.TAG, "afterHookedMethod getDeviceId"); } });
- 在代码中如果有地方调用 TelephonyManager.getDeviceId 的,都会被 epic 的 beforeHookedMethod 给拦截到,只需要在 beforeHookedMethod 打印出堆栈即可看到是谁调用的。
为何打印堆栈信息
- 在应用运行时记录每次触发隐私行为的时间点和调用链信息,根据触发时间来判断该隐私行为是否过量执行,根据调用链信息来辅助判断具体是哪一块业务需要来获取隐私数据。
当检测到了风险函数调用情况,则需要知道该函数是在哪里调用的?这个该怎么做呢?
- 获取当前线程,然后通过线程获取stackTraces,再然后遍历打印即可。根据堆栈信息,可以看到调用链的类名,方法名称,代码行数等。
关于针对运行期检查隐私合规api调用
- 具体可以看:MonitorPrivacy
5.3 工具检测权限
使用场景
- 新增三方sdk或开源库,有收集个人信息或者权限的,这个时候需要用工具检测。主要是为了新增的权限在隐私政策中申请,用工具去保证重要权限防止漏掉隐私说明。
敏感权限检查
- 扫描AndroidManifest.xml检查敏感权限,这个可以借助三方工具。
5.4 敏感信息控频
- 敏感设备信息获取是指只要调用系统API就会认为获取敏感信息,并不关心有没有获取到敏感信息以及调用系统API的目的。
主要原则:
- 没有权限不能调用系统API,如无READ_PHONE_STATE权限调用掉用getImei()、getDeviceId()等API
- 控制调用频次,即使有权限不能频繁调用系统API
- 控制传输频次,不能频繁传输敏感信息
控频方案:
- App自身系统隐私API调用,统一调用基础库,该库统一做内存级别缓存,不重复调用系统API。
- 传输控频,主要有2种方案:敏感信息统一传送一次,各业务单独对接,业务见相互依赖强;数据统一整包加密
敏感信息主要有那些
- imei(IMEI),android_id(Android唯一标识符),provider_name(手机运营商),operator_id(卡运营商id),sn(sn设备号)等等
举一个简单例子【利用缓存,避免频繁调用api获取敏感信息】
public static String getAndroidId(@NonNull Context context, String defaultValue) { if (null != Cache.ANDROID_ID) { return Cache.ANDROID_ID; } String androidId = SafePrivateHelper.getAndroidId(); if (!TextUtils.isEmpty(androidId)) { Cache.ANDROID_ID = androidId; } else { androidId = defaultValue; } return androidId; }
- 具体的方案可以看这个依赖库:PrivateServer
5.5 隐私协议声明
隐私协议声明很重要,能规避大部分问题,隐私协议声明要注意几点:
- app自身收集的个人信息、用途需要在隐私协议中声明
- app申请权限及目的在隐私协议中声明
- 集成的所有第三方sdk及第三方sdk收集个人信息的用户需要在隐私协议中声明;包括检测机构检测出来的+三方sdk隐私协议中声明的
- 在隐私协议中声明,app及三方sdk在静默和后台也会收集个人信息
针对危险权限,需要在隐私协议中说明一下。具体如下所示:
5.6 敏感权限实践
敏感权限。如下所示,频繁读取敏感权限也会触发合规
android.permission.READ_CALL_LOG:读取通话记录 android.permission.WRITE_CALL_LOG:写通话记录 android.permission.READ_CONTACTS:读取联系人 android.permission.WRITE_CONTACTS:写联系人 android.permission.ACCESS_FINE_LOCATION:精确定位 android.permission.RECEIVE_BOOT_COMPLETED:开机自启动 android.permission.SEND_SMS: 发送短信 android.permission.RECEIVE_SMS:接受收短信 android.permission.READ_SMS:读取短信
5.7 底层依赖库权限说明
针对申请隐私权限需要弹窗说明
- 比如以前可以在app启动的时候,一下子申请完所有权限。但是这种已经不符合规范了,要求必须在使用的时候申请权限,并且要有弹窗说明。一句话概括为那里使用那里申请权限!
举一个例子加深理解
- 比如你有一个二维码扫描库,你在扫描的时候需要申请相机权限,并且先要弹出弹窗说明文案;比如你有个相册库,你在打开相册的时候,需要申请读写权限。
二维码库和相册库已经自己申请权限,如何复用壳工程中的权限说明弹窗?
- 具体方案:采用接口隔离,具体的实现类放到壳工程中实现。具体可以看看我的这个基础空间通用接口库:EventUploadLib
06.合规测试检查重点
6.1 合规处理优先级
- 合规需求第一优先级,第一时间跟版上线,不要有任何商量和侥幸,比开发需求还要重要!否则应用市场无法上架很麻烦……
- 新增需求不合规不允许上线:新增需求如有不合规的地方,但又来不及修改,则延期上线,整改到合规再上
- 发版准出增加,合规确认环节:每次发版,产品、研发、测试 都需要负责检查对应的合规项,对检查结果负责,都确认之后才可以发版
6.2 QA测试检查重点
- 重点手工 check list。研发和测试都要重视这块工作!
- 第一次打开时,各种隐私协议打开是否正常。
- 第一次打开时,未同意隐私协议前,不能有任何网络请求发出,可通过手机设置代理查看。
- 第一次打开时,未同意隐私协议前,不能有任何隐私 API 调用,通过Xposed的手机是否有隐私api调用。
6.3 交互层面合规
申请权限弹窗申明
- App上一些用户权限需要有申请弹窗说明,相关交互内容有特定的交互要求,需qa配合在发版前回归阶段进行有限的检查。
筛查范围
- 安卓端,app启动时,明显的权限申请弹窗、隐私协议、个性化推荐等交互流程。
- 权限弹窗控制频次(比如App申请通知权限弹窗设置用于点击取消后,频次至少间隔48小时);同意隐私协议不能默认勾选;个性化推荐支持关闭
权限弹窗控制频次
- 操作步骤:最新下载未打开的安卓包,启动app时,出现权限弹框任意一个例如:本地存储、相机、定位权限,点击拒绝;将app关闭杀死后台程序,再次打开app,查看是否还有上述被拒绝的权限弹框,例如:本地存储、相机、定位权限。
- 预期效果:如果拒绝之后再弹框就是有问题、不合理,需要上报开发排查原因;如果没有上述三个权限弹窗,则为正常。
同意隐私协议不能默认勾选
- 打开app时,关注涉及隐私协议页面,查看默认勾选状态。预期效果:默认为:未勾选,则为正常;默认为:勾选,则为有问题,需要上报开发排查原因。
收集与功能无关的个人信息
- 在未使用任何功能的情况,查看是否有弹窗索取手机存储权限。预期效果:不进行弹窗索取手机存储权限。
6.4 服务端敏感收集
背景简单说明一下
- 禁止APP收集用户信息,比如imei、cuid,oaid等用户设备信息。所以在发版前需要确保客户端内请求不携带imei、oaid等敏感字段,接口返回也不包含以上敏感字段。
筛查范围记录
- APP内客户端/fe发起的接口请求,建议各APP先彻底筛查一遍,排除隐患,后续迭代版本例行筛查F0功能或新增功能即可。
筛查规则:请求 request body/response中不包括
- imei,imei的值(一般有两种形式,eg:imei正常格式没有加密的 imei=866917034628451,加密过的imei = oC0JwJIxaEiKIWlzkqHO)。
筛查方法说明
- 连接代理(全局代理,不是指定某个域名的代理),以charles为例。回到charles界面,ctrl+F,输入关键字“imei”“oaid”以及imei的值进行反查。
- 关注request里面是否携带此三个参数,只要有携带,不管是否传值,都是问题,需要报给客户端;关注response中是否返回此三个参数,如果有,需要上报开发排查原因,是否可以不返回此参数。
6.5 隐私协议筛查
方案说明:
- 确保隐私协议可访问; 通过脚本自动检查三方 SDK 是否在隐私协议中声明;法务 + 产品 定期检查;
实施措施:
- 建立隐私协议可访问性自动化巡检机制;三方SDK检测,根据检测出来新增的三方SDK扫描隐私协议,确认该sdk是否在隐私协议中声明。
开发需要注意点
- 新增三方sdk或开源库,有收集个人信息或者权限的 必须在隐私政策里申明(找产品和法务);没收集个人信息或者权限的三方库或者自有库,确定好后将sdk包名和中文名称备注一下。
07.其他说明介绍
7.1 参考博客链接
腾讯隐私政策审核规范
腾讯隐私政策整改方法