3.5组件安全检测

文章目录
  1. 1. 1 Activity组件导出检测 # 05001
  2. 2. 2 Service组件导出检测 # 05002
  3. 3. 3 Receiver组件导出检测 # 05003
  4. 4. 4 Provider组件导出检测 # 05004
  5. 5. (未实现)ContentProvider目录遍历漏洞检测
  6. 6. 5 Provider:grant-uri-permission属性检测 # 05005
  7. 7. 6 Intent-Based攻击检测 # 05006
  8. 8. 7 Intent Scheme URL漏洞攻击检测 # 05007
  9. 9. 8 应用本地拒绝服务漏洞检测 # 05008
  10. 10. 9 manifest中定义组件未实现检测 # 05009
  11. 11. 10 Debug或Test敏感测试组件泄露检测 # 05010
  12. 12. 11 Intent不安全反射风险检测 # 05011

1 Activity组件导出检测 # 05001

Activity组件对外暴露会导致数据泄露和恶意的dos攻击。

风险等级: 提醒

检测规则:

  1. 先检查组件的android:exported属性的值,若为true或者未设置改属性的值,则继续进行第2步;若为false,则该组件不具有导出性质,是安全的。
  2. 若组件的含有标签,即使未设置exported属性,默认也会设置为true,若组件不含有标签,则未设置exported属性值,默认会设置为true。根据该规则,可以确认exported是true还是false。如果是false则是安全的,若是true,则继续进行第3步。
  3. 判断android:permission属性的值,空、normal、dangerous是不安全的,signature、signatureOrSystem是安全的。

建议:

  • 最小化组件暴露。对不会参与跨应用调用的组件添加android:exported=false属性
  • 设置组件访问权限。对跨应用间调用的组件或者公开的receiver、service、activity和activity-alias设置权限,同时将权限的protectionLevel设置为signaturesignatureOrSystem
  • 组件传输数据验证。对组件之间,特别是跨应用的组件之间的数据传入与返回做验证和增加异常处理,防止恶意调试数据传入,更要防止敏感数据返回

2 Service组件导出检测 # 05002

Service组件对外暴露会导致数据泄露和恶意的dos攻击。

风险等级: 提醒

检测规则:

规则同1

建议:

  • 最小化组件暴露。对不会参与跨应用调用的组件添加android:exported=false属性
  • 设置组件访问权限。对跨应用间调用的组件或者公开的receiver、service、activity和activity-alias设置权限,同时将权限的protectionLevel设置为signaturesignatureOrSystem
  • 组件传输数据验证。对组件之间,特别是跨应用的组件之间的数据传入与返回做验证和增加异常处理,防止恶意调试数据传入,更要防止敏感数据返回

3 Receiver组件导出检测 # 05003

Receiver组件对外暴露会导致数据泄露和恶意的dos攻击。

风险等级: 提醒

检测规则:

规则同1

建议:

  • 最小化组件暴露。对不会参与跨应用调用的组件添加android:exported=false属性
  • 设置组件访问权限。对跨应用间调用的组件或者公开的receiver、service、activity和activity-alias设置权限,同时将权限的protectionLevel设置为signaturesignatureOrSystem
  • 组件传输数据验证。对组件之间,特别是跨应用的组件之间的数据传入与返回做验证和增加异常处理,防止恶意调试数据传入,更要防止敏感数据返回

4 Provider组件导出检测 # 05004

provider组件导出可能会带来信息泄露隐患。API Level在17以下的所有应用的android:exported属性默认值为true,17及以上默认值为false。

风险等级:提醒

检测规则:

规则同1

建议:

  • 最小化组件暴露。对不会参与跨应用调用的组件添加android:exported=false属性
  • 设置组件访问权限。对导出的provider组件设置权限,同时将权限的protectionLevel设置为signaturesignatureOrSystem
  • 由于contentprovider无法在android2.2(API-8)申明为私有。故建议将minSdkVersion设为8以上。

(未实现)ContentProvider目录遍历漏洞检测

该漏洞由于Content Provider组件暴露,没有对Content Provider组件访问权限进行限制且对Uri路径没有进行过滤,攻击者通过Content Provider实现的OpenFile接口进行攻击,如通过../的方式访问任意的目录文件,造成隐私泄露。

ContentProvider目录遍历漏洞检测-说明

风险等级:提醒

检测方法:

找出导出的provider组件

使用Android安全分析框架Drozer动态分析APK,需要安装安卓模拟器(安装了Genymotion,基于X86 + ARM支持包)

使用adb进行端口转发,转发到Drozer使用的端口31415

adb forward tcp:31415 tcp:31415

在Android设备上开启Drozer Agent

在PC上启动Drozer

drozer console connect 

在Drozer控制台中使用命令,分析目标APK是否存在目录遍历漏洞

run scanner.provider.traversal -a <package-name>

建议:

  • 将不必要导出的Content Provider设置为不导出
  • 由于Android组件Content Provider无法在Android 2.2(即API Level 8)系统上设为不导出,因此如果应用的Content Provider不必要导出,阿里聚安全建议声明最低SDK版本为8以上版本;由于API level 在17以下的所有应用的android:exported属性默认值都为true,因此如果应用的Content Provider不必要导出,阿里聚安全建议显示设置注册的Content Provider组件的android:exported属性为false;
  • 去除没有必要的openFile()接口
  • 如果应用的Content Provider组件没有必要实现openFile()接口,阿里聚安全建议移除该Content Provider的不必要的openFile()接口
  • 过滤限制跨域访问,对访问的目标文件的路径进行有效判断
  • 使用Uri.decode()先对Content Query Uri进行解码后,再过滤如可通过../实现任意可读文件的访问的Uri字符串
  • 设置权限来进行内部应用通过Content Provider的数据共享
  • 使用签名验证来控制Content Provider共享数据的访问权限,如设置protectionLevel=signature或signatureOrSystem
  • 公开的content provider确保不存储敏感数据
  • 提供asset文件时注意权限保护

5 Provider:grant-uri-permission属性检测 # 05005

grant-uri-permission若设置为true,可被其它程序员通过uri访问到content provider的内容,容易造成信息泄露。默认是false。

风险等级:提醒

问题示例:

1
2
3
4
<provider
android:grantUriPermissions="true">
...
</provider>

建议:

如无需对外提供数据,则将content provider的android:grantUriPermissions设置为false。

1
2
3
4
<provider
android:grantUriPermissions="false">
...
</provider>

查阅更多:

6 Intent-Based攻击检测 # 05006

在AndroidManifest文件中定义了android.intent.category.BROWSABLE属性的组件,可以通过浏览器唤起,这会导致远程命令执行漏洞攻击。

风险等级:低危

问题示例:

Activity只有配置了category filter才有被android.intent.category.BROWSABLE通过这种方式在浏览器中打开

通过扫描Minifest中的所有组件,检测出所有组件有中intent-filter带有<category android:name="android.intent.category.BROWSABLE"/>属性的,将其标注为问题代码代码段,并报出低危风险的提示。

1
2
3
4
5
<activity android:name=".MainActivity">
<intent-filter>
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>

建议:

  • APP中任何接收外部输入数据的地方都是潜在的攻击点,检查并过滤来自网页的参数
  • 不要通过网页传输敏感信息,有的网站为了引导已经登录的用户到APP上使用,会使用脚本动态的生成URL Scheme的参数,其中包括了用户名、密码或者登录态token等敏感信息,让用户打开APP直接就登录了。恶意应用也可以注册相同的URL Sechme来截取这些敏感信息。Android系统会让用户选择使用哪个应用打开链接,但是如果用户不注意,就会使用恶意应用打开,导致敏感信息泄露或者其他风险。

7 Intent Scheme URL漏洞攻击检测 # 05007

Intent Scheme URI是一种特殊的URL格式,用来通过Web页面启动已安装应用的Activity组件,大多数主流浏览器都支持此功能。

Android Browser的攻击手段——Intent Scheme URLs攻击。这种攻击方式利用了浏览器保护措施的不足,通过浏览器作为桥梁间接实现Intend-Based攻击。相比于普通Intend-Based攻击,这种方式极具隐蔽性,

如果在app中,没有检查获取到的load_url的值,攻击者可以构造钓鱼网站,诱导用户点击加载,就可以盗取用户信息。所以,对Intent URI的处理不当时,就会导致基于Intent的攻击。

风险等级:高危

问题示例:

如果浏览器支持Intent Scheme URI语法,一般会分三个步骤进行处理:

  1. 利用Intent.parseUri解析uri,获取原始的intent对象
  2. 对intent对象设置过滤规则
  3. 通过Context.startActivityIfNeeded或者Context.startActivity发送intent;其中步骤2起关键作用,过滤规则缺失或者存在缺陷都会导致Intent Schem URL攻击

关键在于Intent.parseUri函数,比较安全的使用Intent Scheme URI方法是,如果使用了Intent.parseUri函数,获取的intent必须严格过滤,intent至少包含以下三个策略:

  • addCategory(“android.intent.category.BROWSABLE”)
  • setComponent(null)
  • setSelector(null)

通过扫描出所有调用了Intent.parseUri方法的路径,并检测是否使用上述的三个策略。若三者均使用则认为安全,否则需要标注为高危

建议:

Intent.parseUri函数返回的Intent对象需要按照以下方式进行实现,才可以认为是安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 将intent的URI(intent scheme URL)转换为intent对象
Intent intent = Intent.parseUri(uri);

// 禁止在没有设置可浏览的目录(BROWSABLE category)的时候启动活动
intent.addCategory("android.intent.category.BROWSABLE");

// 禁止显式调用(explicit call)
intent.setComponent(null);

// 禁止intent的选择器(selector)
intent.setSelector(null);

// 通过intent启动活动
context.startActivityIfNeeded(intent, -1)

8 应用本地拒绝服务漏洞检测 # 05008

Android系统提供了Activity、Service和Broadcast Receiver等组件,并提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android系统则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。

Android应用本地拒绝服务漏洞源于程序没有对Intent.GetXXXExtra()获取的异常或者畸形数据处理时没有进行异常捕获,从而导致攻击者可通过向受害者应用发送此类空数据、异常或者畸形数据来达到使该应用Crash的目的,简单的说就是攻击者通过Intent发送空数据、异常或畸形数据给受害者应用,导致其崩溃。

风险等级:低危

问题示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//源于程序没有对getAction()等获取到的数据进行空指针判断,从而导致了空指针异常导致应用崩溃
Intent i = new Intent();
try {
if (i.getAction().equals("TestForNullPointerException")) {

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

}
} catch Exception {
}

Intent abc = new Intent();
Intent kkk = new Intent();
if (abc.getAction().equals("TestForNullPointerException")) {
Log.d("TAG", "Test for Android Refuse Service Bug");
}

//源于程序没有对getSerializableExtra()等获取到的数据进行类型判断而进行强制类型转换,从而导致类型转换异常导致拒绝服务漏洞
Intent a = getIntent();
try {
String test = (String) a.getSerializableExtra("serializable_key");
} catch Exception {
//针对异常进行操作
}

//源于程序没有对getIntegerArrayListExtra()等获取到的数据数组元素大小判断,导致数组访问越界而造成拒绝服务漏洞
Intent intent = getIntent();

ArrayList<Integer> intArray = intent.getIntegerArrayListExtra("user_id");
if (intArray != null) {
for (int i = 0; i < 10; i++) {
intArray.get(i);
}
}


//源于程序没有找到从getSerializableExtra()获取到的序列化对象的类定义,因此导致发生类未定义的异常导致拒绝服务漏洞
a.getSerializableExtra("key");

9 manifest中定义组件未实现检测 # 05009

在manifest文件中定义的组件导出,且没有代码实现,则攻击者可以通过ddos攻击导致app奔溃。

风险等级:中危

问题示例:

首先获取app源码中所有的类路径(包名+类名),然后检测manifest中声明的所有组件是否存在于类路径中即可。

建议:

删除manifest文件中无效的导出组件

10 Debug或Test敏感测试组件泄露检测 # 05010

一些app在正式发布前,为了方便调试app,都会在app里集成一些调试或测试界面。这些测试界面可能包含敏感的信息。

风险等级:低危中危

问题示例:

遍历manifest文件中的所有组件名称,找出所有带有debug或test等测试字样的关键字组件,并根据组件的intent-filter属性构造intent发送让组件弹出进行检测

建议:

在正式发布前移除所有的测试组件

11 Intent不安全反射风险检测 # 05011

通过Intent接收的Extra参数来构造反射对象会导致从不受信任的源加载类。攻击者可以通过巧妙地构造达到加载其它类的目的。

风险等级:低危

问题示例:
Step1:检测出导出的组件

Step2:在导出的组件下,检测两个关键函数,分别是:getIntent()和Class.forName(“….”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SecondActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
//这里的intent使用了getStringExtra方法加载了一个名称为className的类
Intent intent = getIntent();
String className = intent.getStringExtra("className");
String methodName = intent.getStringExtra("methodName");

try {
Class<?> clz = null;
//尝试以反射的方式构造className的实例
clz = Class.forName(className);
Date object = (Date) clz.newInstance();
Method method = clz.getMethod(methodName);
Toast.makeText(getApplicationContext(), method.invoke(object, null) + "======", Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}

}
}

逆向后对应的smali代码如下:

1
2
3
4
5
...
invoke-virtual {p0}, Lcom/bug/intent/reflection/SecondActivity;->getIntent()Landroid/content/Intent;
...
invoke-static {v0}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;
...

建议:

  • 不要通过Intent接收的Extra传播的反射函数
  • 将接受反射的组件设置为非导出组件