Web Vulnerabilities 101 (Part 1): When User Data Gets Mistaken for a Command
Almost every dangerous web application flaw traces back to one very old mistake: the system cannot tell the difference between data sent by a user and its own commands. A customer types a name into a search box, but the database reads that string as an instruction. Someone leaves a comment, and another visitor's browser executes it as JavaScript.
In OWASP Top 10:2025 — the latest release, superseding the 2021 edition that most articles still quote — Injection sits at A05, yet it remains the category with the highest number of CVEs: more than 62,000 across 37 CWEs, including over 30,000 for Cross-site Scripting and over 14,000 for SQL Injection. It dropped in rank not because it became less dangerous, but because other categories moved up.
This article is written for junior developers, QA engineers and project managers who want to understand what they are actually defending against.
How the categories changed from OWASP Top 10:2021 to 2025. Source: OWASP Foundation, CC BY-SA 4.0.1. SQL Injection: the classic that refuses to die
The application takes a parameter from the URL and concatenates it straight into a query:
String query = "SELECT * FROM accounts WHERE custID='" + request.getParameter("id") + "'";
When an attacker submits a string containing a quote plus an always-true condition, the structure of the statement breaks and the query returns the whole table instead of a single row. From there it can escalate to reading, modifying or deleting data, or invoking stored procedures.
A common misconception: using an ORM does not make you safe by default. OWASP explicitly documents Hibernate HQL still being vulnerable when developers concatenate input instead of binding parameters.
How to prevent it
The first choice is always parameterised queries or the safe API of your ORM, so that data travels on one channel and query structure on another:
PreparedStatement ps = conn.prepareStatement("SELECT * FROM accounts WHERE custID = ?");
ps.setString(1, request.getParameter("id"));
Add defence in depth: allowlist-based server-side validation; never let users decide table names, column names or sort direction, because those cannot be escaped; give the application database account the minimum privileges it needs; and run SAST/DAST in CI/CD so flaws are caught before production. A WAF is a mitigation layer, never a substitute for writing the query correctly.
How a SQL injection attack works. Source: Cloudflare Learning Center.2. OS command injection: far more damaging than SQLi
When user input is concatenated into an operating system command — for example a domain lookup feature calling nslookup — an attacker only needs a command separator to execute arbitrary commands on the host. This is the shortest path to losing the entire server.
Prevention: avoid shelling out when a library can do the job; if you must spawn a process, pass arguments as an array rather than a single command string and do not go through a shell; validate input with a strict allowlist; run the process with least privilege inside a constrained container.
3. Cross-Site Scripting: attacker code running in your users' browsers
XSS happens when user-supplied content is rendered into HTML without proper encoding. There are three main flavours: stored (the payload lives in the database and hits everyone who views the page), reflected (delivered through a crafted link) and DOM-based (client-side JavaScript writes untrusted data into the DOM). Typical impact includes session theft, actions performed on behalf of the victim, and fake login forms injected into a legitimate page.
How to prevent it
The core principle is context-aware output encoding: HTML body, attribute, JavaScript and URL contexts each require a different escaping strategy. Modern frameworks such as React, Vue, Angular and Blade escape by default; the problem is developers deliberately opting out with innerHTML, v-html or dangerouslySetInnerHTML. If you genuinely need to render user-supplied HTML, sanitise it with a dedicated library such as DOMPurify instead of hand-rolled regular expressions.
Two additional layers pay for themselves: a Content-Security-Policy header to block unauthorised scripts even when a flaw slips through, and the HttpOnly attribute on session cookies so JavaScript cannot read the token.
A cross-site scripting attack flow. Source: Cloudflare Learning Center.4. SSRF: making your server fetch data on the attacker's behalf
Server-Side Request Forgery occurs when the application accepts a URL from the user and issues a request to it — import-image-from-URL features, webhooks, HTML-to-PDF converters. The attacker swaps in an internal address to reach what the Internet cannot: internal services, cloud metadata endpoints holding temporary credentials, or admin panels exposed only on the private network.
Worth noting: in the 2025 edition OWASP folded SSRF into A01 Broken Access Control. That classification captures the essence — it is a trust boundary violation.
Prevention: allow requests only to a pre-approved list of domains or IPs rather than maintaining a blocklist; resolve DNS and check the destination IP before connecting so internal ranges are rejected; do not follow redirects automatically; isolate the fetching component in a subnet with no access to internal systems and enforce egress control at the network layer.
5. Path traversal and file upload
Two features that look harmless and are routinely forgotten. For downloads driven by a user-supplied filename, traversal sequences let an attacker climb out of the intended directory and read configuration files or private keys. For uploads, the risk is an executable file being planted and then invoked.
Prevention: never concatenate a user-supplied filename into a path — generate a random name and keep the mapping in the database; canonicalise the path and verify it still sits inside the allowed root; determine file type from actual content rather than trusting the extension or Content-Type; store files in separate object storage, serve them from a different domain, and disable execution in the upload directory.
6. Prompt injection: the 2026 edition of an old problem
If your product ships a chatbot or a RAG pipeline, note that OWASP tracks LLM01:2025 Prompt Injection separately in the OWASP Top 10 for LLM. The nature is identical to classic injection: the model cannot distinguish system instructions from user input or from retrieved document content. The indirect variant is worse, where the malicious instruction is already sitting inside a document the bot retrieves.
Mitigations: treat every model output as untrusted data and never pass it straight into SQL, a shell or HTML; scope the tools an agent may call to least privilege; require human confirmation for actions with real-world impact; mark the boundary between system instructions and retrieved content clearly; log all prompts and tool calls so incidents can be investigated.
Checklist before merging a pull request
- Every database query is parameterised, with no string concatenation left.
- No user input is concatenated into an operating system command.
- All data rendered to the UI is encoded for its output context.
- Content-Security-Policy is set and session cookies are HttpOnly.
- Every user-supplied URL passes an allowlist before the server calls it.
- File paths are canonicalised and confined to the permitted directory.
- Validation runs server-side, not only in the client-side form.
- CI includes SAST and dependency scanning.
Conclusion
Part one boils down to a single rule: keep data separate from commands, and never trust input from the client. Part two covers the flaws that are harder to see but usually cost more — access control, authentication, misconfiguration and the software supply chain.
References
- OWASP Top 10:2025 — owasp.org/Top10/2025
- OWASP Cheat Sheet Series — cheatsheetseries.owasp.org
- PortSwigger Web Security Academy — portswigger.net/web-security
- Cloudflare Learning Center — cloudflare.com/learning