静态代码分析:从Manifest看组件安全
做Android安全这么多年,我有个习惯——拿到一个APK,第一件事不是反编译代码,而是先打开AndroidManifest.xml。为什么?因为Manifest就像房子的设计图纸,哪里开了门、哪里装了锁、哪里留了窗户,一眼就能看出来。很多安全漏洞,其实在Manifest阶段就能发现。
今天我们就聊聊,怎么通过静态分析Manifest文件,找出Activity劫持、Provider数据泄露这些风险点。
一、组件导出属性:门到底开没开?
组件导出,说白了就是「这个组件能不能被其他App调用」。你想想看,一个Activity如果被标记为exported="true",那任何App都能启动它。这就像你家大门没上锁,谁都能推门进来。
核心判断逻辑:
- exported="true" → 组件对外暴露,任何App可调用
- exported="false" → 组件仅内部使用
- 未显式设置 → 取决于是否有Intent Filter
这里有个坑,我踩过好几次。如果组件声明了Intent Filter,系统默认会把exported设为true。很多开发者以为不写exported就是安全的,其实恰恰相反。
<!-- 危险写法:隐式导出 -->
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="com.example.LOGIN" />
</intent-filter>
</activity>
<!-- 安全写法:显式声明 -->
<activity
android:name=".LoginActivity"
android:exported="false">
<intent-filter>
<action android:name="com.example.LOGIN" />
</intent-filter>
</activity>
我个人建议,所有组件都显式声明exported属性。别偷懒,别依赖默认行为。默认行为这东西,不同厂商、不同Android版本可能不一样,你赌不起。
二、权限声明:谁有钥匙?
权限声明是第二道防线。就算组件导出了,如果设置了权限,那只有持有对应权限的App才能调用。这就像门虽然开着,但门口有个保安查证件。
我在项目中遇到过一种情况:某个ContentProvider声明了权限,但权限的保护级别是normal。normal级别意味着什么?任何App只要在Manifest里声明一下就能获取,系统不会弹窗询问用户。说白了,形同虚设。
| 保护级别 | 含义 | 风险等级 |
|---|---|---|
| normal | App声明即可获取,无需用户确认 | 低(但容易被滥用) |
| dangerous | 运行时需用户授权 | 中 |
| signature | 仅同签名App可获取 | 高(推荐) |
| signatureOrSystem | 系统应用或同签名App | 高 |
我的经验:对于敏感组件,尽量用signature级别的权限。这样只有你自己签名的App才能调用,第三方恶意App根本拿不到钥匙。
三、Intent Filter配置:暴露的攻击面
Intent Filter定义了组件能响应哪些隐式Intent。你想想看,如果一个Activity声明了
这里有个经典攻击场景——Activity劫持。恶意App可以注册一个与目标App相同Action的Activity,然后通过Intent匹配机制,劫持用户的点击操作。
<!-- 容易被劫持的配置 -->
<activity android:name=".PaymentActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" android:host="pay.example.com" />
</intent-filter>
</activity>
这段配置有什么问题?它声明了VIEW Action,并且没有设置exported="false"。这意味着任何App都可以通过一个https://pay.example.com的链接来启动这个Activity。如果恶意App也注册了同样的Intent Filter,系统可能会弹出选择框,用户一不小心就点错了。
避坑指南:我曾经审计过一个金融App,它的支付确认Activity就用了上面的配置。攻击者只需要写一个简单的App,注册同样的Intent Filter,就能在用户支付时弹出自己的伪造界面。嗯,后来我们强制要求所有涉及敏感操作的Activity必须使用显式Intent调用,并且exported设为false。
四、ContentProvider:数据泄露的重灾区
ContentProvider是Android组件安全的重灾区。为什么?因为它直接操作数据。一个导出的ContentProvider,如果没有做好权限控制,就等于把数据库直接暴露给了全世界。
我见过最离谱的一个案例:某个社交App的ContentProvider导出了,而且没有设置任何权限。攻击者只需要知道URI路径,就能通过ContentResolver.query()读取用户的聊天记录、通讯录、甚至密码。
<!-- 高危配置 -->
<provider
android:name=".DataProvider"
android:authorities="com.example.data"
android:exported="true" />
<!-- 安全配置 -->
<provider
android:name=".DataProvider"
android:authorities="com.example.data"
android:exported="false" />
如果确实需要导出Provider,那必须做好三件事:
- 设置readPermission和writePermission
- 使用signature级别的权限
- 对URI进行路径校验,防止目录遍历
五、知识体系总览
说了这么多,我们来梳理一下。Manifest静态分析的核心逻辑,其实就是回答三个问题:
- 门开了吗?(exported属性)
- 谁有钥匙?(权限声明)
- 门牌号写了什么?(Intent Filter)
下面这张图,是我自己总结的Manifest安全分析流程,你可以对照着用。
这张图的核心逻辑很简单:拿到Manifest后,逐个检查每个组件的exported、权限和Intent Filter。三个维度都安全,组件才算安全。任何一个维度出问题,都可能成为攻击入口。
六、实战建议
最后,分享几个我在实际项目中用到的检查清单:
Manifest安全检查清单:
- 所有组件是否显式声明了exported?
- 导出的组件是否设置了合理的权限?
- 权限的保护级别是否至少为signature?
- Intent Filter是否暴露了敏感Action?
- ContentProvider的URI路径是否做了校验?
- 是否有不必要的
声明?
嗯,其实这些检查点都不复杂。但就是这些看似简单的地方,最容易出问题。我见过太多App,功能做得花里胡哨,结果Manifest里一堆漏洞。安全这东西,往往就藏在细节里。
公众号:蓝海资料掘金营,微信deep3321