A 37-lab, fully hands-on curriculum covering reconnaissance, the OWASP Top 10, advanced vulnerability classes, mobile and source-assisted hunting, and the career path from first report to working hunter — practiced entirely on legal, intentionally vulnerable targets before you ever touch a real program.
Bug bounty hunting is testing software with permission. The exact same actions, done without permission, are computer crime in most countries. Every lab in this workbook is built around that line. Internalize these five rules before you open a single tool.
This is general training guidance, not legal advice. Laws vary by country (e.g. the U.S. Computer Fraud and Abuse Act, UK Computer Misuse Act) — know the rules where you and your target are located.
Every command in this workbook assumes Kali. If you're on Kali in a VMware VM, a few one-time settings save real friction later.
| Setting | Recommendation |
|---|---|
| Network Adapter | Bridged, not NAT — gives the VM a real IP on your network, avoids double-NAT headaches with Docker port mapping, and is needed for some Module 1 recon practice |
| RAM | 8 GB minimum. Bump to 12 GB+ once Docker (Juice Shop/MobSF), Burp, and any decompiling are running at the same time |
| CPU Cores | 4 if your host can spare them — Docker, Burp, and jadx/apktool all benefit |
| VMware Tools | Install open-vm-tools (usually preinstalled on Kali's official VMware image) for clipboard sharing and proper display scaling |
| Snapshots | Take one right after setup, and again before Module 2 — labs that change app state are trivial to revert this way |
Burp Suite Community is already on Kali by default (just run burpsuite) — skip Lab 0.3's manual download. Everything else this workbook uses across all 9 modules, in one idempotent block:
# safe to run even if some of these are already installed sudo apt update && sudo apt full-upgrade -y sudo apt install -y jadx apktool gitleaks ffuf gobuster amass \ docker.io android-tools-adb nodejs npm python3-pip # Python-based tools not in apt pip install --user frida-tools objection semgrep pip-audit
Only needed if you don't have a spare Android device to pass through to the VM. The idea: run the emulator on the Windows host, where it has real hardware acceleration, and point its traffic at Burp running inside Kali.
Not inside the VM — directly on the host, so the emulator gets real hardware acceleration.
Run ip a inside Kali and note the address on the bridged interface.
In Burp's Proxy → Options, change the listener from 127.0.0.1 to all interfaces so it accepts connections from outside the VM.
Set its proxy to <kali-vm-ip>:8080 — same as configuring any other device against Burp.
Same process as Lab 0.3, just reachable now since the listener is open to the network.
Just physically split: the target app runs on Windows, your tools run in Kali. For Frida/Objection specifically, install them on the Windows host too (pip install frida-tools objection — no WSL required) so you can attach to the Windows-side emulator directly.
A bug bounty program is a standing invitation from an organization: "test our systems, within these rules, and we'll pay you for valid vulnerability reports." It flips the economics of security — instead of paying a fixed fee for a one-time pentest, a company pays per confirmed finding, drawing on thousands of researchers with different specialties instead of one consulting team. A Vulnerability Disclosure Program (VDP) is the unpaid cousin of this: "you can test us and report issues, we just won't pay a bounty" — these are where almost every hunter builds their first reputation.
| Platform | Known For | Good First Stop? |
|---|---|---|
| HackerOne | Largest researcher base; huge public Hacktivity feed of disclosed reports | Yes — read before you hunt |
| Bugcrowd | Strong VDP focus; free "Bugcrowd University" training | Yes — free structured lessons |
| Intigriti | EU-heavy program list | Yes, for EU-based hunters |
| YesWeHack | EU/Asia-Pacific programs | Yes |
| Synack | Invite-only, vetted researcher pool | No — apply once you have a track record |
Sign up for free researcher accounts on HackerOne and Bugcrowd.
Open HackerOne's public Hacktivity feed and read five disclosed web-app reports. Note the structure: title, summary, steps to reproduce, impact.
Find one Vulnerability Disclosure Program you could realistically test once you finish this workbook. Bookmark its policy page. Do not test it yet — that comes after Module 4.
| Element | What To Check |
|---|---|
| In-scope assets | The exact domains, subdomains, IP ranges, or apps listed — nothing else |
| Out-of-scope | Anything explicitly excluded, even if it looks related to the company |
| Eligible vulnerability classes | The bug types the program will actually reward or accept |
| Disqualified / "won't fix" | Commonly excluded: missing security headers alone, self-XSS, clickjacking on non-sensitive pages, logout CSRF, software version disclosure |
| Safe harbor clause | Language protecting you from legal action — but only for in-scope, policy-compliant testing |
| Disclosure timeline | How long you must wait before any public write-up, and whether public disclosure is allowed at all |
Confirm the issue is real and reproducible before reporting — don't report a hunch.
Use the program's designated submission form or security@ address. Never DM employees or post on social media.
Don't re-test aggressively or escalate publicly while the team investigates. One clean follow-up question is fine; daily pings are not.
Provide extra reproduction detail or a quick re-test if asked — this is what separates a one-off reporter from a trusted hunter.
And only after the agreed embargo period. Many programs never permit public write-ups — check before you assume.
Download it from PortSwigger's site and install it for your OS. The free Community Edition covers everything in this workbook. On Kali, it's already there — just run burpsuite.
Install the FoxyProxy extension and add a profile pointing to 127.0.0.1:8080 (Burp's default proxy listener). Switch it on only while testing.
With the proxy on, visit http://burp in your browser, download cacert.der, and import it as a trusted root certificate so HTTPS traffic decrypts cleanly instead of throwing warnings.
Install Docker Desktop if you don't already have it — this is how you'll run your practice target locally, on your own machine, fully under your control.
Run the command below, then open http://localhost:3000. Juice Shop is an official OWASP project built specifically to be hacked — there is no target you could pick that is more legal to attack.
Browse Juice Shop with the proxy switched on and watch requests appear live in Burp's HTTP history tab.
# pull & run OWASP Juice Shop locally — your practice target for Modules 2 & 3
docker run --rm -p 3000:3000 bkimminich/juice-shop
| Check | Expected Result |
|---|---|
| localhost:3000 | Juice Shop storefront homepage loads |
| Burp HTTP history | Requests to localhost:3000 appear as you browse |
| HTTPS sites, proxy on | Load with no certificate warning (CA installed correctly) |
example.com, which IANA permanently reserves for exactly this kind of documentation use.Install subfinder or amass — both aggregate subdomains from dozens of public sources without sending traffic to the target itself.
Visit crt.sh and search a domain you own. Every TLS certificate ever issued for it is public record — and often reveals subdomains that were never meant to be found.
Aggregate findings from dozens of sources in one pass.
Search site:yourdomain.com -www to surface indexed subdomains and pages search engines have crawled.
Target, subdomain list, dates, notes. Build this habit now — every future engagement starts the same way.
# passive subdomain enumeration subfinder -d yourdomain.com -silent # certificate-transparency lookup via crt.sh curl -s "https://crt.sh/?q=%25.yourdomain.com&output=json" | jq -r '.[].name_value' | sort -u
| Technique | What It Reveals |
|---|---|
| crt.sh | Subdomains that ever had a TLS certificate issued |
| subfinder / amass | Subdomains aggregated from dozens of public sources |
| site: dork | Pages and parameters search engines have indexed |
| web.archive.org | Old endpoints/parameters no longer linked anywhere live |
Use Burp's HTTP history (or curl -I) to inspect Server and X-Powered-By headers on your local Juice Shop instance.
Browse Juice Shop with the extension active and note the detected stack — frontend framework, backend hints, analytics, everything.
Request a malformed or nonexistent route and see whether the response leaks a stack trace, file path, or framework version.
On a domain you own, search the Wayback Machine for old JavaScript bundle names or API paths that might still be live but no longer linked anywhere.
# quick header fingerprint
curl -I http://localhost:3000
| Signal | What It Tells You |
|---|---|
| Server / X-Powered-By | Web server, and sometimes the backend framework |
| Cookie names | Backend framework hints (e.g. Express session naming) |
| JS bundle file names | Frontend framework (Angular/React build artifacts) |
| Verbose error / stack trace | Exact framework + version, sometimes file paths |
Or gobuster — either works for this lab.
SecLists' common.txt is a solid, widely-used starting point.
Fuzz for real paths against your local instance only.
Open each 200/301/403 manually and note what it actually is.
Juice Shop exposes /rest/ and /api/ routes. Browse normally with Burp's proxy on, then review the HTTP history to map real calls the app makes.
# directory & endpoint discovery against your local Juice Shop instance
ffuf -w /path/to/common.txt -u http://localhost:3000/FUZZ -mc 200,301,302,403
| Result Code | What It Usually Means |
|---|---|
| 200 | Page/endpoint exists and returned content — investigate it |
| 301 / 302 | Redirect — follow it, may reveal an auth-gated route |
| 403 | Exists but you're not authorized — worth a closer look |
| 404 | Not found — expected for nearly every guess |
Visit your account, basket, and order history while Burp's proxy captures every request.
In Burp's HTTP history, look for calls like /rest/basket/6 or /rest/user/whoami that reference a numeric or predictable ID.
Send the request to Repeater and change the ID to a neighboring value. Resend it — do you get back someone else's data?
As a normal user, try navigating directly to an admin-only route (e.g. Juice Shop's /#/administration). Does it load without checking your role?
Which ID, which endpoint, what data came back that shouldn't have. This becomes your report in Module 4.
| Test | Vulnerable If… |
|---|---|
| Changed ID in basket/order request | You see another user's real data, not an error |
| Direct access to admin route | The admin panel loads with no role check |
| Direct call to admin REST endpoint | Returns 200 with data instead of 401/403 |
Submit a regular (failing) login attempt and capture the request in Burp.
Replace the email field with the classic bypass payload below.
Did it log you in without valid credentials — often as the very first account in the database?
Check the account email shown post-login to prove whose account you accessed.
-- classic SQLi authentication-bypass payload, entered in the email field ' OR 1=1--
| Payload | Expected If Vulnerable |
|---|---|
| ' OR 1=1-- | Logged in with no valid credentials, often as the first DB row |
| ' UNION SELECT ... | Data from other tables appears in the response |
DROP TABLE, etc.) even on a practice target shared with other students. The goal is proving read/bypass impact, not breaking the environment.Juice Shop's search bar reflects your input straight into the page. Try the payload below in the search field.
Find a field that stores input for other users to view later — a product review or comment. Submit the payload there and reload as a different session.
Start a free "DOM XSS" lab on PortSwigger Web Security Academy — purpose-built for client-side sinks like innerHTML and document.write.
Use alert(document.domain) as your proof. It's the industry-standard, non-destructive way to show you control execution.
// non-destructive proof-of-execution payload — industry standard for reports <script>alert(document.domain)</script>
| XSS Type | Where Tested | What Confirms It |
|---|---|---|
| Reflected | Search bar / URL parameter | Alert fires immediately from a crafted link |
| Stored | Product review / comment field | Alert fires for any user who later views that content |
| DOM-based | PortSwigger DOM XSS labs | Alert fires via a client-side sink, no server round-trip |
alert(document.domain) rather than a "scarier" payload — it proves identical impact without resembling an actual attack on a live user.Open PortSwigger Academy's "CSRF vulnerability with no defenses" lab (free).
An HTML form that fires a state-changing request on page load — see the template below.
Follow the lab's instructions to deliver it to the simulated victim and confirm the account state changes.
Find a feature that fetches a URL on the server's behalf — a "check stock at this URL" type feature is the classic Academy example.
Supply an internal address instead of the expected external one.
Did the app return internal content it shouldn't have access to?
<!-- minimal CSRF PoC: auto-submitting form --> <form action="https://lab-target/email/change" method="POST" id="f"> <input type="hidden" name="email" value="attacker@evil.com"> </form> <script>document.getElementById('f').submit()</script> // SSRF: point a "fetch URL" feature at an internal address http://localhost:8080/admin
| Bug | Proof Of Impact |
|---|---|
| CSRF | Victim's account state changes purely from visiting your hosted page — no token required |
| SSRF | Server returns content from an internal/unexpected address you supplied |
Request /.git/config. A 200 response can mean the entire repo history is downloadable.
Juice Shop deliberately ships some weak accounts — try a few common combinations through the login form, mindful of rate limits.
Send a deliberately malformed request and see whether a stack trace renders instead of a clean error.
Look for missing Content-Security-Policy, X-Frame-Options, and Strict-Transport-Security.
# check for an exposed .git directory curl -I http://localhost:3000/.git/config # check security headers curl -I http://localhost:3000
| Check | Vulnerable If… |
|---|---|
| /.git/config | Returns 200 with real Git configuration content |
| Default credentials | Any weak/test account actually logs in |
| Malformed input | Returns a stack trace exposing paths or versions |
| Security headers | CSP / X-Frame-Options missing on sensitive pages |
Request a reset and inspect the token in Burp — is it predictable, reusable, or missing an expiration?
Use Burp's built-in JWT tab or jwt.io to read the header and payload — what claims does it carry, and how is it signed?
Run a free PortSwigger JWT lab and test whether the server actually verifies the signature, or accepts an alg: none token.
Log out, then replay the old token against a protected endpoint — does the server still accept it?
// decode a JWT's header & payload (no key needed for this part)
echo "<jwt-token>" | cut -d. -f1,2 | base64 -d
| Test | Vulnerable If… |
|---|---|
| Password reset token | Predictable, reusable, or never expires |
JWT with alg: none | Server still accepts it as valid |
| Old token after logout | Still works on protected endpoints |
| Weak JWT signing secret | You can brute-force it and re-sign your own forged token |
Intercept a basket request in Repeater and alter the price or set quantity to a negative number before checkout.
Try reusing a one-time coupon code multiple times, or stacking several at once.
Use Burp's parallel request sending (or Turbo Intruder) to fire the same redeem/discount request several times simultaneously.
Try jumping straight to an "order confirmation" style URL without completing the step that's supposed to come before it.
// example: tampered basket request, quantity changed to negative
{"ProductId": 5, "BasketId": 12, "quantity": -10}
| Test | Vulnerable If… |
|---|---|
| Tampered price/quantity | Server accepts it and the final total reflects the tampered value |
| Reused one-time coupon | The discount applies more than once |
| Parallel duplicate requests | A single-use action is processed more than once |
| Skipped workflow step | Server accepts an action before its prerequisite actually completed |
On Juice Shop's profile picture or complaint upload, try a file with a mismatched extension/content-type — e.g. an SVG containing a script, renamed with an image extension.
Try a filename containing path traversal characters and see how the server handles it.
Replay each API call with no auth token, an expired token, and another user's token — this is BOLA (Broken Object Level Authorization), the API version of IDOR.
Send the same request rapidly and check whether you ever get a 429 throttling response.
// path traversal attempt in an upload filename
../../../etc/passwd.jpg
| Test | Vulnerable If… |
|---|---|
| Mismatched file type (e.g. SVG with script) | Stored/served without sanitization, executable in a browser context |
| Path traversal in filename | Server writes outside the intended upload directory |
| API call with another user's token | Returns that other user's data (BOLA) |
| Rapid repeated API calls | No throttling response at all |
Re-run fingerprinting and endpoint discovery fresh. List every API route you can find.
Systematically test every ID-bearing endpoint you found for IDOR/BOLA.
Test every input field — search, login, registration, contact/feedback — for SQLi and XSS.
Test checkout for price/quantity tampering, coupon reuse, and step-skipping.
Check headers, exposed files, the password reset flow, and session handling.
Draft a one-paragraph summary and reproduction steps for your single best finding.
| Category | Found A Real Issue? | Severity Guess |
|---|---|---|
| Access Control / IDOR | — | — |
| Injection (SQLi/XSS) | — | — |
| Business Logic | — | — |
| Configuration | — | — |
| Auth / Session | — | — |
The clearest, most reliably reproducible one — not necessarily the scariest-sounding.
Pattern: "[Vulnerability class] in [location] allows [impact]" — e.g. "IDOR in /rest/basket/:id allows viewing other users' basket contents."
Exactly as you'd hand them to a stranger who has never opened the app before.
A screenshot of the request/response pair, or a short screen recording — visual evidence, not just a description.
"An attacker can view any other user's basket contents by incrementing the basket ID" — not "this could lead to a full data breach."
Use CVSS (the Common Vulnerability Scoring System) if the program requires it, or its plain Low/Medium/High/Critical scale otherwise.
| Section | What Triagers Are Checking For |
|---|---|
| Title | Specific enough to understand the bug without opening the report |
| Steps to reproduce | Followable exactly, in order, with no missing context |
| Proof of Concept | Visual evidence the bug is real — not just "this looks bad" |
| Impact | Precise, not inflated |
| Severity | Matches the program's own rating scale |
| Status | What It Means | What You Do |
|---|---|---|
| New | Received, not yet reviewed | Wait — don't follow up within 24–48h |
| Triaged | Confirmed and validated | Cooperate promptly if asked for more info |
| Duplicate | Someone reported it first | Accept gracefully; ask politely for the report ID if you want to learn from it |
| Not Applicable | Doesn't qualify under policy | Read the explanation; don't re-argue the same point |
| Resolved | Fixed, bounty paid if applicable | Ask if public disclosure is permitted before writing about it |
Never email an employee directly or post on social media.
To any triager follow-up questions.
One calm clarifying question is fine; repeated pushback damages your reputation.
Submissions, response time, acceptance rate — over time this becomes your case for private program invites.
Re-run Modules 0–1 against a second self-owned target. Join Bugcrowd University's free courses for the categories you felt weakest in.
Clear 10 more PortSwigger Academy labs in categories you haven't tried yet — insecure deserialization, NoSQL injection, GraphQL flaws.
Pick the VDP you bookmarked back in Lab 0.1. Read its policy twice. Do your first authorized recon pass under its actual scope.
Submit your first real report on that program, using the format from Lab 4.1. Whatever the outcome, log what you'd do differently.
| Category | Resource |
|---|---|
| Free training | PortSwigger Web Security Academy · Bugcrowd University · OWASP Juice Shop · OWASP WebGoat · TryHackMe free rooms |
| Best free education | HackerOne's public Hacktivity feed — read disclosed reports in your weakest category every week |
| Communities | r/bugbounty · Bugcrowd & HackerOne community forums · infosec Discord servers |
| Programs to start on | Any public Vulnerability Disclosure Program (VDP) before applying anywhere invite-only |
PortSwigger Academy's "Exploiting XXE to retrieve files" is the standard starting point.
Stock checkers, file import/export, and SOAP-based features are the classic locations.
Define an external entity pointing at a local file, then reference it in the document body.
Does the file's content come back directly in the response?
If nothing comes back directly, point the entity at a server you control (e.g. Burp Collaborator) and watch for a callback.
<!-- XXE payload to read a local file --> <?xml version="1.0"?> <!DOCTYPE data [<!ENTITY xxe SYSTEM "file:///etc/hostname">]> <data>&xxe;</data>
| Test | Vulnerable If… |
|---|---|
| In-band file read payload | File contents appear directly in the response |
| Out-of-band (Collaborator) payload | You receive a DNS/HTTP callback from the target server |
| Parameter-entity blind XXE | Data exfiltrates through a secondary out-of-band channel |
Free, purpose-built for this exact bug class.
Anything reflected back unescaped into a rendered page — a "name" field used in a personalized greeting is the classic case.
The payloads below are distinctive across the major template engine families.
Whichever probe evaluates tells you which engine — and therefore which exploitation path — you're dealing with.
Once fingerprinted, follow the lab's own path from "it evaluates math" toward full code execution.
// generic SSTI probes — distinctive across engines {{7*7}} // Jinja2 / Twig: renders 49 if vulnerable ${7*7} // FreeMarker / Velocity-style: renders 49 if vulnerable <%= 7*7 %> // ERB / similar engines: renders 49 if vulnerable
| Engine Family | Probe | Confirms Vulnerability If… |
|---|---|---|
| Jinja2 / Twig | {{7*7}} | Response shows 49 instead of the literal text |
| FreeMarker / Velocity | ${7*7} | Response shows 49 |
| ERB-style | <%= 7*7 %> | Response shows 49 |
Free, with a session-handling mechanic built around this exact bug.
Look for base64 blobs, Java's rO0 magic bytes, or PHP's O:8:"ClassName" pattern.
Inspect the underlying object structure once decoded.
Change a role or admin flag, then re-encode it in the original format.
Does the server trust your modified value without re-validating it?
# PHP serialized object — note the O:length:"ClassName" pattern O:4:"User":2:{s:8:"username";s:5:"alice";s:5:"admin";b:0;} # Java serialized objects start with this magic byte sequence (base64) rO0AB...
| Signal | Format |
|---|---|
rO0 (base64) / 0xac 0xed | Java serialized object |
O:N:"ClassName" | PHP serialized object |
| Long base64 blob in a cookie | Worth decoding regardless of platform |
Free, built specifically around MongoDB-style query injection.
Instead of a SQL-style string, send an operator object in place of a value.
If the app expects form data rather than JSON, the operator syntax changes slightly — see below.
$where JavaScript payloadSome MongoDB deployments allow arbitrary JavaScript evaluation inside queries.
Same proof standard as SQLi — are you logged in without a valid password?
// NoSQL auth bypass: send password as an operator object instead of a string {"username": "admin", "password": {"$ne": null}} // classic operator-based bypass in a URL-encoded form field username=admin&password[$ne]=1
| Payload | Expected If Vulnerable |
|---|---|
{"$ne": null} in place of a string password | Logged in without a valid password |
password[$ne]=1 (form encoding) | Same bypass via array-style operator injection |
$where JavaScript payload | Server evaluates attacker-supplied logic server-side |
Commonly /graphql, /api/graphql, or /v1/graphql.
It documents the entire schema for you, including hidden fields and mutations.
Exactly like Lab 3.2's API authorization testing, just expressed in GraphQL syntax.
GraphQL allows multiple queries in a single request — check whether this lets you bypass a per-request rate limit.
Request every field a query allows and see if it returns more than the UI ever displays.
# introspection query — dumps the entire schema if enabled
{ __schema { types { name fields { name } } } }
| Test | Vulnerable If… |
|---|---|
| Introspection query | Returns the full schema instead of being disabled in production |
| ID-bearing query/mutation | Returns another user's data (GraphQL BOLA) |
| Batched queries in one request | Bypasses a per-request rate limit |
| Full-field query | Returns internal fields never shown in the UI |
On a domain you own, check each subdomain's DNS record for a CNAME pointing to an external service.
Look for a "no such bucket/app" style error instead of real content — a sign the underlying resource was deleted but the DNS record never was.
Public "can-i-take-over-xyz" style references document the exact error text each cloud provider returns for an unclaimed resource.
If confirmed, you could register the resource yourself to verify the takeover — then immediately release it and fix the DNS record.
Free, and the safest place to ever attempt this technique.
The classic CL.TE desync sends both Content-Length and Transfer-Encoding, disagreeing about where the request ends.
Does the front-end and back-end server disagree, letting a hidden second request "ride along"?
Usually capturing another (simulated) user's request.
-- DNS check: does this subdomain's CNAME point somewhere now abandoned? dig CNAME forgotten.yourdomain.com // classic CL.TE request smuggling headers — practice inside a lab only Content-Length: 13 Transfer-Encoding: chunked 0 SMUGGLED
| Bug | Confirms Vulnerable |
|---|---|
| Subdomain takeover | CNAME points to a deleted/unclaimed resource, matching a known "not found" fingerprint |
| Request smuggling | Front-end/back-end disagree on request length, letting a second hidden request ride along |
Gets you adb and an emulator — or use a personal Android device with USB debugging enabled.
Free, automated static and dynamic analysis for APKs, via Docker.
Your dynamic instrumentation toolkit for later labs in this module.
OWASP MASTG Crackmes, DIVA — Damn Insecure and Vulnerable App (github.com/payatu/diva-android), and InsecureBankv2 (github.com/dineshshetty/Android-InsecureBankv2) — all free, open-source, built specifically to be hacked.
Side-load a practice APK onto your emulator/device via adb.
# pull MobSF via Docker docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest # install Frida + Objection for dynamic instrumentation pip install frida-tools objection # install a practice APK on a connected device/emulator adb install diva-beta.apk
| Check | Expected Result |
|---|---|
| localhost:8000 | MobSF upload UI loads in browser |
frida-ps -U | Lists running processes on the connected device |
adb devices | Shows your device as "device", not "unauthorized" |
| Practice app installed | Appears in the app drawer and launches normally |
mobsf / mobsf — change it before exposing the container beyond your own machine.Get readable Java/Kotlin-like source from the compiled bytecode.
Get AndroidManifest.xml and resources in near-original form.
Look for exported="true" on Activities, Services, or Broadcast Receivers — these can potentially be launched by any other app on the device.
API keys, hardcoded credentials, encryption keys committed straight into the code.
Then compare its automated findings against what you found manually.
# decompile to readable source jadx -d output_folder diva-beta.apk # unpack manifest & resources apktool d diva-beta.apk # grep for common hardcoded-secret patterns grep -rniE "api[_-]?key|secret|password\s*=" output_folder/
| Signal | What It Means |
|---|---|
exported="true", no permission set | Any app on the device may launch that component |
| Hardcoded API key/credential | A secret an attacker gets for free by decompiling |
android:debuggable="true" | Debugging enabled in production — should never happen |
| MobSF "High" severity finding | Worth manually confirming, not just trusting the scanner |
Log in and enter sensitive test data — never real personal information.
Requires a rooted device/emulator — or use adb backup on a non-rooted one.
Look through the XML files for plaintext credentials or tokens.
Open with a SQLite browser and search sensitive columns.
Data written there is readable by any other app with storage permission.
# pull the app's private data directory (rooted device/emulator) adb shell "run-as jakhar.aseem.diva cat /data/data/jakhar.aseem.diva/shared_prefs/diva_prefs.xml" # or, on a non-rooted device, use a full backup adb backup -f backup.ab jakhar.aseem.diva
| Location | Vulnerable If… |
|---|---|
| SharedPreferences XML | Stores a password, token, or PII as plaintext |
| SQLite database | Sensitive columns are unencrypted |
| External/shared storage | Sensitive data is readable by any app with storage permission |
| App-switcher cached screenshot | A login screen's contents are recoverable from a cached snapshot |
/data/data/. Never root a personal device that holds your real accounts just for this lab — use a disposable emulator image.Point it at Burp listening on your machine.
As a trusted user certificate (or system, on rooted devices).
Requests should appear in HTTP history, exactly like a web app.
A connection error or missing requests usually means certificate pinning — the app only trusts its own hardcoded certificate.
Attach to the running app and disable the pinning check at runtime, with no APK modification needed.
Retry the same action and check Burp's HTTP history again.
# attach Objection to a running app and bypass SSL pinning objection -g jakhar.aseem.diva explore # inside the objection shell: android sslpinning disable
| Check | Expected Result |
|---|---|
| Proxy set, CA installed | HTTPS requests appear in Burp with no errors |
| App fails to load data with proxy on | Likely certificate pinning in play |
sslpinning disable, then retry | Requests now appear in Burp's HTTP history |
From your Lab 6.2 manifest review.
Via adb, bypassing the app's normal login/navigation flow.
If the app registers a custom URL scheme, craft a link that jumps straight to a sensitive screen.
Send a crafted broadcast and observe whether the app reacts to it from an untrusted source.
Which component, which intent, what unauthorized action resulted.
# launch an exported activity directly, skipping login # (use the real package/activity names from your own Lab 6.2 manifest review) adb shell am start -n com.android.insecurebankv2/.PostLogin # trigger a custom URL scheme deep link adb shell am start -a android.intent.action.VIEW -d "divabank://transfer?amount=1000"
| Test | Vulnerable If… |
|---|---|
| Direct launch of exported Activity | Reaches a post-login screen without authenticating |
| Crafted deep link | Triggers a sensitive action outside the normal in-app flow |
| Crafted broadcast intent | App performs an action it should only trust from itself |
Info.plist for App Transport Security exceptions, and checking strings for hardcoded secrets all work without a jailbreak. Full dynamic testing (Frida-based pinning bypass, runtime manipulation) generally requires a jailbroken device or a cloud service like Corellium. Treat iOS as "this module, static-only" until you have access to one.| Access Type | Example |
|---|---|
| Program explicitly shares source | Whitebox/internal engagements, some private programs |
| OSS bounty programs | Programs covering open-source projects directly |
| Exposed source maps | .map files left enabled on a production JS bundle |
| Public company repo | A company's own open-source libraries/SDKs on GitHub |
The core shift: black-box, you guess where a bug might be. White-box, you can grep for it directly — then confirm it the same way you always have.
At github.com/juice-shop/juice-shop — the exact source of the app you've been testing black-box since Module 0.
E.g. the SQL injection login bypass from Lab 2.2.
Note specifically what the source revealed that probing alone never would have.
A classic injection pattern, in your cloned Juice Shop repo.
Classic XSS or code-injection sinks.
Command injection candidates if user input reaches them.
Does user-controlled input actually reach this sink, or is it hardcoded/sanitized?
Against your running Juice Shop instance — full circle from source to working proof.
# grep for classic vulnerable patterns across a cloned repo
grep -rn "innerHTML" --include=*.ts src/
grep -rn "child_process" --include=*.js routes/
grep -rnE "SELECT .* \+ |query\(.*\+" --include=*.ts src/
| Pattern | Risk If Reachable By User Input |
|---|---|
| String-concatenated SQL | SQL injection |
| innerHTML / eval / unsanitized template literal | XSS or code injection |
| child_process.exec / os.system | Command injection |
| Unvalidated deserialization of request body | Insecure deserialization (see Lab 5.3) |
A fast, rule-based static analysis tool with public security rulesets.
Covers hundreds of known vulnerable patterns in seconds.
Separate genuine findings from noise — the same instinct as Lab 7.2's manual grep, just at scale.
Tools built specifically to scan full commit history for secrets.
A secret deleted in a later commit is still recoverable from history unless the repo was rewritten.
# run Semgrep's public security ruleset against a cloned repo semgrep --config p/security-audit . # scan full git history for leaked secrets, not just current files gitleaks detect --source . --log-opts="--all"
| Tool | What It Catches That Manual Grep Doesn't |
|---|---|
| Semgrep | Hundreds of known vulnerable patterns across many languages in seconds |
| gitleaks / trufflehog | Secrets committed and later deleted, still present in full git history |
Against the project's manifest file.
Is the vulnerable code path actually reachable by the app, or just present but unused?
This becomes your report's remediation advice.
"You're running version X of library Y with known CVE-Z" is a valid, reportable bug on its own.
# Node.js projects npm audit # Python projects pip-audit
| Result | What To Do |
|---|---|
| High/Critical severity flagged | Check if the vulnerable function is actually called by the app |
| Fix available in a newer version | Note the exact version gap for your report's remediation section |
| Vulnerable code path not reachable | Still worth a lower-severity note — some programs still reward it |
A personal site, a blogging platform, or even a structured GitHub repo of markdown write-ups — the platform matters far less than consistency.
Using this workbook's report format from Module 4. These are 100% safe to publish — your own work, on sanctioned practice targets.
Not even after it's fixed. Check the program's disclosure policy every single time.
Target/lab name, vulnerability class, your approach, the fix.
This is exactly what triagers and program managers check before private invites.
| Safe To Publish | Never Without Explicit Permission |
|---|---|
| Your own lab/CTF write-ups (Juice Shop, PortSwigger, HTB, etc.) | Any real program's vulnerability, even after it's resolved |
| General technique explainers (how IDOR works, in the abstract) | Specific details identifying a real, unfixed target |
| Your own tools/scripts | A program's internal information learned incidentally during testing |
picoCTF for fundamentals, TryHackMe or HackTheBox for web-focused rooms, live competitions for speed under pressure.
Identify your weakest category.
Don't just play whatever's trending — target the gap.
Then read the official write-up after, win or lose — that's often where the real learning happens.
Challenge, category, solved/unsolved, what you'd do differently.
| Platform | Best For |
|---|---|
| picoCTF | Absolute fundamentals, beginner-friendly, free |
| TryHackMe | Structured, guided rooms across many categories |
| HackTheBox | Less hand-holding, closer to real-world conditions |
| Live competitive CTFs | Speed, pressure, and networking with other hunters |
| Certification | Signals | Worth It If… |
|---|---|---|
| eJPT | Foundational pentest knowledge, entry-level | You want a credential before any real-world experience |
| eWPT | Web app pentest focus, closely matches this workbook | You want web-specific credibility cheaply |
| OSCP | Hands-on offensive security, broadly respected | You're aiming at pentest/red-team jobs, not just bounty income |
| CRTO / OSEP | Advanced, red-team specific | You're already employed in offensive security and going deeper |
| None — pure bounty track | Public reputation, no certification | Your goal is bounty income itself, not a job requiring a credential |
A job that requires a credential on paper, or pure bounty income, which cares only about results.
The free path — this workbook, PortSwigger Academy, CTFs, and real programs — is sufficient on its own.
Check three real job listings you'd actually apply to and match the certification to what they ask for — don't guess.
Rather than five superficially — a specific Discord server, r/bugbounty, or a local security meetup.
Engage with a handful who write specifically about the areas your capstone scorecard flagged.
Track submissions, acceptance rate, and your best-performing category, then bias next quarter toward it.
A local BSides conference is inexpensive and the single best way to turn online contacts into real opportunities.
The fundamentals don't change, but it's easy to drift as confidence grows.
| Cadence | What To Review |
|---|---|
| Weekly | Did I submit anything? Did I read 1–2 disclosed reports outside my comfort zone? |
| Quarterly | Acceptance rate, best-performing category, one skill gap to target next |
| Yearly | Re-read program policies you actively test, refresh your portfolio, reassess certification needs |
| Category | Tool / Resource | Used In | On Kali |
|---|---|---|---|
| Proxy & Interception | Burp Suite Community Edition — portswigger.net/burp/communitydownload | Modules 0, 1, 2, 3, 5, 6 | Pre-installed |
| Recon | subfinder, amass, crt.sh, ffuf / gobuster | Module 1 | apt install |
| Mobile | MobSF, Frida, Objection, jadx, apktool | Module 6 | apt + pip install |
| Source Analysis | Semgrep, gitleaks / trufflehog, npm audit, pip-audit | Module 7 | apt + pip install |
| Practice Targets | OWASP Juice Shop — github.com/juice-shop/juice-shop · PortSwigger Web Security Academy — portswigger.net/web-security · DIVA — github.com/payatu/diva-android · InsecureBankv2 — github.com/dineshshetty/Android-InsecureBankv2 | Modules 0–7 | Clone/download |
| Free Training | PortSwigger Academy · Bugcrowd University · TryHackMe · picoCTF · HackTheBox | Modules 0, 8 | Browser-based |
| Bug Bounty Platforms | HackerOne · Bugcrowd · Intigriti · YesWeHack · Synack | Modules 0, 4, 8 | Browser-based |
| Communities | r/bugbounty · platform community forums · local BSides conferences | Module 8 | — |
| Term | Definition |
|---|---|
| BOLA | Broken Object Level Authorization — an endpoint returns or modifies another user's data because it never checks whether the requester actually owns that object |
| CSRF | Cross-Site Request Forgery — tricking a logged-in victim's browser into submitting a request they never intended to send |
| CVSS | Common Vulnerability Scoring System — a standardized 0–10 scale used to rate how severe a finding is |
| IDOR | Insecure Direct Object Reference — changing an ID in a request to access another user's data |
| PoC | Proof of Concept — the minimum evidence (screenshot, request/response, short script) that proves a vulnerability is real |
| Safe Harbor | A program's promise not to pursue legal action against researchers who test strictly within its published scope and rules |
| Scope | The exact list of assets, vulnerability types, and rules a program has authorized you to test |
| SSRF | Server-Side Request Forgery — tricking the server itself into making a request to an address you chose, often an internal one |
| SSTI | Server-Side Template Injection — user input escapes a template engine and gets evaluated as code on the server |
| Triage | A program's review process confirming whether a report is valid, a duplicate, or not applicable |
| VDP | Vulnerability Disclosure Program — accepts and fixes reports but does not pay a bounty |
| XXE | XML External Entity injection — an XML parser follows an attacker-defined external entity, often reading local files |