The trick
Android ContentProvider.query looks like an API. Under it is SQLite. If the implementation concatenates selection or sortOrder into the statement instead of using selectionArgs, every caller who can reach that URI is speaking SQL with the provider's UID.
Lab package lab.sample.app. Authority lab.sample.notes. Exported. No read permission. Contacts-style stores on the platform fail the same way when a helper concatenates instead of binding.
B APP ATTACK SURFACE 13 Content Provider Probe ★ session — lab.sample.app [prov] authority lab.sample.notes · exported [prov] query() selection concatenated [prov] selectionArgs unused on that path [prov] no rows printed on this page mitsec@lab.sample.app:
What AndroScope is allowed to say
Module 13 on an authorized lab build: authority, exported flag, whether selection is forwarded raw. It does not print table dumps. Empty cursor means you did not hit that URI, not that the glue is safe.
Fix
- Every dynamic clause is a
?plusselectionArgs. - Allowlist projection columns. Ignore caller
sortOrderunless you parsed it. - Un-export the provider, or put a signature / dangerous permission on it.
- Do not expose a notes / contacts / file-index URI to every UID on the device.