Security testing cheatsheet
Learn to test software the way an attacker would — safely, legally, and so developers can fix what you find. Examples use OWASP Juice Shop, an app built to be broken into for practice, running on your own machine. Each section has three parts:
- In short — the idea in one sentence.
- Example — a real request against the practice app, with its real response.
- Try it — a small exercise.
Every technique here is for systems you own or have written permission to
test. Running these against someone else's system can be a crime, even with
good intentions. The examples target OWASP Juice Shop
(deliberately vulnerable, made for this) on localhost. See
permission and scope before anything else.
Read permission and scope first — always. Then start the practice app and go through Part 1 (the ideas and the baseline checks any tester can do). Part 2 is the main web risks. Part 3 is scanners, deeper testing and reporting. The verified results shown are from Juice Shop v20.2.0; the app changes, so your details may differ.
Contents
Permission & scope · Practice app
Part 1 — Beginner: What security testing is · Think like an attacker · OWASP Top 10 · HTTP & auth recap · Security headers · Reading errors · Baseline checks
Part 2 — Core: Injection & SQLi · Broken access control · Authentication · XSS · Secrets in code · Vulnerable dependencies · Business logic · Common mistakes
Part 3 — Advanced: Automated scanning (ZAP) · SAST & dependency scans in CI · Burp Suite · API & cloud · CVSS & severity · Reporting · Words you'll meet
Permission & scope
In short: get written permission and a clear scope before you touch a system — this is the difference between security testing and a crime.
A rules of engagement document should say:
| Item | Example |
|---|---|
| In scope | staging.example.com, the mobile app build 4.2 |
| Out of scope | Production, third-party payment pages, employees (no phishing) |
| Allowed | Automated scanning (rate-limited), manual testing, test accounts |
| Not allowed | Denial-of-service, deleting data, social engineering |
| Window | Mon–Fri, 09:00–18:00, specific dates |
| Contacts | Who to call if something breaks; how to report criticals fast |
For learning, use legal targets built for it: OWASP Juice Shop, the PortSwigger Web Security Academy (free), OWASP WebGoat, DVWA, Hack The Box, TryHackMe.
Practice app
In short: OWASP Juice Shop is a full shop with dozens of planted vulnerabilities and a hidden scoreboard that tracks the ones you find.
docker run --rm -d -p 3001:3000 --name juice bkimminich/juice-shop
# open http://localhost:3001 (find the hidden /#/score-board)
curl -s localhost:3001/rest/admin/application-version # {"version":"20.2.0"}
Stop it with docker rm -f juice when you're done.
Part 1 — Beginner
1. What security testing is
In short: security testing looks for ways the system can be misused — reading data you shouldn't, acting as someone else, or breaking it — and reports them with a fix.
| Kind | What it is | Who does it |
|---|---|---|
| Baseline checks | Headers, auth, obvious flaws during normal QA | Any tester |
| SAST / SCA scans | Tools read code and dependencies | Automated, in CI |
| DAST scans | Tools attack the running app | Automated + tester |
| Penetration test | Skilled manual attack, time-boxed | Specialist |
| Secure code review | Manual review of risky code | Specialist / senior dev |
This page focuses on the baseline checks every tester should do, plus the scanners and the main risks — enough to add real value on any team.
Try it: open Juice Shop and find the hidden scoreboard (/#/score-board).
It lists the challenges — your practice checklist.
2. Think like an attacker
In short: for each feature, ask what an attacker wants and how they'd get it — then test that path.
A quick threat model (STRIDE) for a login form:
| Threat | Question |
|---|---|
| Spoofing | Can I log in as someone else? |
| Tampering | Can I change data I shouldn't (price, role)? |
| Repudiation | Are actions logged so they can't be denied? |
| Information disclosure | Does it leak data or internal details? |
| Denial of service | Can I make it slow or crash? |
| Elevation of privilege | Can a normal user become admin? |
Where the valuable data is (accounts, payments, personal data) is where to spend your time.
3. OWASP Top 10
In short: the OWASP Top 10 is the industry list of the most common serious web risks — a great coverage checklist. Check owasp.org/Top10 for the current edition.
| Risk theme | One-line check |
|---|---|
| Broken access control | Reach other users' data/actions by changing an id |
| Cryptographic failures | Sensitive data not encrypted; HTTP; weak hashing |
| Injection | Input runs as SQL, commands, or script (XSS) |
| Insecure design | The logic itself is unsafe (refund > payment) |
| Security misconfiguration | Verbose errors, default settings, missing headers |
| Vulnerable components | Old libraries with known CVEs |
| Identification & auth failures | Weak login, sessions, MFA bypass |
| Software & data integrity | Unsigned updates, unsafe deserialization |
| Logging & monitoring failures | Attacks go unnoticed |
| Server-side request forgery | Server fetches attacker-controlled URLs |
Juice Shop has challenges for every one of these.
4. HTTP & auth recap
In short: security testing lives in the HTTP request — method, headers, body, cookies — so you must be comfortable reading and changing it.
Key status codes for security: 401 (not authenticated), 403 (authenticated but not allowed), 500 (server error — often leaks details).
Tokens: modern apps use a JWT (JSON Web Token) — three base64 parts
(header.payload.signature) sent as Authorization: Bearer <token>.
J=http://localhost:3001
# a normal login returns a JWT
curl -s -X POST $J/rest/user/login -H 'Content-Type: application/json' \
-d '{"email":"test@test.com","password":"wrong"}' -w " [%{http_code}]\n" | head -c 80
# [401] ← wrong credentials, as it should be
New to this? See the API testing cheat sheet, sections 1–9.
5. Security headers
In short: a few response headers block whole classes of attack — their absence is an easy, real finding.
Real result on Juice Shop:
curl -s -I http://localhost:3001/ | grep -iE 'content-security-policy|strict-transport|x-frame-options|x-content-type-options'
# X-Content-Type-Options: nosniff
# X-Frame-Options: SAMEORIGIN
# (no Content-Security-Policy, no Strict-Transport-Security)
| Header | Blocks | Juice Shop |
|---|---|---|
Content-Security-Policy | Cross-site scripting, data injection | ❌ missing — a finding |
Strict-Transport-Security | Downgrade to HTTP | ❌ missing |
X-Frame-Options / CSP frame-ancestors | Clickjacking | ✅ SAMEORIGIN |
X-Content-Type-Options: nosniff | MIME sniffing | ✅ present |
Cookie flags Secure, HttpOnly, SameSite | Cookie theft | Check per cookie |
Try it: check the headers of a site you own. Which are missing?
6. Reading errors
In short: error messages and 500s often leak the database type, queries, file paths or stack traces — useful to an attacker, and a finding on their own.
Real result — a broken input in Juice Shop's search:
curl -s "http://localhost:3001/rest/products/search?q=test')--" -w " [%{http_code}]\n" | head -c 90
# <title>Error: SQLITE_ERROR: incomplete input</title> [500]
That one line tells an attacker the database is SQLite and that user input reaches the SQL query — the door to SQL injection (next part). Production apps should return a generic error and log the detail server-side.
7. Baseline checks
In short: without any special tools, every tester can run a short security baseline during normal testing.
[ ] HTTPS everywhere; HTTP redirects to HTTPS
[ ] Security headers present (section 5)
[ ] Wrong login is rejected (401) with a generic message (no "user not found")
[ ] Logout ends the session; Back button doesn't show private pages
[ ] Change an id in a URL/request → you can't see others' data (section 9)
[ ] Enter ' " < > in fields → no 500, no script runs, no SQL error
[ ] Errors are generic (no stack traces, no SQL, no file paths)
[ ] Sensitive data isn't in the URL, page source, or localStorage
[ ] Cookies have Secure, HttpOnly, SameSite
Try it: run this baseline on Juice Shop. You'll already find several issues.
Part 2 — Core
8. Injection & SQLi
In short: injection happens when input is treated as code; SQL injection (SQLi) is the classic — input changes the meaning of a database query.
Real result — the famous Juice Shop login bypass:
J=http://localhost:3001
curl -s -X POST $J/rest/user/login -H 'Content-Type: application/json' \
-d '{"email":"'"'"' OR 1=1--","password":"anything"}' \
| python3 -c "import json,sys;a=json.load(sys.stdin)['authentication'];print('logged in as', a['umail'])"
# logged in as admin@juice-sh.op
The email ' OR 1=1-- turns the login query into "match any row", so the app
logs you in as the first user — the admin — with no password. The fix is
parameterised queries (the database treats input as data, never code),
plus input validation.
To test: put ', ", ' OR 1=1--, 1;-- into fields and parameters, and
watch for 500s, SQL errors, or unexpected success.
Try it: try the same payload in the email field of Juice Shop's login page in the browser.
9. Broken access control
In short: the most common serious flaw — a logged-in user reaches data or actions that should be someone else's, usually by changing an id (BOLA / IDOR).
Real result — one token reads several users' baskets:
J=http://localhost:3001
TOKEN=$(curl -s -X POST $J/rest/user/login -H 'Content-Type: application/json' \
-d '{"email":"'"'"' OR 1=1--","password":"x"}' | python3 -c "import json,sys;print(json.load(sys.stdin)['authentication']['token'])")
for id in 1 2 3; do curl -s -o /dev/null -w "basket $id -> %{http_code}\n" "$J/rest/basket/$id" -H "Authorization: Bearer $TOKEN"; done
# basket 1 -> 200
# basket 2 -> 200
# basket 3 -> 200
Each basket belongs to a different user, yet one token reads them all — there's
no check that the basket belongs to the caller. The fix: check ownership on the
server for every object (basket.userId == currentUser.id).
To test: log in as user A, then request user B's ids on every endpoint that takes one — orders, baskets, messages, documents, profile. Also try admin endpoints as a normal user (function-level access control).
Not everything is broken — GET /api/Users returns 401 without a token,
which is correct. Report the failures, note the passes.
10. Authentication
In short: test how logins, sessions and tokens can be abused — weak passwords, no rate limit, tokens that never expire, resettable by anyone.
| Check | How |
|---|---|
| Rate limiting | Many wrong logins fast → lockout or delay, or brute force is possible |
| Generic errors | "Invalid email or password", never "no such user" (which reveals valid emails) |
| Password rules | Weak passwords accepted? Common-password list used? |
| Token expiry | Does an old JWT still work hours later? |
| Token contents | Decode the JWT payload — any sensitive data? Is the signature checked? |
| Password reset | Can you reset someone else's? Predictable reset tokens? Security-question guessing |
| MFA | Can it be skipped by calling the API directly? |
# decode a JWT payload (test tokens only — never paste real tokens into websites)
echo "<JWT>" | cut -d. -f2 | base64 -d 2>/dev/null; echo
Try it: in Juice Shop, decode the admin token from section 9. What's in the payload?
11. XSS
In short: cross-site scripting (XSS) is injection into a web page — attacker-controlled script runs in another user's browser, stealing sessions or acting as them.
| Type | Where the payload lives |
|---|---|
| Reflected | In the request (URL, form), echoed straight back |
| Stored | Saved (comment, name) and shown to others later — the most dangerous |
| DOM-based | JavaScript writes untrusted input into the page |
Real result — Juice Shop reflects search input into the response body:
curl -s "http://localhost:3001/rest/products/search?q=%3Ciframe%3E" | grep -o "iframe" | head -1
# iframe ← the tag comes back in the response
Whether it executes depends on how the page renders it; that reflection is
the signal to test further. A classic test payload is
<img src=x onerror=alert(1)>. The fix: escape output for its context
(HTML, attribute, JS, URL) and set a Content-Security-Policy (the header
missing in section 5).
Only run XSS payloads on apps you're authorised to test. alert(1) is a
harmless proof; never use real attack payloads against systems you don't own.
12. Secrets in code
In short: API keys, passwords and tokens committed to a repo are one of the easiest and most damaging leaks — scan for them.
| Where they hide | Find them with |
|---|---|
| Source files, config | gitleaks detect, GitHub secret scanning |
| Git history (even if deleted later) | gitleaks detect scans all commits |
| Front-end bundles, mobile apps | Search the built files; anything shipped to a client is public |
| Docker images, CI logs | Trivy secret scan; review pipeline output |
docker run --rm -v "$PWD:/repo" zricethezav/gitleaks:latest detect --source=/repo --no-banner
A secret in Git history is compromised even after you delete the line — rotate it (change the key), don't just remove it.
13. Vulnerable dependencies
In short: most code in an app is third-party libraries; known vulnerabilities in them (CVEs) are found automatically and often the easiest thing to fix.
npm audit --audit-level=high # Node projects
pip-audit # Python
docker run --rm -v "$PWD:/src" aquasec/trivy:latest fs --scanners vuln /src # any project + images
| Tool | Scans |
|---|---|
npm audit, pip-audit, bundler-audit | Language dependencies |
| Trivy, Grype | Dependencies, container images, IaC |
| Dependabot, Renovate | Auto-raise update PRs |
| OWASP Dependency-Check | Many ecosystems |
Triage results: is the vulnerable code path actually used? Fix HIGH/CRITICAL that are reachable first.
14. Business logic
In short: flaws where every request is valid but the rules are broken — scanners can't find these; you find them by understanding the domain.
| Flaw | Test |
|---|---|
| Negative quantity / price | Order −5 items → refund? |
| Coupon abuse | Apply the same coupon many times; stack coupons |
| Refund > payment | Refund more than was paid |
| Skip a step | Go straight to "order confirmed" without paying |
| Change a hidden field | Post "price": 0 or "role": "admin" in the body |
| Race conditions | Redeem a one-time voucher twice at the same instant |
Juice Shop has several of these (place an order with tampered totals, apply another user's coupon, etc.). The fix is always server-side validation of the rule — never trust the client.
Try it: in Juice Shop, add an item to the basket, then use the API to change the quantity to a negative number. What happens to the total?
15. Common mistakes
In short: how security testing goes wrong.
| Mistake | Better |
|---|---|
| Testing without written permission | Get scope in writing; use legal practice targets |
| Running a scanner and pasting raw output | Triage: confirm, remove false positives, prioritise |
| Only checking the happy path is "secure" | Test as the wrong user, with no token, with bad input |
| Real attack payloads on shared/practice apps | Harmless proofs (alert(1)); never destructive |
| Reporting "SQL injection" with no repro | Exact request, response, and impact |
| Ignoring the passes | Note what's correctly protected too |
| Testing production without a window/plan | Agree timing, limits, rollback, and who's on call |
Part 3 — Advanced
16. Automated scanning (ZAP)
In short: OWASP ZAP is a free web scanner; its baseline scan is passive (it spiders and observes, doesn't attack) — safe to run in CI.
Real result against Juice Shop:
docker run --rm --add-host=host.docker.internal:host-gateway ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t http://host.docker.internal:3001
Total of 88 URLs
WARN-NEW: Content Security Policy (CSP) Header Not Set [10038] x 5
WARN-NEW: Cross-Domain Misconfiguration [10098] x 5
WARN-NEW: Timestamp Disclosure - Unix [10096] x 5
WARN-NEW: Cross-Origin-Embedder-Policy Header Missing or Invalid [90004] x 10
…
FAIL-NEW: 0 WARN-NEW: 8 PASS: 59
- Baseline (passive): safe, fast, CI-friendly — confirms the missing CSP from section 5.
- Full scan (
zap-full-scan.py): active — it attacks; only on non-production with permission. - Every result still needs a human to confirm and rate.
17. SAST & dependency scans in CI
In short: run three fast scanners on every pull request — code (SAST), dependencies and images (SCA), and secrets — and gate on new HIGH/CRITICAL.
# .github/workflows/security.yml (excerpt)
- name: SAST — Semgrep
run: docker run --rm -v "$PWD:/src" semgrep/semgrep semgrep scan --config auto --error /src
- name: Dependencies & secrets — Trivy
run: docker run --rm -v "$PWD:/src" aquasec/trivy fs --scanners vuln,secret --severity HIGH,CRITICAL --exit-code 1 /src
- name: DAST — ZAP baseline (passive) against staging
run: docker run --rm ghcr.io/zaproxy/zaproxy zap-baseline.py -t https://staging.example.com
| Type | Tool | Finds |
|---|---|---|
| SAST | Semgrep, CodeQL, SonarQube | Risky patterns in your code |
| SCA | Trivy, Grype, Dependency-Check | Vulnerable dependencies & images |
| Secrets | Gitleaks, Trivy | Committed keys and passwords |
| DAST | ZAP baseline | Runtime issues (headers, cookies) |
Gate on new HIGH/CRITICAL only, or teams switch the scanner off.
18. Burp Suite
In short: Burp Suite is the standard tool for manual web security testing — a proxy that sits between browser and server so you can read and change every request.
| Feature | Use |
|---|---|
| Proxy | Intercept and edit requests live |
| Repeater | Re-send a request with tweaks (the manual tester's workhorse) |
| Intruder | Automate variations (id enumeration, fuzzing) — throttled |
| Decoder | Encode/decode base64, URL, JWT |
| Scanner | Active scan (Pro only) |
The free Community edition covers Proxy, Repeater and Decoder — enough to learn with. OWASP ZAP is a fully free alternative with a similar proxy and Repeater-like "Requester".
Try it: point Burp or ZAP at Juice Shop, add an item to the basket, and use Repeater to change the quantity in the captured request.
19. API & cloud
In short: APIs and cloud config have their own top-10 lists — use them as checklists.
OWASP API Security Top 10 — the biggest is again broken authorization (BOLA): test every endpoint with the wrong user, no token, and the wrong role. Full checklist in the API testing cheat sheet.
Cloud (AWS/GCP/Azure): the common wins are public storage buckets, over-broad IAM roles, open security groups, and logging turned off. Scan infrastructure-as-code with Checkov or Trivy and check against the CIS Benchmarks.
LLM features: the OWASP Top 10 for LLM Applications — prompt injection, data leakage — see the AI/LLM testing cheat sheet.
20. CVSS & severity
In short: rate each finding with CVSS (a 0–10 score) plus business context, so teams fix the worst first.
| CVSS | Rating | Example from Juice Shop |
|---|---|---|
| 9.0–10.0 | Critical | SQLi login bypass to admin |
| 7.0–8.9 | High | BOLA reading other users' baskets |
| 4.0–6.9 | Medium | Missing CSP header (enables XSS impact) |
| 0.1–3.9 | Low | Timestamp disclosure, verbose error |
CVSS gives a consistent base score; always adjust for your context — a "medium" on a page holding medical data may be your top priority.
21. Reporting
In short: each finding is a runnable proof plus impact and a concrete fix; the report opens with the risks that matter most.
ID / Title: SEC-01 — Authentication bypass via SQL injection on /rest/user/login
Severity: Critical (CVSS ~9.8)
Affected: POST /rest/user/login (Juice Shop v20.2.0, local)
Steps: curl -s -X POST $J/rest/user/login -H 'Content-Type: application/json' \
-d '{"email":"'"'"' OR 1=1--","password":"x"}'
Result: HTTP 200; logged in as admin@juice-sh.op without a valid password
Impact: Full account takeover of any user, including admin
Fix: Use parameterised queries; validate input; never build SQL by string concatenation
Reference: OWASP A03:2021 Injection
The report starts with an executive summary: overall risk, count by severity, and the top three to fix now. Report criticals immediately by phone/secure channel — don't wait for the written report.
22. Words you'll meet
In short: the jargon, in one line each.
| Word | Meaning |
|---|---|
| Rules of engagement | The signed agreement on what/how/when you may test |
| SAST / DAST / SCA | Static (code) / dynamic (running app) / software-composition (dependencies) analysis |
| SQLi | SQL injection — input changes a database query |
| XSS | Cross-site scripting — attacker's script runs in a victim's browser |
| BOLA / IDOR | Broken Object Level Authorization / Insecure Direct Object Reference — reaching others' data by id |
| CSRF | Cross-site request forgery — a victim's browser is tricked into a request |
| SSRF | Server-side request forgery — the server is tricked into fetching a URL |
| JWT | JSON Web Token — a signed token carrying user info |
| CVE | A public id for a known vulnerability |
| CVSS | Common Vulnerability Scoring System — 0–10 severity |
| Payload | The input crafted to trigger a vulnerability |
| False positive | A scanner warning that isn't a real problem |
For the service, including penetration-test phases, see the security testing guide.