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,我一般看这几个点:

  1. App是否加载了外部URL?比如用户输入的链接、分享的链接
  2. 是否设置了setAllowFileAccess(true)?如果是,就能加载本地HTML
  3. 是否禁用了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漏洞 组件暴露漏洞 越权漏洞 硬编码密钥 明文存储 XSS注入 JS Bridge Activity暴露 Receiver暴露 水平越权 垂直越权 四大漏洞类型相互关联,实战中往往组合出现

这张图把本章的四个核心漏洞类型串起来了。你会发现,它们之间其实是有联系的——比如越权漏洞往往伴随着组件暴露,而WebView漏洞又可能被用来窃取本地存储的密钥。所以挖掘的时候,别孤立地看问题。

好了,这一章的内容就到这儿。记住我强调的:客户端没有秘密,服务端必须做校验。下次你拿到一个App,先别急着跑自动化工具,静下心来看代码逻辑,往往能发现意想不到的漏洞。


公众号:蓝海资料掘金营,微信deep3321