以OWASP移动应用安全认证标准和移动安全测试指导为基础,这个检测列表是为了设计、测试和发行安全的Android应用的安全考虑。
翻译自:https://github.com/JinxKing/android_app_security_checklist
- 数据存储
数据存储
保护认证凭据、私有信息和其他敏感信息是移动安全的关键。在这一章中,将学习Android为本地数据存储提供的API,并进行练习使用。
数据存储总结起来比较简单:公共数据应该是对所有人可用,敏感数据和私有数据必须被保护,或者设备存储之外。
注意敏感数据的含义取决于app的处理。数据分类会在“移动App安全测试”一章中的“敏感数据认证”具体描述。
测试敏感数据的本地存储
传统的建议是尽量少的敏感数据永久存储本地。在大部分实际场景中,然而,一些数据类型必须被存储。比如,当每次app启动时需要用户输入一个非常复杂的密码,在使用上这不是一个好的做法。大部分app必须本地缓存一些认证信息来避免这种情况。如果一个特定场景需要,私人认证信息和其他类型的敏感数据应该被存储下来。
当必须永久存储但不被app合适的保护,敏感数据是危险的。app可能把数据存储在好几个地方,比如设备中或者外部SD卡。当你尝试探索这些问题,考虑这些信息可能被处理并存储在不同的地方。认证信息比较有价值,可能会被攻击。
公开敏感信息有一系列影响。一般来说,一个攻击者认证信息并且用于其他的攻击,例如社会工程、账户劫持、从有支付选项的app中聚集信息。
存储数据对于很多移动app来说是基本。数据可以用很多方法永久存储。下面这些方法被广泛使用:
- Shared Preference
- SQLite Databases
- Realm Databases
- Internet Storage
- External Storage
Shared Preference
Shared Preference API经常用来永久存储键值对。数据一般被写在一个解释文件.xml中。Shared Preference对象可以声明对所有app公开,也可以声明私有。误用经常导致敏感信息的暴露。例子:
SharedPreferences sharedPref = getSharedPreferences("key", MODE_WORLD_READABLE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString("username", "administrator");
editor.putString("password", "supersecret");
editor.commit();
一旦activity被调用,文件key.xml会被创建。
- 用户名和密码会被存储在
/data/data/<package-name>/shared_prefs/key.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="username">administrator</string>
<string name="password">supersecret</string>
</map>
- MODE_WORLD_READABLE允许所有应用访问并获取
key.xml
的内容
root@hermes:/data/data/sg.vp.owasp_mobile.myfirstapp/shared_prefs # ls -la
-rw-rw-r-- u0_a118 170 2016-04-23 16:51 key.xml
注意:
MODE_WORLD_READABLE
和MODE_WORLD_WRITEABLE
被API 17(Android 4.2)的应用弃用。API 17以下的应用会被影响。
SQLite DataBase(Unencrypted)
用库android.database.sqlite
,存储在SQLite,代码如下:
SQLiteDatabase notSoSecure = openOrCreateDatabase("privateNotSoSecure",MODE_PRIVATE,null);
notSoSecure.execSQL("CREATE TABLE IF NOT EXISTS Accounts(Username VARCHAR, Password VARCHAR);");
notSoSecure.execSQL("INSERT INTO Accounts VALUES('admin','AdminPass');");
notSoSecure.close();
调用后,privateNotSoSecure
文件会被创建,路径如下:/data/data/<package-name>/databases/privateNotSoSecure
数据库目录下除了数据库外还包含文件如下:
- Journal files.用于提交和回滚的临时文件
- Lock file.锁定和日志功能的一部分,设计用于提高数据库的并发和减少写入饥饿问题。
SQLite DataBase(Encrypted)
用库SQLCipher
,SQLite会被加密
如果适用加密数据库,确定密码是否硬编码在资源中(SharePreference),或者隐藏在代码或者文件系统等地方。取得密钥的安全方法包括:
- 当app打开时,询问用户加密数据库适用PIN或者密码
- 存储密钥在服务器中并且只允许从web服务中访问
Realm Dtabases
Realm DataBase for Java在开发者中非常受欢迎,数据库和内容用存储在配置文件中的密钥加密。
//the getKey() method either gets the key from the server or from a Keystore, or is deferred from a password.
RealmConfiguration config = new RealmConfiguration.Builder()
.encryptionKey(getKey())
.build();
Realm realm = Realm.getInstance(config);
如果数据库没有被加密,可以获得数据。如果被加密,确定密钥是否被硬编码在source或者resources中,是否存储在SharePreference或者其他位置。
Internal Storage
如果存储在设备的内部存储中,不能被设备中的其他app访问。当用户卸载app,这些文件会被移除。下面的代码会永久的把数据保存在内部存储中。
FileOutputStream fos = null;
try {
fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(test.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
检查文件模式确认只有app能访问文件,设置为MODE_PRIVATE
,MODE_WORLD_READABLE
和MODE_WORLD_WRITEABLE
(都被弃用)会有安全风险。
搜索类FileInputStream
,查找出app内那些文件被读取。
外部存储
一些Android设备支持外部存储,可以使用下列代码在外部存储中存储敏感信息。
File file = new File (Environment.getExternalFilesDir(), "password.txt");
String password = "SecretPassword";
FileOutputStream fos;
fos = new FileOutputStream(file);
fos.write(password.getBytes());
fos.close();
代码调用时,会创建文件password.txt
,而且卸载时不会删除。
静态分析
本地存储
检查资源:
- 检查
AndroidManifest.xml
读取外部存储权限,比如,uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
- 检查源代码中的关键字和API调用,是否被用来存储数据
- 文件权限,比如:
-
MODE_WORLD_READABLE
和MODE_WORLD_WRITABLE
:应该避免使用MODE_WORLD_READABLE
和MODE_WORLD_WRITABLE
,因为任何app都能从读取文件。计时存储在私有数据目录下,如果数据必须与其他应用分享,比如content provider。content provider为其他应用提供了读和写权限,能动态授权
-
- 类和函数,比如:
-
SharedPreference
类(存储键值对) -
FileOutputStream
类(内部存储和外部存储) -
getExternal*
函数(使用外部存储) -
getWritableDatabase
函数(写数据库) -
getReadableDatabase
函数(读数据库) -
getCacheDir
和getExternalCacheDirs
函数(使用cache文件) 加密应该使用经过验证的SDK函数实现。下面描述了源代码中不好的实践。
-
- 文件权限,比如:
- 存储通过简单变换的的“加密”敏感信息。
- 在没有Android系统的特性的情况下使用或创建的密钥,比如Android密钥存储库
- 密钥被硬编码暴露
典型的误用:硬编码的加密密钥 对称密钥存储在设备上,还原数据只是时间问题了。考虑下面的代码:
this.db = localUserSecretStore.getWritableDatabase("SuperPassword123");
获取密钥是微不足道的,因为它包含在源代码中,并且对于应用程序的所有安装都是相同的。以这种方式加密数据是没有好处的。寻找硬编码的API密钥/私钥和其他有价值的数据;他们也有类似的风险。解密/加密密钥代表了另一种试图让它变得更困难但并非不可能获得的尝试。
考虑下面的代码:
//A more complicated effort to store the XOR'ed halves of a key (instead of the key itself)
private static final String[] myCompositeKey = new String[]{
"oNQavjbaNNSgEqoCkT9Em4imeQQ=","3o8eFOX4ri/F8fgHgiy/BS47"
};
解密的算法可能如下:
public void useXorStringHiding(String myHiddenMessage) {
byte[] xorParts0 = Base64.decode(myCompositeKey[0],0);
byte[] xorParts1 = Base64.decode(myCompositeKey[1],0);
byte[] xorKey = new byte[xorParts0.length];
for(int i = 0; i < xorParts1.length; i++){
xorKey[i] = (byte) (xorParts0[i] ^ xorParts1[i]);
}
HidingUtil.doHiding(myHiddenMessage.getBytes(), xorKey, false);
}
验证密钥的公共地址:
- resources(特别是在 res/values/strings.xml)
Example:
<resources>
<string name="app_name">SuperApp</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="secret_key">My_Secret_Key</string>
</resources>
- 配置信息,比如:local.properties 或者 gradle.properties Example:
buildTypes {
debug {
minifyEnabled true
buildConfigField "String", "hiddenPassword", "\"${hiddenPassword}\""
}
}
KeyStore
Android KeyStore支持相关安全信任存储。从Android 4.3,为存储和使用app私有密钥提供公共AP。app使用一个公钥创建一个新的私钥/公钥对来加密数据,用私钥解密。
使用用户认证能保护密钥存储在Android KeyStore,用户的锁屏凭据用来认证。
你能在两种模式中使用密钥:
- 在授权后的一段时间内,用户才能使用密钥。在这种模式中,一旦用户解锁设备所有密钥能被使用。可以为每个密钥制定认证时间。只有当用户启用了安全锁屏,可以使用这个选项。如果用户没有启用安全锁屏,所有密钥会永久失效
- 用户被授权使用与一个密钥相关联的特定加密操作。在这种模式下,用户必须为涉及到密钥的每个操作请求一个单独的授权。目前,指纹认证是请求这种授权的唯一方法。
由Android KeyStore承担的安全等级取决于设备的执行。大部分的设备提供硬件备份KeyStore:密钥在一个可信的执行环境或者安全的环境中生成和使用,操作系统不能直接访问他们。意味着加密密钥不能被简单地得到,甚至是root设备。
KeyChain KeyChain类用于存储和恢复系统范围的私钥和相关证书。如果某些重要的东西第一次存储, 用户需要配置锁屏pin或者密码保护存储。KeyChain是系统范围的,所有app能访问存储在keystore中的内容
检查源代码确定原生Android机制是否确认敏感信息。敏感信息应该加密,不能明文存储。如果敏感信息必须被存储在设备上,一些KeyStore的API调用可以用来保护数据。完整步骤:
- 确定app使用KeyStore和Cipher机制在设备上安全存储加密信息。查询模型
import java.security.KeyStore
,import javax.crypto.Cipher, import
java.security.SecureRandom
,佳偶皮相关使用。 - 调用函数
store(OutputStream stream, char[] password)
并使用密码存储KeyStore在磁盘上。确认密码由用户提供,不是硬编码。
动态分析
安装和使用app,至少执行过所有功能一次。当用户输入后数据会生成,端点输出,或者app运送,完整如下:
- 认证开发文件,备份文件和不应该包含在发行中的老文件
- 确认SQLite 数据库可用,是否包含敏感信息。SQLite存储在
/data/data/<package-name>/databases
- 检查SharedPreference存储的xml文件(
/data/data/<package-name>/shared_prefs
)是否由敏感信息。 - 检查文件的权限(rwx)
- Realm数据库是否可用
/data/data/<package-name>/files/
,是否加密,是否包含敏感信息。默认文件扩展名是realm
文件名是default
- 检查外部存储中是否由敏感信息
敏感数据的测试日志
日志中的敏感信息可能会暴露给攻击者或者恶意应用。使用日志的两个类:
- Log Class
- Logger Class
使用日志机制,需要从生产版本中删除日志记录。
静态分析
检查app的源代码的日志,搜查关键字:
- 函数和类,比如:
android.util.Log
-
Log.d
|Log.e
|Log.i
|Log.v
|Log.wtf
Logger
- 关键字和系统输出:
-
System.out.print
|System.out.print
- logfile
- logs
-
- 当准备发行版本时,可以使用ProGuard(包含在Android Studio中)删除相关代码。检查是否所有的
andriod.util.Log
类和函数都被移除了,检查ProGuard的配置文件(proguard-project.txt)选项:
-assumenosideeffects class android.util.Log
{
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
public static int wtf(...);
}
Log.v("Private key [byte format]: " + key);
Log.v(new StringBuilder("Private key [byte format]: ").append(key.toString()).toString());
ProGuard能消除Log.v
方法调用,new StringBuilder
会不会消除取决于ProGuard的版本。
这是一种安全风险,因为(未使用的)字符串将纯文本数据泄漏到内存中,这可以通过调试器或内存转储来访问。
这个问题上的一些选择:
- 执行一个定制的日志工具,能接收简单的参数,并在内部构造日志语句
SecureLog.v("Private key [byte format]: ", key);
然后配置ProGuard取消调用
- 在源码中移除日志而不是bytecode中,下面是简单的gradle任务:
afterEvaluate {
project.getTasks().findAll { task -> task.name.contains("compile") && task.name.contains("Release")}.each { task ->
task.dependsOn('removeLogs')
}
task removeLogs() {
doLast {
fileTree(dir: project.file('src')).each { File file ->
def out = file.getText("UTF-8").replaceAll("((android\\.util\\.)*Log\\.([ewidv]|wtf)\\s*\\([\\S\\s]*?\\)\\s*;)", "/*\$1*/")
file.write(out);
}
}
}
}
动态分析
一次使用app所有的功能,然后识别应用数据目录并找到日志文件/data/data/<package-name>
.检查应用日志决定日志数据是否生成,一些移动应用生成和存储到这个目录下。
很多应用开发者仍然使用System.out.println
或者printStackTrace
而不是一个合适的日志类。因此当应用开启、运行和关闭,测试策略必须包括所有输出生成。检查System.out.println
或者printStackTrace
输出了什么内容,可以使用Logcat
。两种方法执行Logcat:
- Logcat是DDMS和Android Studio的一部分。如果app在debug模式中运行,日志输出会显示在Logcat tab.可以在Logcat中通过定义模式过滤app的日志。
- 可以用adb执行并存储日志:
$ adb logcat > logcat.log
检查敏感信息是否发送给第三方
overview
可以在app中嵌入第三方服务,这些服务可以执行追踪服务,监督用户行为,贩卖横幅广告,提高用户体验等。
缺点是缺少可视化:无法知道第三方库指定了什么代码。因此,你应该确定唯一一件事,就是没有敏感信息能被发送给服务。
大部分第三方服务以两种方法执行:
- 标准库,比如一个包含在APK中的Android工程jar
- a full apk
静态分析
通过一个IDE向导程序或者收工添加一个库或者SDK可以自动整合第三方库。这种情况下,检查AndroidManifest.xml
中是否能访问SMS,contacts,location是非常有必要的。
检查源代码中的API调用和第三方库函数或者SDK。为安全最佳实践检查代码更改。
检查加载库确定是否有必要加载、过期或者包含未知的漏洞
所有数据发送给第三方服务应该匿名。被跟踪到用户账户或者会话的数据不发送给第三方。
动态测试
检查所有对嵌入式敏感信息的外部服务请求。 为了拦截客户端和服务器之间的通信,可以通过启动一个中间人(MITM)攻击,使用Burp suite或OWASP ZAP来执行动态分析。 一旦通过拦截代理路由流量,就可以尝试嗅探应用程序和服务器之间的流量。 所有不直接发送到主函数主机的应用程序请求都应该检查敏感信息,比如跟踪器或广告服务中的PII。
检查键盘缓存对字符输入不可用
overview
当用户在输入时,软件自动建议数据。这个特点对于消息应用非常有用。然而,当用户选择一个输入框时,键盘缓存可能暴露敏感信息。
静态分析
在一个activity的布局定义中,可以定义有XML属性的TextViews。如果XML属性android:inputType
给了值textNoSuggestions
,当输入被选定时键盘缓存不显示。用户将必须手工输入所有东西。
<EditText
android:id="@+id/KeyBoardCache"
android:inputType="textNoSuggestions"/>
动态分析
打开app点击输入框。
在剪切板中寻找敏感信息
overview
当用户在输入框输入数据时,他们应该使用剪切板来复制和粘贴数据。设备中的app分享剪切板,所有恶意应用可以访问敏感数据。
静态分析
识别接收敏感信息的输入字段,以及降低剪贴板访问风险的对策。重写输入字段函数是一种通用的最佳实践,它禁用了这些函数的剪贴板。
EditText etxt = (EditText) findViewById(R.id.editText1);
etxt.setCustomSelectionActionModeCallback(new Callback() {
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
public void onDestroyActionMode(ActionMode mode) {
}
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
});
longclickable
在输入域中设置不可用
android:longClickable="false"
动态分析
启动app的,点击接收敏感信息的输入域。如果显示了复制/粘贴选项,说明剪切板功能没有被禁用。
可以使用Drozer模块post.capture.clipboard
从剪切板中提取数据。
dz> run post.capture.clipboard
[*] Clipboard value: ClipData.Item { T:Secretmessage }
检查敏感信息在IPC机制中是否暴露
overview
content provider作为Android IPC机制的一部分,允许app的存储数据被其他应用访问和修改。如果配置不合适,这些机制可能会泄露
笔者注:intent中的敏感信息容易泄露
静态分析
第一个补助是查看AndroidManifest.xml
,检测content provider是否暴露。依据元素<provider>
识别content providers。完整步骤:
- 检查属性
android:exported="true"
- 检测数据是否被权限(
android:permission
)保护,权限限制了暴露程度。 - 决定
android:protectionLevel
属性是否有值signature
。这个设置是指数据只能被相同企业的app访问(被相同的密钥签名)。 - 如果适用了
android:permission
,其他应用必须声明一致<uses-permission>
。可以使用android:grantUriPermissions
属性授权更明确的访问,还可以用使用<grant-uri-permission>
元素限制访问。
检查源代码理解content provider是如何被使用的。搜索关键字:
android.content.ContentProvider
android.database.Cursor
android.database.sqlite
.query
.update
.delete
为了避免SQL注入攻击,使用参数化的查询方法,比如query
, update
,以及 delete
. 确保对所有方法的参数进行适当的处理;比如,如果全部由用户输入组成参数,selection
会引起SQL注入。
检查AndroidManifest
识别所有<provider>
元素:
<provider android:authorities="com.mwr.example.sieve.DBContentProvider" android:exported="true" android:multiprocess="true" android:name=".DBContentProvider">
<path-permission android:path="/Keys" android:readPermission="com.mwr.example.sieve.READ_KEYS" android:writePermission="com.mwr.example.sieve.WRITE_KEYS"/>
</provider>
<provider android:authorities="com.mwr.example.sieve.FileBackupProvider" android:exported="true" android:multiprocess="true" android:name=".FileBackupProvider"/>
以上例子中,有两个暴露的content provider组件。注意一条路径("/key")被读和写权限保护。
检查源代码
检查有没有敏感信息泄露。
public Cursor query(final Uri uri, final String[] array, final String s, final String[] array2, final String s2) {
final int match = this.sUriMatcher.match(uri);
final SQLiteQueryBuilder sqLiteQueryBuilder = new SQLiteQueryBuilder();
if (match >= 100 && match < 200) {
sqLiteQueryBuilder.setTables("Passwords");
}
else if (match >= 200) {
sqLiteQueryBuilder.setTables("Key");
}
return sqLiteQueryBuilder.query(this.pwdb.getReadableDatabase(), array, s, array2, (String)null, (String)null, s2);
}
这里实际上有两条路径,"Keys"和"Passwords",后者没有被保护。
当访问URI时,查询声明返回所有密码和路径Passwords/
。我们将会在动态分析中定位这个问题,显示需要的URI
动态分析
测试内容提供器
动态分析一个应用content provider,先遍历攻击面:将应用包名传递给Drozer模块app.provider.info
dz> run app.provider.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
Authority: com.mwr.example.sieve.DBContentProvider
Read Permission: null
Write Permission: null
Content Provider: com.mwr.example.sieve.DBContentProvider
Multiprocess Allowed: True
Grant Uri Permissions: False
Path Permissions:
Path: /Keys
Type: PATTERN_LITERAL
Read Permission: com.mwr.example.sieve.READ_KEYS
Write Permission: com.mwr.example.sieve.WRITE_KEYS
Authority: com.mwr.example.sieve.FileBackupProvider
Read Permission: null
Write Permission: null
Content Provider: com.mwr.example.sieve.FileBackupProvider
Multiprocess Allowed: True
Grant Uri Permissions: False
在这个例子中两个content provider是暴露的。没有权限就能访问,除了DBContentProvider
中的/Keys
路径。用这些信息,可以重构部分内容URIs访问DBContentProvider
(content://...)
识别应用中content provider URIs ,可以使用Drozer的scanner.provider.finduris
模块。这个模块猜测路径确定访问URIs:
dz> run scanner.provider.finduris -a com.mwr.example.sieve
Scanning com.mwr.example.sieve...
Unable to Query content://com.mwr.example.sieve.DBContentProvider/
...
Unable to Query content://com.mwr.example.sieve.DBContentProvider/Keys
Accessible content URIs:
content://com.mwr.example.sieve.DBContentProvider/Keys/
content://com.mwr.example.sieve.DBContentProvider/Passwords
content://com.mwr.example.sieve.DBContentProvider/Passwords/
一旦你有可以访问的 content providers表,就可以通过app.provider.query
从每个provider中提取数据:
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --vertical
_id: 1
service: Email
username: incognitoguy50
password: PSFjqXIMVa5NJFudgDuuLVgJYFD+8w== (Base64 - encoded)
email: incognitoguy50@gmail.com
k可以使用Drozer插入,更新,删除记录:
- 插入
dz> run app.provider.insert content://com.vulnerable.im/messages
--string date 1331763850325
--string type 0
--integer _id 7
- 更新
dz> run app.provider.update content://settings/secure
--selection "name=?"
--selection-args assisted_gps_enabled
--integer value 0
- 删除
dz> run app.provider.delete content://settings/secure
--selection "name=?"
--selection-args my_setting
SQL注入
Android平台推荐SQLite数据库存储用户数据。因为数据库基于SQL,存在SQL注入漏洞。可以使用Drozer模块app.provier.query
测试SQL注入:
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "'"
unrecognized token: "' FROM Passwords" (code 1): , while compiling: SELECT ' FROM Passwords
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --selection "'"
unrecognized token: "')" (code 1): , while compiling: SELECT * FROM Passwords WHERE (')
如果一个应用存在sql注入,它会返回一个冗长的错误信息。Android中的SQL注入可能会修改或者查询有漏洞的content provider中的数据。下列例子中,Drozer模块app.provider.query
用来列出所有的数据库
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "*
FROM SQLITE_MASTER WHERE type='table';--"
| type | name | tbl_name | rootpage | sql |
| table | android_metadata | android_metadata | 3 | CREATE TABLE ... |
| table | Passwords | Passwords | 4 | CREATE TABLE ... |
| table | Key | Key | 5 | CREATE TABLE ... |
SQL注入也可以用于从其他受保护的表中检索数据:
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM Key;--"
| Password | pin |
| thisismypassword | 9876 |
还可以使用scanner.provider.injection
模块自动进行寻找content provider内容:
dz> run scanner.provider.injection -a com.mwr.example.sieve
Scanning com.mwr.example.sieve...
Injection in Projection:
content://com.mwr.example.sieve.DBContentProvider/Keys/
content://com.mwr.example.sieve.DBContentProvider/Passwords
content://com.mwr.example.sieve.DBContentProvider/Passwords/
Injection in Selection:
content://com.mwr.example.sieve.DBContentProvider/Keys/
content://com.mwr.example.sieve.DBContentProvider/Passwords
content://com.mwr.example.sieve.DBContentProvider/Passwords/
基于Content Provider的文件系统
content provider能提供文件系统的入口。这允许app来分享文件(Android沙箱一般会限制)。可以使用Drozer模块app.provider.read
和 app.provider.download
从基于文件的content provider分别读取和下载文件。这些content providers容易受到目录遍历的影响,它允许在目标应用程序的沙箱中读取其他受保护的文件。
dz> run app.provider.download content://com.vulnerable.app.FileProvider/../../../../../../../../data/data/com.vulnerable.app/database.db /home/user/database.db
Written 24488 bytes
使用scanner.provider.traversal
模块自动寻找容易受目录遍历影响的content provider
dz> run scanner.provider.traversal -a com.mwr.example.sieve
Scanning com.mwr.example.sieve...
Vulnerable Providers:
content://com.mwr.example.sieve.FileBackupProvider/
content://com.mwr.example.sieve.FileBackupProvider
adb
查询content provider
$ adb shell content query --uri content://com.owaspomtg.vulnapp.provider.CredentialProvider/credentials
Row: 0 id=1, username=admin, password=StrongPwd
Row: 1 id=2, username=test, password=test
...
通过用户接口检查敏感数据暴露
overview
许多应用程序要求用户输入几种类型的数据,例如,注册一个账户或支付款项。 如果应用程序不能正确地屏蔽它,当在明文显示数据时,敏感数据可能会被暴露出来。
对敏感数据的屏蔽,通过显示星号或圆点而不是清晰的文本,应该在应用程序的活动中执行,以防止信息披露和减少诸如肩冲浪等风险。
静态分析
为了确保应用程序屏蔽敏感的用户输入,请在EditText的定义中检查以下属性:android:inputType="textPassword"
有了这个设置,dots(而不是输入字符)将显示在文本字段中,防止应用程序将密码或pin泄漏到用户界面。
动态分析
...
测试敏感数据的备份
Android提供自动备份,复制数据和所有app的已安装的设置信息。存储敏感信息的app可能会通过数据备份泄露信息。
Android备份选项:
- Android有内置的USB备份设备。进入调试模式,使用adb
adb backup
命令穿件完整数据备份。 - Google提供了备份到google服务器上
- 两个备份api:
- 键值对备份,上传Android备份服务云。
- 应用程序自动备份。(Android 6.0)
- OEM提供更多选择
静态分析
本地
AndroidManifest.xml
文件中,提供属性allowBackup
。如果设置为true,就可以被adb备份。
不可备份,则设置为false。默认true。
检查AndroidManifest.xml
中的代码:
android:allowBackup="true"
云
无论使用键值对备份还是自动备份,必须注意以下事项:
-
哪个文件被发送到云上
-
哪个文件包含敏感信息
-
发送到云上的敏感信息是否被加密
-
自动备份:通过配置allowBackup属性自动备份。执行备份代理时,通过
android:fullBackupOnly
激活自动备份。 -
键值对备份:为了确保键值对备份,必须在manifest文件中定义备份代理:
android:backupAgent
动态分析
adb拉取备份
$ adb backup -apk -nosystem <package-name>
获得备份文件转格式
$ dd if=mybackup.ab bs=24 skip=1|openssl zlib -d > mybackup.tar
如果报错openssl:Error: 'zlib' is an invalid command
,用python代替:
dd if=backup.ab bs=1 skip=24 | python -c "import zlib,sys;sys.stdout.write(zlib.decompress(sys.stdin.read()))" > backup.tar
java -jar android-backup-extractor-20160710-bin/abe.jar unpack backup.ab
$ tar xvf mybackup.tar
自动截屏泄露信息
应用切换,系统会保存应用屏幕截图作为背景,这会导致安全问题
静态分析
如下设置会使屏幕内容为空白
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE);
setContentView(R.layout.activity_main);
动态分析
点开看一下....
检查内存中的敏感数据
这一届描述怎样通过进程存内存中大数据暴露
识别存储在内存中的敏感信息,
网友评论