This complete guide covers all 16 Security tutorials on TheCodeForge, organised by topic.
Security bugs are not a separate category of bug. They are ordinary bugs in code that happens to sit on a trust boundary — and the reason they keep recurring is that the boundary is rarely where the author thought it was. SQL injection is a string-building bug. XSS is an escaping bug. SSRF is a bug about which hostnames your server is willing to resolve. Log4Shell was a logging call.
Which makes this track less about attack catalogues and more about one repeated question: at this line, is the data I am about to interpolate, render, deserialise, or fetch coming from somewhere I control? Almost every item below is an answer to that question going wrong, and almost every fix is structural — a parameterised API, a strict allow-list, a verified signature — rather than a filter that tries to recognise bad input.
Every injection class has the same shape: a string is assembled from code plus data, then handed to an interpreter that cannot tell which part was which. SQL, HTML, shell, LDAP, XPath and log formats are all interpreters. Escaping tries to fix this after the fact by making the data unrecognisable as code, and it works right up until the context changes — the same string is safe in an HTML text node and dangerous inside a <script> block or an onclick attribute.
Parameterisation fixes it at the source by never assembling the string at all. The query and the values travel to the interpreter separately, so there is no point at which the value could be reinterpreted as syntax. That is why it is the only defence that does not degrade as your code grows.
subprocess.run([...]) instead of a shell string. A templating engine's auto-escaping instead of manual replacement. Structured logging instead of interpolating user input into a format string — which is precisely the door Log4Shell walked through.JWTs concentrate a lot of trust in a small string, and the failures cluster. Signature verification failed is usually a key mismatch after a rotation or a deploy to a different environment — the token is genuine, just signed by a key this service no longer has. An expired token that is still accepted is almost always clock skew: the verifier's leeway is generous, or the two machines disagree about the time.
The third failure is the dangerous one, because it produces no error at all: decoding a token without verifying it. Several libraries expose a decode function that parses the payload and checks nothing, and it is very easy to reach for it while debugging and never take it back out.
| Symptom | Real cause | Fix |
|---|---|---|
| Signature verification failed | Key rotated, wrong environment's secret, or algorithm changed between issuer and verifier | Publish a JWKS with key IDs and select the key by the token's kid, so rotation is a non-event |
| Expired token still accepted | Clock skew plus a large leeway window, or expiry simply not being checked | Sync clocks with NTP, keep leeway at a few seconds, and assert that exp validation is on |
| Any token accepted | Decode-without-verify, or accepting the alg the token itself claims | Verify with an explicit algorithm allow-list; never let the token choose how it is checked |
| Valid token, wrong tenant | aud and iss not checked | Validate audience and issuer as strictly as signature |
MD5 and SHA-256 are not broken as hashes in the way people usually mean. They are wrong for passwords because they are fast, and speed is exactly what an attacker with a stolen database wants. A modern GPU tries enormous numbers of SHA-256 candidates per second; the same hardware against bcrypt or Argon2 at sane parameters is reduced to a crawl, because those algorithms are deliberately expensive and, in Argon2's case, deliberately memory-hungry so that GPUs lose their advantage.
Salting is a separate concern and solves a different problem: it stops one precomputed table from cracking every account at once. Modern password hashes generate and embed the salt for you, which is why the stored value contains its own parameters — and why you should never implement the format by hand.
The three vulnerabilities in this track that hurt mature codebases most are the ones where the code is doing exactly what it was told. An IDOR is an endpoint that loads the record whose id was requested — correctly — without asking whether the caller owns it. SSRF is an image-fetcher or webhook that resolves the hostname it was given, including 169.254.169.254 and anything else inside your network. Dependency confusion is a package manager resolving the highest version it can find, which may be an attacker's upload to a public registry under your internal package name.
None of these are caught by input validation, because the input is well-formed. They are caught by making authority explicit: every query scoped to the caller, every outbound fetch checked against an allow-list after DNS resolution, every internal package name claimed and every registry pinned.
| Vulnerability | The code is doing | Make authority explicit by |
|---|---|---|
| IDOR | Loading the requested id | Scoping every query by owner (WHERE id = ? AND account_id = ?), not by a separate permission check that a new endpoint can forget |
| SSRF | Fetching the supplied URL | Resolving DNS first, rejecting private and link-local ranges, re-checking after redirects, and disabling redirects where you can |
| Dependency confusion | Installing the newest matching version | Scoped names, a single private registry with explicit upstreams, and a committed lockfile |
| Secrets in git history | Storing what it was committed | Treating any pushed secret as burned: rotate first, purge history second, add scanning to pre-commit third |
raw(), extra(), text(), a hand-built WHERE fragment — is as vulnerable as a plain driver if you interpolate into it. Table and column names also cannot be parameterised in any ORM, so dynamic ORDER BY and dynamic table selection must be allow-listed. The ORM protects the common path; injection now lives in the unusual 10%.SameSite=Lax is the browser default and blocks the classic cross-site form post, but it still allows top-level navigations, it does nothing for same-site subdomain attacks, and any endpoint that accepts a state change on GET remains exposed. Keep tokens on state-changing endpoints and treat SameSite as defence in depth rather than the whole defence.Every tutorial starts with a plain-English analogy — then real code, then interview questions.
Browse Security Tutorials →