Common Vulnerability Categories — XSS, SQL Injection, CSRF

9 min read

The OWASP Top 10 gives you the categories. This lesson gives you the mechanics — how the most commonly encountered vulnerabilities actually work, what they look like in a real application, and exactly how to test for them. These are the bugs QA engineers find most often without a penetration testing background.

Four vulnerabilities every QA engineer should be able to test

Common web vulnerabilities — what they do and how to test them

XSS

  • Malicious JavaScript runs in another user's browser

  • Reflected: input echoed back in page

  • Stored: input saved and shown to others

  • Test: <script>alert(1)</script> in any input

  • Fix: output encode all user-controlled data

SQL Injection

  • User input alters the database query

  • Can bypass login, dump tables, delete data

  • Test: ' OR '1'='1 in login fields

  • Test: 1; DROP TABLE users; -- in any ID field

  • Fix: parameterised queries, never concatenate

CSRF

  • Victim's browser makes request attacker controls

  • Exploits automatic cookie sending

  • Test: replay POST request from different origin

  • Check: is a CSRF token required on all forms?

  • Fix: synchronizer tokens, SameSite cookies

IDOR

  • Access other users' resources by changing IDs

  • Most common form of Broken Access Control

  • Test: change /profile/1 to /profile/2

  • Test: modify document/order IDs in API calls

  • Fix: server-side ownership check on every resource

Cross-Site Scripting (XSS)

XSS occurs when an application takes user input and renders it in the browser without sanitising or encoding it first. An attacker who can inject <script>alert(document.cookie)</script> into a page that other users see can steal their session cookies, redirect them, or perform actions as them.

Reflected XSS happens when the malicious input is echoed back immediately — a search results page that shows "You searched for: [input]" without encoding the input is a classic example. A crafted link containing the payload causes anyone who clicks it to execute the script.

Stored XSS is more dangerous: the payload is saved in the database (a comment, a username, a product review) and executed for every user who views that content. One submission affects everyone.

Testing for XSS: inject <script>alert(1)</script> into every input field, URL parameter, and header that gets displayed back in a page. Also try <img src=x onerror=alert(1)> and "><script>alert(1)</script> to break out of attribute contexts. In a well-secured modern application, these should appear as literal text — not trigger an alert.

Modern frameworks (React, Angular, Vue) escape output by default, which significantly reduces XSS risk. But raw HTML injection (dangerouslySetInnerHTML in React, [innerHTML] in Angular), template literals, and server-rendered pages are common escape hatches that reintroduce the vulnerability.

SQL Injection

SQL injection occurs when user input is concatenated directly into a SQL query instead of being passed as a parameterised value. The attacker's input becomes part of the SQL command the database executes.

The classic test is entering ' OR '1'='1 into a login form username field. If the application builds its query as SELECT * FROM users WHERE username = '[input]', this transforms it into SELECT * FROM users WHERE username = '' OR '1'='1' — which is always true and may return all users, bypassing authentication entirely.

Modern ORMs (Hibernate, SQLAlchemy, ActiveRecord, Prisma) protect against SQL injection by default through parameterised queries. The risk re-appears with raw query interfaces, dynamic ORDER BY clauses, and poorly written stored procedures. Test any input that appears to influence database queries: search fields, filter parameters, sort options, URL-based IDs.

Important: SQL injection tests in a shared environment can cause real damage (deleting rows, locking accounts). Always test in an isolated environment and never run destructive payloads without explicit authorisation.

Cross-Site Request Forgery (CSRF)

CSRF exploits the fact that browsers automatically include cookies with every request to a domain — including requests triggered by visiting an attacker's site. If a user is logged into bank.com and visits attacker.com, attacker.com can cause the browser to send a POST request to bank.com/transfer with the user's session cookie attached — without the user's knowledge.

The defence is a CSRF token: a secret, per-session, unpredictable value that the server requires on every state-changing request. An attacker's site cannot read this token (due to the browser's same-origin policy) and cannot forge a valid request without it.

Testing: use your browser's developer tools or a proxy like Burp to capture a POST request that changes state (a password update, a fund transfer, a profile edit). Remove or modify the CSRF token and replay the request. If it succeeds, the CSRF protection is absent or broken. Modern frameworks include CSRF protection by default — but it can be inadvertently disabled.

Insecure Direct Object Reference (IDOR)

IDOR is the most common form of Broken Access Control (OWASP A01). The application exposes a direct reference to a database record — a user ID, an order number, a document ID — and fails to verify that the requesting user is authorised to access that specific record.

The test is simple: log in as User A and note the ID of a resource you own. Change the ID to something adjacent (incrementing by 1, or trying a known ID belonging to another test account). If the application returns User B's data, the authorisation check is missing.

IDOR frequently appears in APIs. A GET request to /api/orders/12345 that returns order details without checking whether the authenticated user owns order 12345 is vulnerable — regardless of whether the front-end hides the link.

Mass assignment

One more vulnerability worth knowing: mass assignment occurs when a server automatically binds all incoming request fields to an object, including fields that should not be user-settable. A registration form that sends { "name": "Alice", "email": "alice@example.com" } is safe. The same endpoint might also accept { "name": "Alice", "email": "alice@example.com", "role": "admin" } if mass assignment is enabled and the role field is not explicitly excluded.

Test by adding unexpected fields to POST and PUT request bodies and observing whether they take effect.

⚠️ Common mistakes

  • Only testing with the classic payloads. <script>alert(1)</script> and ' OR '1'='1 are well-known and many applications filter them. Security scanners and WAFs catch them. More sophisticated payloads (encoded variants, context-specific injections) bypass these filters. The lesson is: a failure to trigger an alert does not mean the vulnerability is absent.
  • Assuming ORM usage means injection is impossible. ORMs are safe for parameterised operations. Raw queries, LIKE clauses, dynamic table names, and stored procedures are still vulnerable if written carelessly.
  • Confusing CSRF protection in the UI with CSRF protection in the API. An application might have CSRF tokens on its HTML forms but no protection on its JSON API endpoints — which modern single-page apps use exclusively. Test the API directly.

🎯 Practice task

Set up OWASP WebGoat — a deliberately vulnerable application designed for security training. It runs locally and provides guided exercises for each vulnerability type.

  1. Complete the XSS exercise (reflected and stored variants).
  2. Complete the SQL injection exercise (authentication bypass).
  3. Review the CSRF exercise to understand what a missing token looks like in practice.

WebGoat provides immediate feedback and explains both the attack and the fix. Spending an hour with it gives you more practical security testing experience than any amount of reading. The next lesson covers the tools you will use to find these vulnerabilities at scale.

// tip to track lessons you complete and pick up where you left off across devices.