Releases: doretox/atomicvulns
Release list
v1.0 — Full OWASP Top 10 2021 Coverage
v1.0 — Full OWASP Top 10 2021 Coverage
Phase 7 of the ROADMAP, and the 1.0 milestone. Eight atoms closing the categories the earlier phases had left open — and with them, atomicvulns now covers all ten OWASP Top 10 2021 categories, A01 through A10 (38 atoms total).
New atoms
- atom 31 —
crypto-weak-hash— Insecure password storage: an unsalted MD5 digest leaked in a database dump is recovered offline with a precomputed rainbow table and replayed to log in as the victim; the fix is bcrypt, a slow salted primitive no rainbow table can be built against · A02 Cryptographic Failures - atom 32 —
crypto-ecb-mode— Insecure mode of operation: an AES-ECB session badge encrypts each 16-byte block independently, so the attacker alignsadmininto its own block via a chosen email and cut-and-pastes that ciphertext over therole=userblock to forge an admin badge without the key; the fix is authenticated AES-GCM · A02 Cryptographic Failures - atom 33 —
debug-enabled— Debug mode reachable: Flaskapp.run(debug=True)with the Werkzeug PIN disabled turns any unhandled exception into the interactive debugger, whose console runs arbitrary Python — one HTTP request returnsuid=0(root)(RCE); the fix isdebug=False, behind a real WSGI server in production · A05 Security Misconfiguration - atom 34 —
cors-wildcard— CORS misconfiguration: an authenticated API reflects the requestOriginback withAccess-Control-Allow-Credentials: true, so a hostile page reads the victim's private/accountdata in the browser (the literal*wildcard is a red herring — credentials force the origin-reflection vector); the fix is an exact-match allowlist · A05 Security Misconfiguration - atom 35 —
race-condition-basic— TOCTOU race:POST /withdrawreads the balance, checks it, and debits as separate steps, so twenty legitimate withdrawals fired with a single-packet attack all pass the check before any writes, overdrawing to-1900; the fix is one atomic conditionalUPDATEthe database serializes per row · A04 Insecure Design - atom 36 —
cve-demo— Known-vulnerable component: a config API pinsPyYAML 5.3.1(CVE-2020-14343), whosefull_loadstill rebuilds arbitrary Python objects from a crafted document for RCE; uniquely in the repo theapp.pyis byte-identical between the two sides — the whole fix is one line inrequirements.txt, bumping to 5.4.1 · A06 Vulnerable and Outdated Components - atom 37 —
weak-password-reset— Predictable reset token: a reset token generated withrandomseeded fromint(time.time())is regenerated by recovering the seed from the responseDateheader and replaying the PRNG, taking over the account; the fix is a CSPRNG (secrets.token_urlsafe), single-use and short-lived · A07 Identification and Authentication Failures - atom 38 —
logging-failures-demo— Missing security logging: the login rejects wrong passwords correctly but records nothing security-relevant, so a brute-force burst is invisible as an attack; the fixed side adds a structured security log plus a brute-forceWARNINGwithout blocking the login — the proof is the contrast between twodocker compose logs, not something appearing · A09 Security Logging and Monitoring Failures
What this phase covers
This phase closes every category the earlier phases had left open, and with it the OWASP Top 10 2021 map is complete: pure cryptographic failures (A02) from two directions, insecure design (A04) as a concurrency race, the rest of security misconfiguration (A05), vulnerable and outdated components (A06), the rest of authentication failures (A07), and security logging and monitoring failures (A09). Every one of A01 through A10 now has at least one atom — the 1.0 milestone.
The two A02 atoms attack cryptography from opposite ends. crypto-weak-hash is about the primitive: an unsalted MD5 is not encryption to reverse but a digest to precompute, so a leaked hash falls to a rainbow table — and bcrypt, slow and salted, is a primitive no table can be built against. crypto-ecb-mode is about the mode: AES is fine, but ECB encrypts each block independently, and that determinism lets whole ciphertext blocks be rearranged to forge a role without ever touching the key; authenticated GCM removes both the determinism and the tampering. Alongside them the rest of security misconfiguration lands: debug-enabled reaches the Werkzeug console for RCE, and cors-wildcard shows the dangerous CORS setting is origin reflection with credentials, not the * wildcard the browser already refuses.
Two lessons close the Top 10, both new to the repo. cve-demo makes the point that a security fix is not always in your code: the application is byte-identical on both sides and the entire remediation is a dependency version bump — the flaw lived in a component, not a line anyone wrote. logging-failures-demo ends on an unusual note: for the first time the flaw is an absence rather than a presence, and the proof is a contrast between two logs rather than a payload landing — detection is a discipline distinct from prevention, and the fixed side blocks nothing; it only makes the attack visible.
Each atom isolates one flaw with vulnerable/ and fixed/ side by side, Burp-first walkthroughs, and bilingual docs (EN + PT-BR).
Full changelog: v0.6.0...v1.0.0
v0.6 — Rare but Deadly
v0.6 — Rare but Deadly
Phase 6 of the ROADMAP. Five atoms covering what shows up rarely and does enormous damage when it does — and the phase that brings Node.js into the repo.
New atoms
- atom 26 —
prototype-pollution— Prototype pollution: a hand-written deep-merge of untrusted JSON descends through the__proto__key and writes onto the sharedObject.prototype, so{"__proto__":{"isAdmin":true}}poisons every object in the process — a brand-new, untouched object atGET /meinheritsisAdminand is treated as admin · A08 Software and Data Integrity Failures - atom 27 —
deserialization-node— Insecure deserialization: an attacker-controlled cookie unserialized withnode-serializecarries a_$$ND_FUNC$$_-tagged function body that the libraryevals, running during unserialize for remote code execution — the Node face of the flawdeserialization-pickle(20) shows in Python · A08 Software and Data Integrity Failures - atom 28 —
ldap-injection— LDAP injection: a corporate login concatenates untrusted input straight into an LDAP search filter ((&(uid=...)(userPassword=...))), so a bare*sent as the password turns the password check into the presence test(userPassword=*)and logs in as admin · A03 Injection - atom 29 —
sqli-second-order— Second-order SQL injection: a username stored through a safe parameterizedINSERTis later re-read from the database and concatenated raw into the change-passwordUPDATE, soadmin'--planted at registration lies dormant and detonates in a different flow, taking over another account · A03 Injection - atom 30 —
xxe-blind-oob— Blind XXE (out-of-band): a bulk-import endpoint parses untrusted XML with a parser that resolves external entities and may reach the network, but echoes nothing back — so it is confirmed out-of-band, an external parameter entity fetching an attacker-hosted DTD that then exfiltrates an app-server file over the same channel · A05 Security Misconfiguration
What this phase covers
Node.js arrives in the repo, and it arrives for a reason. Prototype pollution (26) is the one class that genuinely only makes sense in JavaScript: there is no payload string to recognize, just a key — __proto__ — that a hand-written deep merge follows out of the request body and into the object every other object inherits from. What makes it worth the trip is the blast radius: an object that never touched attacker input comes back carrying an attacker field. Insecure deserialization in Node (27) then closes a pair opened in Phase 4. deserialization-pickle (20) is the same flaw in Python, and reading the two side by side separates the class from the library — it is not that pickle is dangerous, it is that any format allowed to carry behavior gives remote code execution the moment something parses it. Both fixes land in the same place: data-only formats, and a merge that refuses the keys that reach the prototype.
LDAP injection (28) carries the injection story past SQL. A search filter is a query language of its own, and one unescaped metacharacter rewrites its logic rather than its data — * in the password position turns a comparison into a presence test that every account satisfies, so admin logs in without a password. Rare in modern web apps, routine in corporate and AD-backed logins, and precisely the reason the lesson generalizes: the sink is wherever untrusted text becomes query structure, whatever the language on the other side.
The last two atoms break the assumption that an injection is confirmed where it is sent. Second-order SQLi (29) separates the plant from the trigger in time: the registration form is parameterized and tests clean, but the username it safely stored is later read back and concatenated raw into another query, so the payload sits dormant until the change-password flow detonates it — data coming back out of your own database is untrusted input again. Blind XXE (30) separates the confirmation from the response: the parser is misconfigured exactly as in xxe-basic (18), but nothing is echoed, so the proof arrives on a different channel — the parser fetching an attacker-hosted DTD, then carrying a file out on that same channel. Same cause, same fix, different evidence; and the fix needs both resolve_entities=False and load_dtd=False, since either one alone still fetches.
Each atom isolates one flaw with vulnerable/ and fixed/ side by side, Burp-first walkthroughs, and bilingual docs (EN + PT-BR).
Full changelog: v0.5.0...v0.6.0
v0.5.0
v0.5 — Client-side & NoSQL
Phase 5 of the ROADMAP. Five atoms covering the client side and data stores beyond SQL — where the flaw lives in the browser or in what the server lets the client decide.
New atoms
- atom 21 —
xss-dom— DOM-based XSS: client-side JavaScript reads an attacker-controlled URL fragment (location.hash) and writes it toinnerHTML, executing in the victim's browser without the payload ever reaching the server · A03 Injection - atom 22 —
nosql-injection-mongo— NoSQL injection: a login endpoint passes untrusted JSON into a MongoDB filter, so an operator smuggled in place of a string ({"$ne": null}) logs in as admin without the password · A03 Injection - atom 23 —
csrf-basic— Cross-Site Request Forgery: a state-changing POST authorizes on the session cookie the browser attaches automatically, so an auto-submitting form on another origin forges an authenticated email change · A01 Broken Access Control - atom 24 —
open-redirect— Open Redirect: after login the app redirects to a user-controllednextwith no validation, sending the victim off-site (//evil.example) from a link that still starts on the trusted domain · A01 Broken Access Control - atom 25 —
mass-assignment— Mass Assignment: a profile update copies every field of the client's JSON onto the account, so an extra"role": "admin"escalates a normal user to admin · A01 Broken Access Control
What this phase covers
The XSS story reaches its third face. After reflected (02) and stored (08) — where the server echoes the payload — DOM XSS (21) moves the whole flaw into the browser: the source and sink are both client-side JavaScript, the fragment never reaches the server, and server-side escaping does nothing. NoSQL injection (22) is the modern counterpart to the SQLi arc: same idea — untrusted input becoming query logic — but through a type-confusion operator rather than string syntax, so the parameterization that fixes SQLi has no analog here.
The three A01 atoms sharpen what "broken access control" means beyond object ownership. CSRF (23) is the server authorizing an action without checking intent — the cookie proves who you are, not that you meant it — fixed with a per-session token the attacker cannot read across origins. Open redirect (24) and mass assignment (25) are the same lesson on two surfaces: the server delegating a decision that was its own. In (24) the input controls the destination of a redirect; in (25) it controls which fields of an object get written. Both close on the same pair of ideas — the server decides, not the input, and you enumerate what's allowed (an allowlist) rather than hunt what's forbidden (a blocklist).
Each atom isolates one flaw with vulnerable/ and fixed/ side by side, Burp-first walkthroughs, and bilingual docs (EN + PT-BR).
v0.4 — Server-side & Advanced
v0.4 — Server-side & Advanced
Phase 4 of the ROADMAP. Five atoms covering server-side attack surface — where the payload leaves the app and reaches another system, or an engine evaluates input it should have treated as data.
New atoms
- atom 16 —
ssrf-blind-oob— Blind SSRF, confirmed out-of-band via an embedded listener · A10 SSRF - atom 17 —
ssrf-cloud-metadata— SSRF against the cloud metadata endpoint (169.254.169.254), stealing IAM credentials · A10 SSRF - atom 18 —
xxe-basic— XML External Entity (XXE) injection: arbitrary file disclosure via an lxml parser that resolves external entities · A05 Security Misconfiguration - atom 19 —
ssti-jinja— Server-side template injection (SSTI): user input sewn into a Jinja2 template is evaluated by the engine, disclosing the Flask config and SECRET_KEY · A03 Injection - atom 20 —
deserialization-pickle— Insecure deserialization: an attacker-controlled cookie deserialized with Python's pickle executes embedded behavior via__reduce__, giving remote code execution · A08 Software and Data Integrity Failures
What this phase covers
The SSRF arc completes across three atoms: from reading an internal service directly (atom 04), to confirming a blind request out-of-band when the response is invisible (16), to pointing that same primitive at the one endpoint every cloud instance exposes — the metadata service — and walking away with the instance's IAM credentials (17). Same primitive, escalating stakes.
The remaining three open new categories on the server side. XXE (18) turns an XML parser that resolves external entities into arbitrary file disclosure. SSTI (19) shows user input sewn into a template being evaluated by the engine — the same input, treated as code instead of data. And insecure deserialization (20) closes the phase at remote code execution: a crafted cookie whose __reduce__ runs a command the moment pickle.loads touches it.
Several atoms name the defense a practitioner would reach for and show why it falls short of the root fix — defusedxml's deprecated lxml support (18), Jinja2's bypassable sandbox (19), signing the cookie to stop tampering when the format itself is the flaw (20). The lesson each time is the same: fix the cause (validate the destination, keep input out of the template, change the serialization format), not the symptom.
Each atom isolates one flaw with vulnerable/ and fixed/ side by side, Burp-first walkthroughs, and bilingual docs (EN + PT-BR).
Full changelog: v0.3.0...v0.4.0
v0.3 — Access Control & Authentication
v0.3 — Access Control & Authentication
Phase 3 of the ROADMAP. Five atoms spanning access control and identity — object-level authorization, the JSON Web Token trilogy, and session lifecycle.
New atoms
- atom 11 —
idor-uuid-guessable— Insecure Direct Object Reference (guessable UUID) · A01 Broken Access Control - atom 12 —
bola-rest— Broken Object Level Authorization (BOLA) in a REST API · A01 Broken Access Control - atom 13 —
jwt-weak-secret— JWT weak signing secret, brute-forced · A02 Cryptographic Failures - atom 14 —
jwt-key-confusion— JWT algorithm confusion, RS256 → HS256 · A02 Cryptographic Failures - atom 15 —
session-fixation— session id not regenerated at login · A07 Identification and Authentication Failures
What this phase covers
The JWT trilogy is now complete (none-alg → weak-secret → key-confusion: three ways a token verified by real code still falls — verification skipped, a guessable key, and a strong key the server is tricked into misusing). The object-level pair extends access control from atom 03's numeric IDOR into a guessable-UUID variant and REST BOLA. And session-fixation opens a new axis — A07 identity and session lifecycle — where the attacker forges nothing and steals nothing: they plant a session id before login and let the victim authenticate it.
Two of these atoms (key-confusion, session-fixation) share a lesson: the default tool already mitigates the naive bug (modern PyJWT rejects key confusion; Flask's signed session resists fixation), so each models the anti-pattern where the flaw actually lives — hand-rolled verification, a manual server-side session — and documents why.
Each atom isolates one flaw with vulnerable/ and fixed/ side by side, Burp-first walkthroughs, and bilingual docs (EN + PT-BR).
Full changelog: v0.2.0...v0.3.0
v0.2 — Injection Deep Dive
v0.2 — Injection Deep Dive
Phase 2 of the ROADMAP. Five atoms deepening the injection classes and bridging into access control.
New atoms
- atom 06 — `sqli-blind-boolean` — Blind SQL injection (boolean-based) · A03 Injection
- atom 07 — `sqli-blind-time` — Blind SQL injection (time-based) · A03 Injection
- atom 08 — `xss-stored` — Stored Cross-Site Scripting · A03 Injection
- atom 09 — `command-injection-basic` — OS Command Injection · A03 Injection
- atom 10 — `path-traversal-basic` — Path traversal · A01 Broken Access Control
What this phase covers
The SQLi trilogy is now complete (union → boolean-blind → time-blind: one root cause, three exfiltration channels). The XSS pair is complete (reflected → stored: one cause, two delivery models). Command injection adds the shell as a third injection sink, and path traversal bridges into Broken Access Control — the same /etc/passwd read as command injection, but by navigation rather than execution.
Each atom isolates one flaw with vulnerable/ and fixed/ side by side, Burp-first walkthroughs, and bilingual docs (EN + PT-BR).
Full changelog: v0.1.0...v0.2.0
v0.1 — MVP Pentester
v0.1 — MVP Pentester
Hands-on pentest practice: one OWASP vuln class per atom, in minimal code.
First public release of atomicvulns. Five atoms covering the vulnerabilities most likely to appear in your first pentest reports — each one a ~20-line Flask app with vulnerable/ and fixed/ versions side by side.
Try it now
git clone https://github.com/doretox/atomicvulns.git
cd atomicvulns
./atom up sqli-union-basic
# vulnerable → http://127.0.0.1:8001
# fixed → http://127.0.0.1:8101Point Burp at 127.0.0.1:8080, open the browser, follow the walkthrough.
What's in v0.1
| # | Atom | OWASP | What you learn |
|---|---|---|---|
| 01 | sqli-union-basic |
A03 Injection | UNION-based SQL injection via f-string in raw query |
| 02 | xss-reflected |
A03 Injection | Reflected XSS via Jinja2 |safe filter bypassing autoescape |
| 03 | idor-numeric-id |
A01 Broken Access Control | IDOR by missing ownership check on /notes/<id> |
| 04 | ssrf-basic |
A10 SSRF | Server-side request forgery against an isolated internal service |
| 05 | jwt-none-alg |
A02 Cryptographic Failures | JWT signature bypass via alg=none accepted by misconfigured decoder |
Each atom ships:
vulnerable/andfixed/apps in matching Docker containersWALKTHROUGH.md— Burp-first exploitation, step by stepDIFF.md— the commented diff between vulnerable and fixedREADME.md— context and how to run- All of the above in EN and PT-BR
Why another vulnerable-app project
Monolithic vulnerable apps already exist (DVWA, Juice Shop, WebGoat). atomicvulns is built on a different premise: radical atomism. One vulnerability per app. ~20 lines of code per atom. Pentest students map "code cause → request/response → exploit" without having to understand an entire application first.
What this means in practice:
- No "find the bug" mode. The walkthrough points at the vulnerable line directly. The goal is internalization, not scavenger hunt.
- No "Try it yourself" section padding. Variations live on PortSwigger Web Security Academy, linked from every atom's Theory primer. This repo shows the code that breaks; the Academy explores variations.
- Burp Suite is the primary tool. Every walkthrough uses Burp as the main path. UI is context only.
- Both languages, both directions. Every walkthrough, diff, and README ships in EN and PT-BR, synchronized in the same commit.
What's next
The ROADMAP.md lists 38 atoms across 7 phases. Phase 1 (this release) closes the MVP — the five vulnerabilities most likely to show up in your first pentest reports. Phase 2 deepens injection variants. Phase 3 attacks auth and access control. And so on.
Want to contribute? Open a Discussion to propose an atom or a translation. CONTRIBUTING.md has the workflow.
Found a security issue with the wrapper or infrastructure (not the labs themselves — those are intentional)? See SECURITY.md.
⚠ Intentionally vulnerable. Run locally only. Never expose to the internet or a shared network.