26、移动应用漏洞挖掘实战:本地存储漏洞、WebView漏洞、组件暴露漏洞与越权漏洞
各位好,我是老周。今天这章内容,咱们直接上干货——移动应用漏洞挖掘的实战环节。说实话,我见过太多安全报告写得花里胡哨,但真正能复现的漏洞没几个。所以这一章,我打算带大家走一遍真实挖掘流程,从本地存储到WebView,再到组件暴露,最后串一个越权漏洞的完整案例。
嗯,先别急着上手工具。咱们得先搞清楚:漏洞到底藏在哪里?
核心观点:移动端漏洞挖掘,本质上就是找「信任边界」的裂缝。开发者以为安全的地方,往往就是突破口。
26.1 本地存储漏洞:密钥和明文,谁在裸奔?
本地存储漏洞,说白了就是App把不该存的东西存到了不该存的地方。我最早接触这个,是在一个银行类App的审计中。当时我反编译APK,直接在SharedPreferences里找到了数据库密码——明文,连Base64都没做。
常见的本地存储漏洞分两类:
- 硬编码密钥:密钥写在代码里,反编译就能看到
- 明文存储敏感数据:密码、Token、身份证号直接存本地文件
硬编码密钥的挖掘方法
我个人习惯用Jadx或GDA直接反编译APK,然后搜索关键词:
// 常见搜索关键词
"secret" "key" "password" "token" "api_key" "private"
"AES" "DES" "RSA" "iv" "salt"
"SharedPreferences" "getString" "putString"
举个例子,我曾经在一个社交App里找到这么一段:
// 反编译后的伪代码
String apiKey = "aGVsbG8td29ybGQtc2VjcmV0LWtleQ==";
String iv = "MTIzNDU2Nzg5MDEyMzQ1Ng==";
// 实际上就是 "hello-world-secret-key" 和 "1234567890123456" 的Base64
你想想看,Base64编码能叫加密吗?说白了就是脱裤子放屁。攻击者拿到这个,就能伪造API请求了。
避坑指南:我曾经见过一个团队把密钥放在Native层(SO文件)里,以为这样就安全了。结果我用IDA Pro一分析,字符串还是明文的。记住:客户端没有真正的秘密。密钥应该放在服务端,或者使用Android Keystore / iOS Keychain配合生物识别。
明文存储的检查点
检查明文存储,我一般看这几个地方:
| 存储位置 | 常见风险 | 挖掘方法 |
|---|---|---|
| SharedPreferences | 明文存密码、Token | adb pull /data/data/包名/shared_prefs/ |
| SQLite数据库 | 聊天记录、账号信息未加密 | adb pull 后直接用SQLite浏览器打开 |
| 外部存储 | 日志文件、缓存文件泄露 | adb shell ls -la /sdcard/Android/data/包名/ |
| Keychain(iOS) | 未设置访问权限 | 使用Keychain-Dumper工具 |
嗯,这里要注意:很多App会把用户Token存在SharedPreferences里,而且权限是MODE_WORLD_READABLE。这就意味着任何App都能读你的Token——典型的权限提升漏洞。
26.2 WebView漏洞:JavaScript Bridge是把双刃剑
WebView漏洞,我愿称之为「移动端的XSS乐园」。为什么?因为很多开发者把WebView当成一个浏览器来用,却忘了给它上锁。
最常见的两个问题:
- WebView XSS:加载的网页内容不可控,导致脚本注入
- JavaScript Bridge接口暴露:JS可以调用Native方法,如果接口没做校验,攻击者就能远程执行任意代码
JavaScript Bridge漏洞挖掘
我记得有一次,我审计一个电商App。它有个功能:在WebView里点击「分享」按钮,会调起Native的分享面板。实现方式是这样的:
// Java侧
webView.addJavascriptInterface(new JsBridge(), "NativeBridge");
// JsBridge类
public class JsBridge {
@JavascriptInterface
public void share(String title, String url) {
// 调起系统分享
}
@JavascriptInterface
public void openUrl(String url) {
// 直接打开URL,没有校验
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
}
问题出在openUrl方法上。攻击者只要在WebView里注入一段JS:
// 攻击代码
window.NativeBridge.openUrl("file:///data/data/com.example.app/shared_prefs/user_info.xml");
就能直接读取本地文件。更狠一点,可以构造tel://或sms://协议,甚至调用intent://启动任意Activity。
警告:Android 4.2以上版本,只有加了@JavascriptInterface注解的方法才能被JS调用。但很多App还在用低版本API,或者注解了不该暴露的方法。检查时重点关注:addJavascriptInterface的调用处,以及所有被注解的方法。
WebView XSS的挖掘思路
挖掘WebView XSS,我一般看这几个点:
- App是否加载了外部URL?比如用户输入的链接、分享的链接
- 是否设置了
setAllowFileAccess(true)?如果是,就能加载本地HTML - 是否禁用了JavaScript?如果没禁用,且加载了不可信内容,就是XSS
举个例子,一个新闻类App,用户可以在文章里插入图片。如果图片URL没做过滤,攻击者可以构造:
<img src="x" onerror="alert(document.cookie)">
嗯,虽然移动端WebView的Cookie作用有限,但如果这个WebView能访问内部API,那就能拿到内网数据了。
26.3 组件暴露漏洞:Activity和Broadcast Receiver的「后门」
组件暴露,说白了就是App的Activity、Service、Broadcast Receiver、Content Provider被其他App调用了。Android的四大组件,默认都是不暴露的,但很多开发者为了省事,给组件加了exported="true",或者加了
Activity暴露漏洞
我记得有个案例:一个银行App的「忘记密码」页面,Activity是暴露的。攻击者可以直接通过ADB启动这个Activity,绕过登录验证:
adb shell am start -n com.example.bank/.activity.ResetPasswordActivity
更严重的是,有些Activity会接收Intent中的参数。比如:
// 暴露的Activity
Intent intent = getIntent();
String url = intent.getStringExtra("url");
webView.loadUrl(url); // 直接加载外部URL
攻击者可以构造:
adb shell am start -n com.example.app/.WebViewActivity --es url "file:///data/data/com.example.app/databases/secret.db"
直接读取数据库文件。
Broadcast Receiver暴露漏洞
Broadcast Receiver暴露,通常会导致信息泄露或越权操作。我见过一个天气App,它的Receiver会接收「更新天气」的广播,而且没有权限校验:
// 暴露的Receiver
public class WeatherReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String city = intent.getStringExtra("city");
// 直接发起网络请求更新天气
updateWeather(city);
}
}
攻击者可以发送恶意广播:
adb shell am broadcast -a com.example.UPDATE_WEATHER --es city "Beijing"
虽然这个例子危害不大,但如果Receiver里执行的是「转账」、「删除数据」等操作,那就严重了。
挖掘技巧:我一般用Drozer工具来扫描暴露的组件。命令很简单:drozer console connect,然后run app.activity.info -a 包名。它会列出所有暴露的Activity、Service、Receiver。然后逐个分析Intent参数,看能不能构造恶意输入。
26.4 实战:挖掘一个社交App的越权漏洞
好了,前面讲了这么多,咱们来串一个完整的案例。这是一个真实的社交App,我给它起个代号叫「SocialApp」。
第一步:信息收集
我拿到APK后,先用Jadx反编译。发现它的API请求都带一个Authorization: Bearer {token}头。Token存在SharedPreferences里,文件名是user_prefs.xml。
嗯,这里有个小细节:Token是明文存的,但这不是今天的重点。重点是——我注意到它的API接口设计有问题。
第二步:发现越权点
在反编译代码中,我找到了一个API调用:
// 获取用户资料
String url = "https://api.socialapp.com/v1/user/profile?user_id=" + userId;
// 注意:userId是从Intent中获取的,没有校验是否等于当前登录用户
也就是说,只要修改user_id参数,就能查看任意用户的资料。但问题来了:Token是跟当前用户绑定的,服务端应该会校验Token对应的userId和请求的userId是否一致吧?
我决定测试一下。用Burp Suite抓包,修改user_id为另一个用户的ID,结果——返回了那个用户的手机号和邮箱。
这就是典型的水平越权漏洞:攻击者可以查看其他用户的敏感信息。
第三步:扩大危害
光看资料还不够。我继续翻代码,发现了一个「修改密码」的接口:
// 修改密码
String url = "https://api.socialapp.com/v1/user/change_password";
// POST参数:old_password, new_password, user_id
注意,这里竟然传了user_id!而且没有校验旧密码是否正确(或者说,旧密码校验逻辑有缺陷)。我尝试修改user_id为另一个用户,然后随便填一个旧密码——结果密码修改成功了。
这就从越权变成了任意用户密码重置。攻击者可以修改任何人的密码,然后登录账号。
教训:这个漏洞的根本原因是服务端没有做「主体校验」。Token只能证明「你是谁」,但服务端没有检查「你正在操作的是不是你的资源」。记住:永远不要相信客户端传来的user_id,应该从Token中解析出当前用户ID。
第四步:漏洞报告
我整理了一份完整的漏洞报告,包含:
- 漏洞类型:水平越权 + 任意用户密码重置
- 影响范围:所有用户
- 复现步骤:抓包修改user_id参数
- 修复建议:服务端从Token中获取用户ID,不要信任客户端参数
嗯,这个漏洞最后给了个高危评级。厂商当天就修复了。
知识体系总览
为了让大家更直观地理解本章的知识结构,我画了一张图:
这张图把本章的四个核心漏洞类型串起来了。你会发现,它们之间其实是有联系的——比如越权漏洞往往伴随着组件暴露,而WebView漏洞又可能被用来窃取本地存储的密钥。所以挖掘的时候,别孤立地看问题。
好了,这一章的内容就到这儿。记住我强调的:客户端没有秘密,服务端必须做校验。下次你拿到一个App,先别急着跑自动化工具,静下心来看代码逻辑,往往能发现意想不到的漏洞。
公众号:蓝海资料掘金营,微信deep3321