> For the complete documentation index, see [llms.txt](https://simon-6.gitbook.io/simoncyber/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://simon-6.gitbook.io/simoncyber/portswigger-web-academy/sql-injection/lab-13.md).

# Lab 13

Second-order SQL injection often occurs in situations where developers are aware of SQL injection vulnerabilities, and so safely handle the initial placement of the input into the database. When the data is later processed, it is deemed to be safe, since it was previously placed into the database safely. At this point, the data is handled in an unsafe way, because the developer wrongly deems it to be trusted.

**String concatenation (vulnerable):**

java

```java
String query = "SELECT * FROM products WHERE category = '" + input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
```

If `input` is `x'; DROP TABLE products;--`, the final string the database actually receives and parses as raw SQL becomes:

sql

```sql
SELECT * FROM products WHERE category = 'x'; DROP TABLE products;--'
```

The database parses all of that as code, sees the `'` break the string early, sees the `;` and stacks a new statement, and runs `DROP TABLE products`. Your input directly rewrote the SQL.

**Parameterized query (safe):**

java

```java
PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();
```

Same malicious `input`, same value: `x'; DROP TABLE products;--`. But the database already parsed and locked in `SELECT * FROM products WHERE category = ?` before your input ever arrived. `setString` doesn't rebuild a SQL string at all — it just tells the database "parameter 1 is this exact text." The database treats it as one literal value to search for, equivalent in spirit to running:

sql

```sql
SELECT * FROM products WHERE category = (the literal 27-character string: x'; DROP TABLE products;--)
```

It looks for a category with that exact bizarre name, finds nothing, returns an empty result. Nothing gets executed as code, nothing gets dropped.

The whole difference in one line: concatenation builds the SQL text *using* your input, so your input becomes part of the code; parameterization builds the SQL text *first*, then hands your input over separately as pure data that can never become code, no matter what's inside it.
