Android第三方SDK隐私合规自动化检测方案

1,043 阅读10分钟

1前言

随着移动应用越来越流行,导致的隐私安全问题越来越多,各个国家对App的隐私和安全要求也越来越高。我们自己写的代码可以把控代码中的隐私和安全内容,但是集成第三方SDK,虽然很便捷,但不确定SDK本身是否有违规收集用户隐私的行为,所以就引入了第三方SDK隐私合规检测,这一过程对于实现隐私合规和维护用户信任至关重要;本文将探讨在Android平台上实施这一检测的有效方法。

2检测手段

在SDK隐私合规检测中,检测敏感函数是我们检测SDK隐私合规的手段;敏感函数是指那些能够获取或处理用户隐私数据、影响设备功能和管理外部通信的编程接口。这些函数在应用开发中提供了必要的功能,但由于其潜在的滥用风险,因此被视为“敏感”。

具体来说,敏感函数可能包括但不限于:

1. 用户隐私信息访问:如获取联系人、通话记录、短信及位置信息等。
2. 设备管理功能:如访问相机、麦克风、GPS等硬件。
3. 网络通信功能:用于管理应用与外部服务器之间的数据传输,包括发送和接收数据。
4. 系统级功能:如安装或卸载应用程序、读取或修改系统设置等。

通过检测分析这些敏感函数的调用,我们能够及时识别是否存在不当的数据收集或隐私侵犯行为,从而确保SDK符合相关隐私法规,保护用户的隐私和数据安全。

3通过危险权限识别敏感函数

根据谷歌官方列出来的危险权限列表,其中标记为Protection level: dangerous的是危险权限,根据访问危险权限的编程代码,下面列出部分常见的敏感函数列表:

功能所在包名敏感函数(关键字)
获取已安装应用列表android.content.pm.PackageManagergetInstalledPackages()获取安装包列表
获取位置android.location.LocationManagerrequestLocationUpdates()获取位置
访问相机android.hardware.Cameraopen()打开相机
android.hardware.CameratakePicture()拍照
获取所有联系人android.content.ContentResolverquery(ContactsContract.CommonDataKinds.Phone.CONTENT_URI)获取所有联系人
访问麦克风android.media.AudioRecordstartRecording()开始录音
android.media.MediaRecorderstart()开始录音
网络请求java.net.HttpURLConnectiongetOutputStream()post上传数据
javax.net.ssl.HttpsURLConnectiongetOutputStream()post上传数据

4自动化检测

目前市面上主要有静态检测和动态检测两种方案,但经过调研发现都或多或少存在缺陷不够完美,所以本文旨在实现一种全新的自动化静态检测方案。

说到自动化就需要用到自动化工具,这里我选用了pipeline流水线自动化工具,可能有部分同学还没接触过pipeline流水线,不懂pipeline是什么东西,简而言之,pipeline就是一系列自动化步骤或阶段,这些步骤可以有效地处理和交付代码、数据或其他工件。

我们将编写好的流水线代码部署到Jenkins打包机,这样团队内的所有人都可以直接使用自动化检测。

在本方案中,使用pipeline流水线实现自动化检测敏感函数总共有5个步骤,如下图所示:

公众号-敏感函数自动化检测流程图.drawio.png

通过Http post请求将第三方SDK的依赖坐标传参给Jenkins流水线,流水线接收参数后开始按照顺序自动的执行每一个声明的步骤,下面分别展开介绍每个步骤的实现流程:

4.1获取配置文件

首先解释配置文件是什么,在创建一个新的 Android 项目时,Android Studio 会自动生成几个重要的 Gradle 配置文件来简化项目构建和依赖管理。新建一个Android项目自动生成的文件如图所示:

47C6632B-2AAF-4e98-BB2A-C6BE6ECE9F7C.png

上图所示的几个Gradle配置文件,使得可以执行Gradle命令implementation("xxxx")依赖第三方SDK构建项目、编译打包,所以我们的配置文件就是这几个Gradle配置文件,这几个配置文件在我们的方案中是用于下载第三方SDK的aar包。

另外还有前面提到的敏感函数的列表,为了方便后续新增扩展敏感函数检测,采用Json格式的文件保存:

image2024-7-12_17-27-5.png

这个Json文件同时作为配置文件,在gitlab上创建一个仓库专门存放Gradle、Json这两个配置文件,仓库的文件列表如下:

image2024-8-2_11-15-43.png

准备好配置文件后,就可以配置流水线的第一个stage步骤,在流水线中新建一个stage,使用git命令将该配置仓库clone到本地,代码如下:

stage('Get Configuration') {
    steps {
        checkout(
                [$class           : 'GitSCM',
                 branches         : [[name: "master"]],
                 extensions       : [[$class: 'WipeWorkspace'], [$class: 'CheckoutOption', timeout: 20], [$class: 'CloneOption', noTags: false, reference: '', shallow: false, timeout: 20]],
                 userRemoteConfigs: [[credentialsId: 'keyId', url: 'https://xxxx.com/vesync-code-detect-configuration.git']]])
 
    }
}

4.2自定义敏感函数检测插件

问:什么是检测插件?

答:检查插件是一个纯Java项目打造的jar包,用于下载、反编译第三方SDK aar包获取java源码、并遍历扫描java代码中的敏感函数关键字,该插件内部实现流程如下:

4.2.1接收第三方SDK的依赖坐标和敏感函数配置文件

从main方法的参数中获取在命令行执行jar包时传入的参数:

  ...
  // 获取第三方SDK的依赖坐标,如:com.sensorsdata.analytics.android:SensorsAnalyticsSDK:6.7.9
  String libUrl = args[0];
  // 获取敏感函数配置文件
  String jsonPath = args[1]
  ...
}

4.2.2下载第三方SDK aar包

通过Runtime类执行Gradle命令下载第三方SDK aar包,实现代码如下

try {
    // 执行gradle命令下载依赖库
    String command = "./gradlew downloadLibs -PLIB_URL=" + libUrl + " -PIS_TRANSITIVE=true";
    // 使用Runtime执行命令
    Process process = Runtime.getRuntime().exec(command);
    // 读取命令的输出
    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
    // 等待命令执行完成
    process.waitFor();
    // 获取退出值
    int exitValue = process.exitValue();
    return exitValue;
} catch (Exception e) {
    System.out.println("执行gradle命令出错:" + e.getMessage());
}

4.2.3使用jadx反编译aar获取java源码

获取到的aar包是编译过的二进制文件,无法直接查看源码,所以需要对aar包进行反编译获取java源码。Jadx是一个开源的反编译工具,主要用于将 Dalvik 字节码从 apk、dex、aar、jar、aab 和 zip 文件反编译为 Java 代码。Jadx不仅提供GUI,还能以library库方式集成到Java项目使用,所以我们采用library依赖库的方式,主要的反编译实现代码如下:

/**
 * @param inputPath aar包的路径
 * @param outputPath 反编译输出的java源码路径
 * @return 反编译结果,成功返回0
 */
public int excuteJadx(String inputPath, String outputPath) {
    JadxArgs jadxArgs = new JadxArgs();
    jadxArgs.setInputFile(new File(inputPath));
    jadxArgs.setOutDir(new File(outputPath));
    jadxArgs.setCodeCache(new NoOpCodeCache());
    try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
        jadx.load();
        jadx.save();
    } catch (Exception e) {
        e.printStackTrace();
        return -1;
    }
    return 0;
}

4.2.4解析Josn文件获取敏感函数配置文件

配置文件是一个Json格式的敏感函数列表,内容有敏感函数关键字、敏感函数所在包名、敏感函数类型三个字段,通过解析Json文件获得敏感函数列表,由于敏感函数关键字是唯一的,所以敏感函数作为Map的key值,敏感函数所在包名作为value,保存到HashMap;同时使用HashSet保存所有敏感函数对应的包名,流程图如下:

敏感函数配置文件解析流程.drawio.png

4.2.5遍历所有java文件检测敏感函数关键字

这一步主要是判断该类是否有导入敏感函数相关的包名,以及判断文件中是否存在敏感函数关键字,如果存在则记录到xls表格,这里使用了开源库Poi实现excel表格记录功能,xls表格文件以第三方SDK的引用路径命名,主要流程图如下:

自定义敏感函数关键字检测流程.drawio.png

4.3下载检测插件

将检测插件的java项目打包成jar并将jar包上传到自己的Nexus仓库;在4.1中下载的Gradle配置文件,其中build.gradle.kts声明了一个下载检测插件的任务"downloadPlugin",该任务主要是将下载的检测插件jar包移动到plugin目录,并重命名为code-detect.jar方便后续在终端命令行运行jar包,gradle脚本代码如下:

dependencies {
    val tempLibUrl = getProperty("LIB_URL")
    // 不传LIB_URL参数,则下载代码检测插件,代码检测插件版本号在这更新即可
    val realLibUrl = tempLibUrl.ifBlank { "com.vesync:lib-code-detect:2.1-SNAPSHOT" }
    "implementation"(realLibUrl) {
        isTransitive = getProperty("IS_TRANSITIVE").toBooleanOrDefault()
    }
}
 
tasks.register<Copy>("downloadPlugin") {
    from(configurations.getByName("implementation"))
    into("plugin")
    rename {
        "code-detect.jar"
    }
}

在流水线中执行gradle命令“./gradlew downloadPlugin”,即可将敏感函数检测插件下载到plugin目录并重命名为“code-detect.jar”,流水线脚本如下:

stage('Download jar') {
    steps {
        script {
            // 授权
            sh "chmod u+x ./gradlew"
            // 执行gradle任务
            sh "./gradlew downloadPlugin"
        }
    }
}

4.4执行检测插件

上一步骤已经将检测插件jar包下载到本地,所以可以在流水线中执行jar命令运行检测插件,将第三方SDK的依赖坐标和敏感函数配置文件传进去,即可开始自动检测,代码如下:

stage('Execute jar') {
    steps {
        script {
            sh "java -jar ./plugin/code-detect.jar ${LIB_URL} ./sensitive-function.json"
        }
    }
}

其中${LIB_URL}参数就是从WebHook传过来的第三方SDK的依赖坐标参数,例如是“com.sensorsdata.analytics.android:SensorsAnalyticsSDK:6.7.9”,sensitive-function.json参数是在4.1中下载的敏感函数配置文件。

4.5输出检测报告

上一个步骤执行完后,如果有检测到敏感函数,会输出一份xls表格文件在本地存储,这一步骤是要把检测结果展示在jenkins任务页;

在流水线脚本中首先根据检测结果的文件名(以第三方SDK的依赖坐标命名)判断是否存在检测结果,如果存在则根据文件路径建立文件链接,接着通过pipeline的

currentBuild.description展示检测结果xls表格文件的下载链接在jenkins任务页。

在jenkins任务页可以直接下载检测结果xls表格,表格主要有四项信息:所在类名、行号、调用代码、敏感类型,格式如下:

image2024-11-4_12-2-44.png

5总结

以上就是Android SDK 隐私合规自动化检测方案,核心思想就是通过Jenkins pipeline自动完成整个检测过程,使用方法非常简单,发起http post请求触发Jenkins pipeline执行检测任务即可进行自动化检测,在http请求的body数据传参要检测的第三方SDK的依赖坐标,数据格式如下:

{"lib_url":"com.sensorsdata.analytics.android:SensorsAnalyticsSDK:6.7.9"}

经实战检测Vesync App正在使用的177个第三方SDK,均能正确检测出SDK存在哪些敏感函数调用。

通过Jenkins pipeline实现自动化检测第三方SDK是否存在敏感函数的功能,达到了一键自动化检测的效果,相比于目前市面上常见的检测方式,不仅解决了需要手动操作的痛点还能防止出现遗漏检测,还简单易用使用成本低方便在团队中推广应用、通过自定义检测插件和检测规则,使得检测范围更全面、且还可以扩展敏感函数检测类型,大大提高隐私合规检测的准确性和灵活性。希望本文能对开发者在隐私合规检测方面有所帮助和启发,让我们的App都能符合相关隐私法规,保护用户的隐私和数据安全,避免App出现隐私不合规导致下架的问题。

参考资料:

1.谷歌危险权限官方链接:developer.android.com/reference/a…

2.Jadx反编译工具Github链接:github.com/skylot/jadx

3.集成Jadx参考链接:github.com/skylot/jadx…