2
月
在社区成长 2 月
LEVEL 1
1 XP
一、背景
Android 应用经常通过 Activity、Service、BroadcastReceiver、ContentProvider 和 WebView 承载登录、支付、客服、活动页等功能。如果组件导出策略、深链校验或 WebView 配置不当,外部应用可能绕过正常业务入口调用内部页面,或让不可信网页接触原生能力。
这类问题通常不是单个危险 API 导致,而是多项配置组合形成攻击链。例如:某个 Activity 可被外部启动,同时接受任意 URL,再将 URL 交给启用了 JavaScript 接口的 WebView 加载。本文只面向自有应用、授权测试包和本地环境,重点介绍如何发现、修复并验证此类风险,不涉及真实目标利用。
二、问题原理与主要攻击面
1. 导出组件
android:exported=true 表示组件可以被其他应用调用。即使没有显式设置,在旧系统或特定 intent-filter 配置下,也可能出现与开发预期不同的导出行为。Android 12 起,带有 intent-filter 的组件必须明确声明 android:exported,但明确声明并不代表配置一定安全。
常见风险包括:
(1)内部调试 Activity 对外暴露;
(2)导出的 Service 未验证调用方;
(3)BroadcastReceiver 接收外部伪造广播;
(4)ContentProvider 权限过宽;
(5)深链入口只检查 URL 格式,没有验证登录状态和业务权限。
2. WebView
WebView 的主要风险点包括:
(1)允许加载任意外部 URL;
(2)不必要地启用 JavaScript;
(3)通过 addJavascriptInterface 暴露敏感原生方法;
(4)允许 file:// 访问或文件域跨域访问;
(5)忽略 TLS 证书错误;
(6)把 Token、手机号或设备标识拼入 URL;
(7)调试版本配置被误带入生产环境。
三、前置条件
准备以下本地环境:
1. Android Studio 与 Android SDK;
2. 模拟器或专用测试设备;
3. 自有应用的 Debug APK;
4. adb 命令行工具;
5. 可修改源代码与 AndroidManifest.xml;
6. 一个仅在本机使用的测试页面或 Mock 服务。
不要在未授权应用、生产账号或真实用户数据上执行测试。建议测试包使用独立 applicationId,例如 com.example.demo.debug,并关闭生产接口访问能力。
四、分步骤实践
步骤一:建立组件清单
先从源代码检查 AndroidManifest.xml,记录所有包含以下属性的组件:
android:exported=true
intent-filter
android
ermission
android:grantUriPermissions
android:authorities
也可以在安装测试包后查看系统解析结果:
adb shell dumpsys package com.example.demo
重点核对 Activities、Services、Receivers、Providers 四部分。系统解析结果比只看主 Manifest 更可靠,因为依赖库可能通过 Manifest Merge 添加组件。
在 Android Studio 中打开 Merged Manifest,确认每个导出组件的来源。对于没有外部调用需求的组件,应显式设置:
<activity
android:name=".InternalActivity"
android:exported="false" />
如果组件必须导出,应同时回答三个问题:谁可以调用、可以传入什么数据、调用后可以执行什么操作。
步骤二:检查导出组件的访问控制
需要对外提供但仅供同一签名应用使用的组件,可配置签名级权限:
<permission
android:name="com.example.demo.permission.INTERNAL_API"
android
rotectionLevel="signature" />
<service
android:name=".SyncService"
android:exported="true"
android
ermission="com.example.demo.permission.INTERNAL_API" />
权限不能代替业务校验。对于涉及用户数据或敏感操作的入口,还应检查登录状态、用户权限、数据归属和请求时效。不要仅依赖调用方传入的 userId、role 或 isAdmin 等字段。
步骤三:审计深链参数
对 app://、https:// 等深链入口建立允许列表。至少校验:
1. scheme 和 host 是否完全匹配;
2. path 是否属于预期路由;
3. 参数数量、长度与字符范围;
4. redirect、url、callback 等参数是否指向允许域名;
5. 敏感页面是否重新验证会话和权限;
6. 是否存在通过编码、大小写或尾点绕过域名判断的情况。
不要使用 contains、startsWith 等模糊方式判断可信域名。应解析为 Uri 后分别比较 scheme、标准化后的 host 和端口。例如只允许 HTTPS,并将主机名与固定集合进行精确匹配。
步骤四:加固 WebView
如果业务不需要 JavaScript,应保持关闭:
webView.settings.javaScriptEnabled = false
如果必须启用,则只允许加载经过严格校验的可信页面,不要向任意页面暴露 JavaScript Bridge。对文件访问能力按需关闭:
webView.settings.allowFileAccess = false
webView.settings.allowContentAccess = false
在兼容性允许的前提下,还应关闭文件域跨域能力:
webView.settings.allowFileAccessFromFileURLs = false
webView.settings.allowUniversalAccessFromFileURLs = false
生产版本应关闭 WebView 调试:
if (BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true)
} else {
WebView.setWebContentsDebuggingEnabled(false)
}
对于证书错误,禁止调用 handler.proceed() 继续访问。正确做法是取消加载并记录不包含敏感数据的诊断信息:
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
handler?.cancel()
}
步骤五:限制 WebView 导航范围
页面内部跳转也必须重新检查,不能只验证首次传入的 URL。建议在 WebViewClient 中解析目标 URI,只允许 HTTPS 和固定域名集合;不符合规则的导航应交由安全的外部处理逻辑或直接阻止。
允许列表应基于规范化后的主机名进行精确比较,并显式限制端口。不要把用户输入直接拼接成 javascript:、file: 或 intent: 地址,也不要仅通过字符串替换进行过滤。
步骤六:配置网络安全策略
生产应用应默认禁用明文流量:
<application
android:usesCleartextTraffic="false"
android:networkSecurityConfig="@xml/network_security_config" />
开发环境如果必须访问本地 HTTP Mock 服务,应通过 Debug 专用 Manifest 或 debug-overrides 管理,不要为了测试把生产版本全局改为允许明文流量。
步骤七:执行修复回归
安装修复后的 Debug 包:
adb install -r app-debug.apk
重新运行:
adb shell dumpsys package com.example.demo
确认内部组件不再出现在可外部访问的组件范围内。对于已设置 exported=false 的 Activity,可在本地设备上执行一次预期失败的启动验证:
adb shell am start -n com.example.demo/.InternalActivity
从 adb shell 发起时应被系统拒绝。该检查只用于自有测试包,目的是确认访问控制生效。
对于必须导出的深链 Activity,回归测试应覆盖:合法链接、错误 scheme、错误 host、超长参数、缺失参数、未登录状态和无权限账号。测试断言应关注应用是否安全拒绝,而不是仅检查是否发生崩溃。
五、自动化检测建议
可在 CI 中加入以下防御性检查:
1. 对 Merged Manifest 进行差异比较,新增 exported=true 时阻断构建并要求安全评审;
2. 搜索 addJavascriptInterface、setJavaScriptEnabled、onReceivedSslError、setAllowFileAccess 等敏感调用;
3. 检查 Release 构建是否开启 WebView 调试或明文流量;
4. 对深链路由编写单元测试,验证域名允许列表和参数边界;
5. 使用 Android Lint 和依赖漏洞扫描,并对高风险告警进行人工复核。
简单的文本搜索只能用于发现线索,不能直接判定漏洞。例如启用 JavaScript 可能是正常业务需求,真正需要判断的是页面来源是否可信、桥接接口是否最小化,以及敏感操作是否有原生侧授权。
六、风险与注意事项
1. 不要通过忽略证书错误解决抓包或测试环境兼容问题,应使用 Debug 专用证书配置;
2. 不要把访问令牌、Cookie、身份证号等内容写入日志;
3. 修改 exported 属性前要确认外部 SDK、系统回调和 App Link 是否依赖该组件;
4. 签名权限只对使用相同签名证书的应用有效,证书管理不当会削弱边界;
5. WebView 域名允许列表要考虑重定向后的最终地址;
6. 修复后要同时测试旧系统和目标最低版本,避免安全修复引入兼容性故障。
七、常见问题
问:组件设置 exported=false 后是否绝对安全?
答:它能阻止其他应用直接调用,但应用内部仍可能通过不可信参数间接触发敏感逻辑,因此还需要输入验证和业务授权。
问:只允许公司域名就可以安全启用 JavaScript Bridge 吗?
答:不能简单下结论。还需评估该域名是否承载用户内容、是否存在开放重定向、第三方脚本和内容注入风险。桥接接口应尽量少,只暴露无敏感副作用的方法。
问:测试时需要关闭所有证书校验吗?
答:不需要,也不建议。应使用 Debug 构建、受控测试 CA 或本地 Mock 服务,并确保相关配置不会进入 Release 包。
问:为什么 Manifest 中没看到的组件也会出现在安装包里?
答:依赖库可能通过 Manifest Merge 注入组件,因此审计时应以 Merged Manifest 和系统解析结果为准。
八、总结
Android 组件与 WebView 安全的核心是缩小暴露面并在每个信任边界重新验证。无外部需求的组件应明确禁止导出;必须导出的组件要配置权限并实施业务授权;深链参数必须结构化解析;WebView 应关闭非必要能力、限制导航范围、拒绝证书错误,并将调试配置与生产配置彻底隔离。
有效的修复不应停留在修改一处 Manifest,而应形成“组件盘点—参数校验—WebView 加固—本地验证—CI 回归”的闭环。这样既能降低外部调用和不可信内容带来的风险,也能避免后续版本或第三方依赖重新引入同类问题。
Android 应用经常通过 Activity、Service、BroadcastReceiver、ContentProvider 和 WebView 承载登录、支付、客服、活动页等功能。如果组件导出策略、深链校验或 WebView 配置不当,外部应用可能绕过正常业务入口调用内部页面,或让不可信网页接触原生能力。
这类问题通常不是单个危险 API 导致,而是多项配置组合形成攻击链。例如:某个 Activity 可被外部启动,同时接受任意 URL,再将 URL 交给启用了 JavaScript 接口的 WebView 加载。本文只面向自有应用、授权测试包和本地环境,重点介绍如何发现、修复并验证此类风险,不涉及真实目标利用。
二、问题原理与主要攻击面
1. 导出组件
android:exported=true 表示组件可以被其他应用调用。即使没有显式设置,在旧系统或特定 intent-filter 配置下,也可能出现与开发预期不同的导出行为。Android 12 起,带有 intent-filter 的组件必须明确声明 android:exported,但明确声明并不代表配置一定安全。
常见风险包括:
(1)内部调试 Activity 对外暴露;
(2)导出的 Service 未验证调用方;
(3)BroadcastReceiver 接收外部伪造广播;
(4)ContentProvider 权限过宽;
(5)深链入口只检查 URL 格式,没有验证登录状态和业务权限。
2. WebView
WebView 的主要风险点包括:
(1)允许加载任意外部 URL;
(2)不必要地启用 JavaScript;
(3)通过 addJavascriptInterface 暴露敏感原生方法;
(4)允许 file:// 访问或文件域跨域访问;
(5)忽略 TLS 证书错误;
(6)把 Token、手机号或设备标识拼入 URL;
(7)调试版本配置被误带入生产环境。
三、前置条件
准备以下本地环境:
1. Android Studio 与 Android SDK;
2. 模拟器或专用测试设备;
3. 自有应用的 Debug APK;
4. adb 命令行工具;
5. 可修改源代码与 AndroidManifest.xml;
6. 一个仅在本机使用的测试页面或 Mock 服务。
不要在未授权应用、生产账号或真实用户数据上执行测试。建议测试包使用独立 applicationId,例如 com.example.demo.debug,并关闭生产接口访问能力。
四、分步骤实践
步骤一:建立组件清单
先从源代码检查 AndroidManifest.xml,记录所有包含以下属性的组件:
android:exported=true
intent-filter
android
android:grantUriPermissions
android:authorities
也可以在安装测试包后查看系统解析结果:
adb shell dumpsys package com.example.demo
重点核对 Activities、Services、Receivers、Providers 四部分。系统解析结果比只看主 Manifest 更可靠,因为依赖库可能通过 Manifest Merge 添加组件。
在 Android Studio 中打开 Merged Manifest,确认每个导出组件的来源。对于没有外部调用需求的组件,应显式设置:
<activity
android:name=".InternalActivity"
android:exported="false" />
如果组件必须导出,应同时回答三个问题:谁可以调用、可以传入什么数据、调用后可以执行什么操作。
步骤二:检查导出组件的访问控制
需要对外提供但仅供同一签名应用使用的组件,可配置签名级权限:
<permission
android:name="com.example.demo.permission.INTERNAL_API"
android
<service
android:name=".SyncService"
android:exported="true"
android
权限不能代替业务校验。对于涉及用户数据或敏感操作的入口,还应检查登录状态、用户权限、数据归属和请求时效。不要仅依赖调用方传入的 userId、role 或 isAdmin 等字段。
步骤三:审计深链参数
对 app://、https:// 等深链入口建立允许列表。至少校验:
1. scheme 和 host 是否完全匹配;
2. path 是否属于预期路由;
3. 参数数量、长度与字符范围;
4. redirect、url、callback 等参数是否指向允许域名;
5. 敏感页面是否重新验证会话和权限;
6. 是否存在通过编码、大小写或尾点绕过域名判断的情况。
不要使用 contains、startsWith 等模糊方式判断可信域名。应解析为 Uri 后分别比较 scheme、标准化后的 host 和端口。例如只允许 HTTPS,并将主机名与固定集合进行精确匹配。
步骤四:加固 WebView
如果业务不需要 JavaScript,应保持关闭:
webView.settings.javaScriptEnabled = false
如果必须启用,则只允许加载经过严格校验的可信页面,不要向任意页面暴露 JavaScript Bridge。对文件访问能力按需关闭:
webView.settings.allowFileAccess = false
webView.settings.allowContentAccess = false
在兼容性允许的前提下,还应关闭文件域跨域能力:
webView.settings.allowFileAccessFromFileURLs = false
webView.settings.allowUniversalAccessFromFileURLs = false
生产版本应关闭 WebView 调试:
if (BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true)
} else {
WebView.setWebContentsDebuggingEnabled(false)
}
对于证书错误,禁止调用 handler.proceed() 继续访问。正确做法是取消加载并记录不包含敏感数据的诊断信息:
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
handler?.cancel()
}
步骤五:限制 WebView 导航范围
页面内部跳转也必须重新检查,不能只验证首次传入的 URL。建议在 WebViewClient 中解析目标 URI,只允许 HTTPS 和固定域名集合;不符合规则的导航应交由安全的外部处理逻辑或直接阻止。
允许列表应基于规范化后的主机名进行精确比较,并显式限制端口。不要把用户输入直接拼接成 javascript:、file: 或 intent: 地址,也不要仅通过字符串替换进行过滤。
步骤六:配置网络安全策略
生产应用应默认禁用明文流量:
<application
android:usesCleartextTraffic="false"
android:networkSecurityConfig="@xml/network_security_config" />
开发环境如果必须访问本地 HTTP Mock 服务,应通过 Debug 专用 Manifest 或 debug-overrides 管理,不要为了测试把生产版本全局改为允许明文流量。
步骤七:执行修复回归
安装修复后的 Debug 包:
adb install -r app-debug.apk
重新运行:
adb shell dumpsys package com.example.demo
确认内部组件不再出现在可外部访问的组件范围内。对于已设置 exported=false 的 Activity,可在本地设备上执行一次预期失败的启动验证:
adb shell am start -n com.example.demo/.InternalActivity
从 adb shell 发起时应被系统拒绝。该检查只用于自有测试包,目的是确认访问控制生效。
对于必须导出的深链 Activity,回归测试应覆盖:合法链接、错误 scheme、错误 host、超长参数、缺失参数、未登录状态和无权限账号。测试断言应关注应用是否安全拒绝,而不是仅检查是否发生崩溃。
五、自动化检测建议
可在 CI 中加入以下防御性检查:
1. 对 Merged Manifest 进行差异比较,新增 exported=true 时阻断构建并要求安全评审;
2. 搜索 addJavascriptInterface、setJavaScriptEnabled、onReceivedSslError、setAllowFileAccess 等敏感调用;
3. 检查 Release 构建是否开启 WebView 调试或明文流量;
4. 对深链路由编写单元测试,验证域名允许列表和参数边界;
5. 使用 Android Lint 和依赖漏洞扫描,并对高风险告警进行人工复核。
简单的文本搜索只能用于发现线索,不能直接判定漏洞。例如启用 JavaScript 可能是正常业务需求,真正需要判断的是页面来源是否可信、桥接接口是否最小化,以及敏感操作是否有原生侧授权。
六、风险与注意事项
1. 不要通过忽略证书错误解决抓包或测试环境兼容问题,应使用 Debug 专用证书配置;
2. 不要把访问令牌、Cookie、身份证号等内容写入日志;
3. 修改 exported 属性前要确认外部 SDK、系统回调和 App Link 是否依赖该组件;
4. 签名权限只对使用相同签名证书的应用有效,证书管理不当会削弱边界;
5. WebView 域名允许列表要考虑重定向后的最终地址;
6. 修复后要同时测试旧系统和目标最低版本,避免安全修复引入兼容性故障。
七、常见问题
问:组件设置 exported=false 后是否绝对安全?
答:它能阻止其他应用直接调用,但应用内部仍可能通过不可信参数间接触发敏感逻辑,因此还需要输入验证和业务授权。
问:只允许公司域名就可以安全启用 JavaScript Bridge 吗?
答:不能简单下结论。还需评估该域名是否承载用户内容、是否存在开放重定向、第三方脚本和内容注入风险。桥接接口应尽量少,只暴露无敏感副作用的方法。
问:测试时需要关闭所有证书校验吗?
答:不需要,也不建议。应使用 Debug 构建、受控测试 CA 或本地 Mock 服务,并确保相关配置不会进入 Release 包。
问:为什么 Manifest 中没看到的组件也会出现在安装包里?
答:依赖库可能通过 Manifest Merge 注入组件,因此审计时应以 Merged Manifest 和系统解析结果为准。
八、总结
Android 组件与 WebView 安全的核心是缩小暴露面并在每个信任边界重新验证。无外部需求的组件应明确禁止导出;必须导出的组件要配置权限并实施业务授权;深链参数必须结构化解析;WebView 应关闭非必要能力、限制导航范围、拒绝证书错误,并将调试配置与生产配置彻底隔离。
有效的修复不应停留在修改一处 Manifest,而应形成“组件盘点—参数校验—WebView 加固—本地验证—CI 回归”的闭环。这样既能降低外部调用和不可信内容带来的风险,也能避免后续版本或第三方依赖重新引入同类问题。