System designCore10 min

Common attack classes

Most web attacks are the same mistake in different places: data from an attacker is treated as code, or a request is trusted because of where it seems to come from. Injection, cross-site scripting, request forgery and server-side request forgery each have a known, boring defense; the breaches happen where it was skipped, or in a dependency nobody patched.

1 · The pattern

Most attacks turn data into code, or borrow someone else's authority

A community recipe site lets people sign up, post recipes, comment, search, and import a recipe by pasting a link to another site. Every one of those features takes input from strangers. The classic attacks are the ways that input can be misread:

Attack

What gets confused

Recipe-site example

SQL or command injection

Data is run as a query or command

A search term that ends the query and starts another

Cross-site scripting (XSS)

Data is run as script in other users' browsers

A comment containing a script

Cross-site request forgery (CSRF)

Another site's request carries your login

A hidden form that deletes your recipes

Server-side request forgery (SSRF)

Your server fetches what an attacker chooses

An import link pointing at an internal address

Unsafe deserialization

Data rebuilds objects that run code

A crafted saved-session blob

The OWASP Top 10 for 2025 ranks the broader categories: broken access control first, then security misconfiguration, software supply chain failures, cryptographic failures, and injection fifth. Access control has its own page (Authentication & authorization); this one covers the classes above and the dependencies they hide in. Each has a defense that works in a design, not just a filter that guesses.

2 · Injection

Injection is fixed by keeping data and code apart, not by escaping

The recipe search builds its SQL by gluing the user's text into a string. A search for cake' OR '1'='1 changes the query's meaning; a longer one can read other tables. The first defense is to never glue at all, but to send the query and the data separately, as a prepared statement with parameters:

Search: vulnerable, then safe

python
# Vulnerable: the input becomes part of the SQL text
cur.execute("SELECT id, title FROM recipes WHERE title LIKE '%" + q + "%'")

# Safe: the query is fixed; the input is only ever a value
cur.execute("SELECT id, title FROM recipes WHERE title LIKE %s", (f"%{q}%",))

With parameters, the database receives the query's structure before it sees any user data, so no input can change what the query does. The defenses run from prepared statements, through carefully built stored procedures and allow-list validation, to escaping all user input, the weakest of them: escaping is easy to get wrong in one place out of hundreds. Parts of a query that can't be parameters, such as a sort column, should be chosen from a fixed list.

Command injection is the same mistake with the operating system's shell. The recipe site converts recipes to PDF by running a command with the recipe title in it; a title containing ; rm -rf becomes a second command. The main defense is to avoid calling OS commands directly and use a library function instead. Where a command is unavoidable, pass arguments as a list rather than a shell string, validate them against an allow list, and run the process with the least privilege it needs.

3 · XSS

Cross-site scripting runs an attacker's script in other users' browsers

A comment on a popular recipe contains a script tag that sends the reader's session data to the attacker. If the site renders comments as raw HTML, every reader runs it:

Stored XSS: the script is saved once and served to everyone who opens the page, running with each reader's session and permissions on the site.

No single technique solves XSS; it takes a combination:

Defense

What it does

Encode output for its context

Text in HTML is entity-encoded, so a script tag is shown, not run; attributes, JavaScript and URLs each need their own encoding

Let the framework do it

Modern frameworks auto-escape by default; the risk moves to escape hatches that insert raw HTML

Sanitize allowed HTML

If comments may contain formatting, keep only safe tags with an HTML sanitizer

Content Security Policy

Restricts what scripts a page may load or run; easy to get wrong, so an extra layer, not the main defense

HttpOnly session cookies

Scripts can't read them; limits theft, but doesn't stop the script acting as the user

4 · CSRF

Request forgery rides on the cookies a browser sends automatically

Browsers attach a site's cookies to requests going to that site, even when another site triggered the request. A reader logged in to the recipe site visits a different page that contains a hidden form posting to the recipe site's "delete all my recipes" endpoint. Without protection, the browser sends the session cookie along, and the recipe site sees a valid, logged-in request:

With a CSRF token or a SameSite cookie, the forged request either arrives without the secret token or without the session cookie, and is refused.

Defense

How it stops the forged request

SameSite=Strict cookie

The cookie is sent only on requests that start from the same site

SameSite=Lax cookie

Also sent on top-level cross-site navigations with safe methods such as GET, not on cross-site POSTs; some browsers apply Lax by default

Anti-CSRF token

A secret, server-generated token in each form is checked on submit; another site can't read it

No state changes on GET

Links and image tags can't trigger changes

Origin check

State-changing requests from other origins are refused, as an extra layer

One warning ties this section to the last: cross-site scripting can defeat every CSRF defense, because a script running on the site itself can read the token.

5 · SSRF

Server-side request forgery makes your server fetch what the attacker chooses

The "import recipe from a link" feature fetches whatever URL the user pastes. An attacker pastes an internal address instead, and the recipe site's server, sitting inside the network, fetches it on the attacker's behalf. In cloud environments SSRF is often used to steal credentials from the instance metadata service:

Attackerpastes an internal URLImport servicefetches any URLMetadata service169.254.169.254Internal admin APInot on the internetimport this linkfetchfetch

The import service can reach places the attacker can't. Whatever it fetches can come back to the attacker in the imported 'recipe'.

The defenses depend on whether the destinations can be known in advance. If they can, use an allow list of permitted hosts. If any public URL must be accepted, as with recipe imports, four rules apply.

  • Resolve the name and check the resulting address, refusing private, loopback and link-local ranges, and connect to the address you checked, since an attacker can make a name resolve differently the second time.
  • Turn off redirects in the fetching client, or check each hop, because a public URL can redirect to an internal one.
  • Fetch from an isolated network segment or through an egress proxy that can't reach internal services (Webhooks, which have the same problem).
  • Harden the metadata service: on AWS, require IMDSv2 and disable IMDSv1.

6 · Dependencies

The same holes arrive through libraries and data formats

Much of an application's code is other people's, and attacks follow it there. Two incidents show the shape, from their official records:

Incident

What happened

Lesson

Log4Shell, 2021 (CISA)

Logging an attacker's string made the Log4j library fetch and run code from the attacker's server

Find every vulnerable instance, patch at once starting with internet-facing systems, review logs

Equifax, 2017 (US GAO)

An unpatched Apache Struts flaw in a dispute portal; personal data of at least 145.5 million people accessed

Patch, keep detection working (an expired certificate hid the traffic), segment database access

Both are injection and patching problems at once, which is why the OWASP Top 10 now ranks software supply chain failures third. The defenses are inventory and speed: know every library and version in every service, so a new vulnerability can be matched to affected systems in hours; patch on a schedule and urgently when needed; and limit what any one compromised component can reach (Network segmentation & zero trust).

Deserialization is the data-format version of the same risk. Many languages have native formats that rebuild arbitrary objects from bytes, with features that can be repurposed. Attacks on deserializers of untrusted data have led to denial of service, access-control bypass and remote code execution. Accept untrusted data only in plain data formats such as JSON, validate it against a schema, and never feed native serialized objects from users, cookies or other services into a deserializer.

7 · In practice

Use the design defense first, and layers after it

Attack

Primary defense

Extra layers

SQL injection

Parameterized queries

Allow lists for identifiers, least-privilege database users

Command injection

Library calls instead of shells

Argument lists, allow lists, low privileges

XSS

Contextual output encoding, framework auto-escaping

Sanitizer for rich text, CSP, HttpOnly cookies

CSRF

Tokens and SameSite cookies

No state-changing GETs, Origin checks

SSRF

Allow list, or checked addresses without redirects

Isolated egress, hardened metadata service

Vulnerable dependencies

Inventory and fast patching

Segmentation, monitoring

For the recipe site, the checklist:

  • Never build queries or commands from strings; parameters and libraries only.
  • Encode everything you render, and know your framework's escape hatches.
  • Protect every state change with tokens and SameSite cookies.
  • Treat every server-side fetch of a user URL as hostile.
  • Keep an inventory of dependencies and a way to patch all of them within days.
  • Add a WAF and monitoring as layers, not as the fix (DDoS protection & WAFs).

Check yourself

5 questions

1. Why do parameterized queries prevent SQL injection when escaping often fails?
2. A comment containing a script runs in every reader's browser. Which defense addresses the root cause?
3. A session cookie is set with SameSite=Lax. Which cross-site request carries it?
4. The recipe importer must accept any public URL. What stops SSRF to the metadata service?
5. What made Log4Shell exploitable by merely logging a user's input?

Takeaways

Remember this

  • Most attack classes treat attacker data as code, or trust a request because of where it seems to come from.
  • Prevent injection with parameterized queries and library calls instead of string-built queries and shells.
  • Prevent XSS with contextual output encoding and framework auto-escaping, with CSP and HttpOnly as extra layers.
  • Prevent CSRF with tokens and SameSite cookies, and SSRF with allow lists or checked addresses and isolated egress.
  • Many breaches come through dependencies: keep an inventory, patch fast, and never deserialize untrusted native objects.