Lab 01 — Android Manifest Exposure Analyzer
Difficulty: 3/5 | Runs locally: yes
Pairs with the Phase 04 WARMUP Chapter 6 (the four components and exported surfaces) and the HITCHHIKERS-GUIDE ("Build the Two-Application Range").
Why This Lab Exists (Purpose & Goal)
The AndroidManifest.xml is the app's declaration of its attack surface: every activity, service,
receiver, content provider, permission, and intent-filter that other apps on the device can reach.
The first thing any Android security reviewer does is read the manifest and ask, per component, "who
can invoke this, and does it validate its caller?" The goal of this lab is to build the analyzer that
performs that enumeration systematically and flags the components that are exposed without a guard.
The Concept, In the Weeds
Android's security model assumes apps do not trust each other (Phase 04 WARMUP, Chapter 1). A
component is exported — reachable by other apps — either explicitly (android:exported="true") or,
historically, implicitly by declaring an intent-filter. Every exported component is an entry point
into your app from untrusted callers, so each one needs a caller-validation decision. The analyzer
flags:
- exported components lacking a signature permission (any app can invoke them);
- browsable deep links without host/scheme constraints (claimable / abusable — Lab 02);
- debuggable builds and cleartext-traffic allowances;
- unsafe backup (
allowBackupcopying sensitive data off device); - content providers granting URI permissions too broadly (data leakage, SQL injection, path
traversal via
openFile).
The essential nuance — and the most common reviewer mistake — is that "exported" is a surface, not automatically a vulnerability. Reporting every exported component as a bug destroys your credibility. A finding needs reachable impact: exported + does-something-sensitive + no-validation. That is why this lab is triage, not proof — every finding carries evidence and remediation and must be confirmed against the component's code and the target Android version's behavior.
Why This Matters for Protecting the Company
A mobile app is a piece of the company's attack surface that runs on a device the attacker physically controls, alongside potentially malicious apps. An exported component with no caller validation is how a malicious app on the same device escalates into your app's data and privileges — bypassing the backend entirely. When you can systematically map a mobile app's exported surface and separate true findings (reachable impact) from noise, you protect the mobile clients of the company's services and produce reports that engineers act on rather than dismiss.
Build It
Implement the static analyzer over purpose-built manifests: flag exported components without signature permissions, browsable deep links without host/scheme constraints, debuggable builds, cleartext traffic, unsafe backup, and over-broad provider URI grants. Each finding includes evidence and remediation.
LAB_MODULE=solution pytest -q
Secure Implementation Patterns
The exposed manifest (what the analyzer flags). Implicit export via an intent-filter, no
permission guard, cleartext, broad backup:
<application android:debuggable="true" android:usesCleartextTraffic="true">
<activity android:name=".PaymentActivity"> <!-- exported=null + filter => EXPORTED -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="myapp"/> <!-- no host: any app can claim it -->
</intent-filter>
</activity>
<provider android:name=".DocsProvider" android:grantUriPermissions="true"/> <!-- no permission -->
</application>
The analysis logic — implicit-export detection is the crux (mirrors solution.py):
exported_attr = item.get(ANDROID + "exported")
has_filter = item.find("intent-filter") is not None
# the trap: a component with an intent-filter and NO explicit exported= is exported on old targets
exported = exported_attr == "true" or (exported_attr is None and has_filter)
permission = item.get(ANDROID + "permission")
if exported and not permission:
emit("EXPORTED_WITHOUT_PERMISSION", name, "protect or unexport this component")
# a BROWSABLE deep link with no scheme+host is claimable/abusable:
if browsable and (data is None or not data.get(scheme) or not data.get(host)):
emit("UNCONSTRAINED_DEEP_LINK", name, "constrain scheme+host and validate every parameter")
The hardened manifest:
<application android:debuggable="false" android:allowBackup="false">
<activity android:name=".PaymentActivity" android:exported="true"
android:permission="com.acme.permission.PAY"> <!-- guarded -->
<intent-filter android:autoVerify="true"> <!-- verified App Link -->
<data android:scheme="https" android:host="field.example"/>
</intent-filter>
</activity>
<provider android:name=".DocsProvider" android:exported="false"/> <!-- default-deny -->
</application>
Production practices to carry forward:
- Default
exported="false"; export only with a signature permission for trusted peers plus runtime caller validation (Binder.getCallingUid()). - "Exported" is a surface, not a finding — confirm reachable impact against component code and the target Android version before reporting (calibration).
- Verify from a real second app signed with a different key, not from
adb shell(special UID). - Pair manifest analysis with content-provider review: parameterize SQL, canonicalize
openFilepaths, scope returned rows.
Validation — What You Should Be Able to Do Now
- Enumerate an app's exported components and produce a component access matrix with a caller-validation decision per component.
- Distinguish "exported" (a surface) from "vulnerable" (reachable impact) — and avoid the credibility- destroying habit of flagging every export.
- Explain the danger of an over-broad content provider (data leak, SQLi, traversal) and its fix.
- Treat decompiled/manifest output as triage, confirmed against code and Android-version behavior.
The Broader Perspective
This lab teaches you to read a system's declared attack surface and reason about each entry point's trust — a skill that transfers directly to reviewing API route tables (Phase 02), cloud resource policies (Phase 07), and AI-agent tool catalogs (Phase 11). The deeper discipline is calibration: a surface is not a finding until you can demonstrate reachable impact, and a reviewer who cries wolf on every exported component is ignored exactly when a real one appears. Precision and honest severity are what make your findings move engineering.
Interview Angle
- "How do you secure a content provider?" — Default
exported=false; if shared, require a permission and per-URI grants; parameterize SQL; validate/canonicalize paths inopenFile; scope rows to the caller's authZ; negative-test from another UID. - "Is an exported activity a vulnerability?" — Only with reachable impact; it's a surface to assess (who can invoke it, what does it do, does it validate input/caller), not an automatic finding.
Extension (Stretch)
Build the two-application range (a caller app signed with a different key) and confirm each finding by invoking the component from an ordinary app UID — proving the surface is reachable.
References
- Phase 04 WARMUP Chapter 6; Android Developers — App security best practices.
- OWASP MASVS/MASTG (platform interaction); AOSP application-sandbox documentation.