# Day 7: OS Command Injection

New OWASP category today: Command Injection. One clean solve, one long, genuinely exhausting debugging session that ended without a final clean win — and a good closing tangent connecting all of this back to why I'm actually doing it.

## The Core Idea

Same root pattern as every SQL injection lab, just aimed at a different target. Instead of user input reaching a database query, it reaches an **operating system command**:

```javascript
exec(`check_stock.sh ${productId} ${storeId}`);
```

If `storeId` isn't validated, a shell metacharacter like `;` lets an attacker chain on a second, completely unrelated command:

```plaintext
storeId=1;whoami
```

becomes

```bash
check_stock.sh 1 1;whoami
```

Both commands run — the shell doesn't know or care that only the first one was intended.

## Lab 1: Simple Case — Solved

Found the vulnerable "check stock" form on a product page. Since it was a `POST` request (not `GET`), the parameters weren't visible in the URL at all — a good reminder that `GET` puts data in the URL, `POST` puts it in the request body, and you have to go find the actual `<form>` HTML or the Network tab's request payload to see what's really being sent.

Built the request with `curl`, matching the form's `method`, `action`, and field names exactly:

```bash
curl -X POST "https://[lab-url]/product/stock" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "productId=1&storeId=1;whoami"
```

The response returned the server's actual username — direct, visible proof of command execution. Straightforward once the request was built correctly.

## Lab 2: Blind OS Command Injection — The Real Session

This one had no visible output at all — same "blind" concept as blind SQLi, just applied to shell commands. The only available signal: **response timing**, using `sleep 10` as the injected payload instead of a boolean condition.

### Where most of the time actually went

This lab had more moving pieces than the first: a CSRF token, a session cookie, five separate form fields, and no visible clue about which specific field was vulnerable. Several real mistakes stacked up in sequence:

1.  **Malformed request body** — used `key:value key:value` formatting instead of proper `key=value&key=value` form-urlencoded syntax. Basic but easy to slip into when typing quickly.
    
2.  **Malformed Cookie header** — wrote `Cookie session=...` instead of `Cookie: session=...`, missing the colon.
    
3.  **Guessing the vulnerable field** — tried `message` first, then `email`, without a systematic process, wasting several rounds
    
4.  **Unencoded space in the payload** — `sleep 10` contains a literal space, which isn't strictly valid inside `x-www-form-urlencoded` data without encoding it as `%20`. This was likely the actual reason a payload that should have worked kept returning "Could not save" instead of triggering a delay.
    

### What actually got confirmed

After systematically isolating variables — confirming the CSRF/session pairing worked with a clean baseline request first, then testing one field at a time with the same simple payload — a timing delay genuinely appeared when injecting into the `email` field. That's real, valid confirmation the vulnerability exists and where it lives, even though the final clean "lab solved" run got tripped up again by the space-encoding issue before I stopped for the day.

**Being honest about this one:** I checked the official solution partway through, after roughly an hour of debugging. Worth saying plainly rather than glossing over — sometimes the useful move is confirming your own reasoning against a reference rather than grinding indefinitely, especially after real, substantial debugging work had already been done. The vulnerability was genuinely found through my own testing; the exact clean payload formatting is what needed a check.

## curl vs. Burp, Revisited

A lot of today's friction was curl-specific: manually retyping full commands for every small edit, hand-encoding special characters, easy-to-miss syntax slips (a missing colon, wrong separator). Burp Suite's Repeater tool solves exactly this class of problem — it captures a real request your browser already sent correctly, then lets you edit one field at a time visually and resend, without re-deriving cookies, CSRF tokens, or content-type headers by hand each time. Burp was laggy on this hardware a couple weeks ago, but given today's specific pain points, it's worth revisiting despite the performance cost — the tradeoff might be worth it for this exact kind of iterative field-testing workflow.

* * *

*Testing performed exclusively against PortSwigger's own intentionally vulnerable lab environments as part of self-directed learning on the Web Security Academy.*
