srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
blob: b880fc391fc829f1efd5fab03aecaaf6304c799f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# mitmux

A terminal-based intercepting proxy for manual pentest work - a
daily-driver alternative to Burp Suite, Caido, or OWASP ZAP that runs
entirely in your terminal, with vi-style modal editing for raw
requests.

mitmux is two binaries: `mitmuxd`, a headless daemon that owns the
proxy listener and the SQLite history database, and `mitmux`, a Bubble
Tea TUI that talks to it over a Unix socket. The daemon keeps running
(and keeps capturing traffic) independently of the TUI - close the UI,
reopen it later, the proxy never stopped.

For the build history, the architectural decisions behind it, and a
list of what's deliberately not implemented (and why), see
[`PLAN.md`](PLAN.md).

## Features

- **Intercepting proxy**: plaintext HTTP passthrough and TLS
  interception (per-host leaf certificates signed by a locally
  generated CA), with HTTP/1.1 and HTTP/2 handled natively and
  independently on the client and upstream legs - a client that only
  speaks HTTP/1.1 and an origin that prefers HTTP/2 both work correctly
  in the same request.
- **History**: every request/response captured to SQLite. Raw wire
  bytes are preserved byte-for-byte on HTTP/1.1 legs (what request
  smuggling and parser-differential analysis actually needs); HTTP/2
  legs - which have no single "raw bytes" representation, being
  multiplexed HPACK-compressed framing - are reconstructed and marked
  as such, never silently presented as exact.
- **Search**: full-text search (FTS5) across headers and bodies, plain
  text just works (`example.com`, `x-forwarded-for`, `192.168.1.1` -
  no quoting needed), plus structured filters: `status:404`,
  `status:4xx`, `status:>=400`, `source:repeater`, `flagged:true`,
  column filters like `host:example.com`, and `AND`/`OR`/`NOT`.
- **Repeater**: edit and resend a raw request. What you type is what
  goes on the wire - no normalization, no auto-fixed `Content-Length`,
  no "helpful" reformatting. That's the point of a Repeater. Multiple
  tabs: sending an entry to Repeater opens a new tab rather than
  replacing whatever's already there, so you can iterate on several
  requests side by side.
- **Intruder** (Sniper only): mark positions in a request template
  with `§markers§`, supply a payload list, fuzz one position at a time
  against a shared payload set. Results land in the same history table
  as everything else, searchable the same way. Payload processing
  (optional case and encode rules, applied to every payload before it's
  sent) and grep-match/grep-extract (flag or pull text out of each
  result's response with a regexp) are both configurable before starting
  an attack - see [Intruder](#intruder) below.
- **Match-and-replace**: header rewrite rules (add, remove, or modify)
  for requests and/or responses, applied live as traffic passes
  through. History still shows what was actually sent/received on each
  side - match-and-replace transforms the wire, it doesn't rewrite the
  audit trail.
- **Flagging**: mark an entry to revisit later (★), filterable via
  `flagged:true`.
- **Comparer**: mark one entry (`c`), then `c` on a different entry to
  see a colored unified diff of either side's request or response.
- **Decoder**: standalone URL/Base64/Hex/HTML encode and decode (`d`),
  output updates live as you type or switch transforms.
- **Vi-modal editing**: the raw request editors (Repeater, Intruder)
  are real modal editors - normal mode by default, `i`/`a`/`o`/etc. to
  insert, `hjkl`, `dd`/`yy`/`p`, word motions, `gg`/`G`. See
  [Vi bindings](#vi-bindings) below. Everywhere else (history table,
  read-only response views), standard vi navigation (`j`/`k`, `g`/`G`,
  `ctrl+u`/`ctrl+d`) already works - that's the underlying TUI
  library's default, not something layered on top.

## Install / build

Requires Go 1.26.5+ (see `go.mod`).

```sh
git clone <this repo>
cd mitmux
go build -o bin/mitmuxd ./cmd/mitmuxd
go build -o bin/mitmux  ./cmd/mitmux
```

## Quick start

1. **Start the daemon.** By default it listens on `127.0.0.1:8080` and
   stores its CA and history database under `~/.config/mitmux` (XDG
   config dir):

   ```sh
   ./bin/mitmuxd
   ```

   On first run it generates a root CA and prints where the cert
   landed, e.g. `~/.config/mitmux/ca.pem`.

2. **Trust the CA.** To intercept HTTPS without constant certificate
   warnings, import `ca.pem` into whatever's making the requests -
   your browser's certificate store, `curl --cacert`, a mobile device's
   trusted-certificate settings, etc. For copy-pasteable, OS-specific
   steps (Linux: whichever of `trust`/`update-ca-trust`/
   `update-ca-certificates` is actually on your system, plus Firefox's
   own NSS store; macOS: Keychain; Windows: `certutil`/PowerShell), run:

   ```sh
   ./bin/mitmuxd -install-ca
   ```

   This only prints commands - it never runs anything against your
   trust store itself. Installing a root CA is a system-wide trust
   change, so you run the printed command yourself.

3. **Point a client at the proxy.** e.g.:

   ```sh
   curl -x http://127.0.0.1:8080 --cacert ~/.config/mitmux/ca.pem https://example.com/
   ```

   Or configure your browser's proxy settings to `127.0.0.1:8080`.

4. **Open the TUI** (in another terminal - the daemon keeps running
   independently):

   ```sh
   ./bin/mitmux
   ```

Both binaries take flags for non-default setups - `-listen`, `-socket`,
`-ca-dir`, `-db`, `-upstream-proxy` on `mitmuxd`; `-socket` on `mitmux`.
Run either with `-h` for the full list.

`-listen` takes a comma-separated list to bind more than one address
(`-listen "127.0.0.1:8080,127.0.0.1:8081"`) - one logical proxy on
several ports/interfaces, sharing the same history, CA and rules.

`-upstream-proxy host:port` chains every outbound connection through
another HTTP CONNECT proxy (Burp, a corporate proxy, anything that
speaks CONNECT) instead of dialing origins directly. Chaining into
another *intercepting* proxy needs that proxy's own CA trusted too -
it terminates and re-signs the connection with its own CA, which
mitmux's outbound TLS client has no reason to trust otherwise; you'll
see a clear certificate-verification error in history rather than a
silent failure. SOCKS5 upstreams aren't implemented.

## Usage

Press `?` from any screen in the TUI for the full, current keybinding
reference - it's generated from the same source as this document, so
it never drifts out of date the way a static list can. The summary
below is enough to get going.

### History (the default view)

| Key | Action |
|---|---|
| `↑`/`↓` or `j`/`k` | navigate (also `g`/`G` top/bottom, `ctrl+u`/`ctrl+d` half-page) |
| `enter` | view request/response detail |
| `r` | open in Repeater |
| `i` | open in Intruder |
| `f` | toggle flag |
| `c` | mark for comparison - press `c` on another entry to diff |
| `d` | Decoder |
| `/` | search |
| `m` | match-and-replace rules |
| `q` | quit |

### Detail view

`tab` switches request/response, `p` toggles pretty-printed JSON on the
response (display-only - never touches the stored or resent bytes), `c`
mark/compare (same as the history list), `r`/`i` jump straight to
Repeater/Intruder seeded from this entry, `esc` back.

### Comparer

Reachable by pressing `c` on two different history entries (from either
the list or detail view). Shows a colored unified diff - `diff -u`
style, `+`/`-` lines - of the two entries' requests or responses,
`tab` to switch between them. CRLF is normalized before diffing so an
exact HTTP/1.1 capture doesn't show every line as changed purely from
the invisible `\r`.

### Decoder

Reachable with `d` from the history list - a standalone tool, not seeded
from any entry. `i` to type or paste text; the output pane updates live
as you type. `tab`/`shift+tab` cycles through URL, Base64, and Hex
encode/decode and HTML entity encode/decode. Base64 decode tries the
standard, URL-safe, padded, and unpadded variants in turn rather than
requiring you to know which one you're looking at. Single-transform
only - not chained/pipelined the way Burp's Decoder supports.

### Search syntax

Plain text searches headers and bodies on both sides of the exchange.
A handful of characters that are FTS5 syntax rather than literal text
(`.`, `-`, `/`, `@`, and more) are handled transparently - you don't
need to quote a domain name or an IP address for it to work.

- `example.com`, `x-forwarded-for`, `192.168.1.1` - literal text, just works
- `host:example.com`, `AND`, `OR`, `NOT` - FTS5 syntax for column
  filters and boolean queries
- `status:404` - exact status code
- `status:4xx` (also `2xx`/`3xx`/`5xx`) - status range shorthand
- `status:>=400`, `status:!=200` - status comparison operators
- `source:proxy` / `source:repeater` / `source:intruder` - where the
  request came from
- `flagged:true` / `flagged:false`
- Combine freely: `admin status:200 source:repeater`

### Repeater / Intruder - vi bindings

The request editors start in **normal mode**, not insert mode - like
real vi. Press `i` (or `a`/`I`/`A`/`o`/`O`) to start typing, `esc` to
go back to normal mode. The mode indicator (`-- NORMAL --` /
`-- INSERT --`) is always visible while one of these editors is
focused.

| Normal-mode key | Action |
|---|---|
| `h` `j` `k` `l` | left / down / up / right |
| `0` / `$` | line start / end |
| `w` / `b` | word forward / back |
| `x` | delete character |
| `i` `a` `I` `A` | insert: before cursor / after cursor / line start / line end |
| `o` / `O` | open a line below / above and insert |
| `dd` / `yy` | delete / yank the current line |
| `p` / `P` | paste below / above |
| `dw` `d$` `d0` | delete word / to end of line / to start of line |
| `gg` / `G` | top / bottom of the buffer |
| `esc` | (in insert mode) back to normal mode - never leaves the view |

This is a genuine, if intentionally scoped, modal editor: it translates
these commands into the underlying text widget's own editing primitives
rather than reimplementing cursor and line manipulation. Not
implemented: registers beyond a single yank slot, visual mode, ex
commands, macros, and counts (`3dd`, `5j`). There's no undo, because
the underlying text widget doesn't have one either.

`ctrl+r` sends (Repeater) or starts the attack (Intruder). `tab`
switches panes. In Intruder's template pane specifically, `ctrl+g`
inserts a `§` marker at the cursor if typing the character directly
isn't convenient on your keyboard/terminal.

Repeater supports multiple concurrent tabs - each open request/response
pair is independent. `]`/`[` switch to the next/previous tab, `ctrl+w`
closes the active one. All three only fire in normal mode, so they
don't interfere with typing (`[`/`]` show up in JSON bodies constantly,
and `ctrl+w` is the editor's own delete-word-backward while composing).

### Intruder

Beyond marking `§positions§` and supplying payloads, two more things are
configurable before `ctrl+r` starts the attack - both normal-mode-only
shortcuts, available from any pane:

- `c` / `e` cycle **payload processing**: an optional case rule
  (off/upper/lower) and an optional encode rule (off/URL/Base64/Hex/
  HTML), shown in the status line above the results table. Applied to
  every payload, case first then encode, right before it's substituted
  into the request - case-folding an already-encoded value would
  corrupt it (e.g. uppercasing Base64 padding), so case always runs on
  the original text first.
- `m` / `v` edit **grep-match** / **grep-extract**, each a Go regexp
  evaluated against every result's actual response bytes (same `enter`
  confirms / `esc` cancels pattern as the history list's `/` search - an
  invalid regexp is rejected with an error rather than silently
  accepted). Grep-match flags a result (a `Match` column) if the pattern
  is found anywhere in the response; grep-extract captures the first
  submatch - or the whole match, if the pattern has no capturing group -
  into an `Extract` column. Both are optional and independent; leave
  either blank to skip that check.

Both settings apply for the attack you're about to start - changing
them mid-run doesn't retroactively re-evaluate requests already sent,
matching Burp's own behavior.

### Match-and-replace rules

Press `m` from the history view. Rules match request or response
headers (`a` add, `enter`/`e` edit, `d` delete, `space` toggle
enabled). Matching is literal-substring by default, or regex if the
rule's Regex toggle is on. Rules operate on the raw header *block* as
text, not per-value substitution, so a rule can add or remove a header
entirely, not just rewrite an existing one. Currently headers only -
see `PLAN.md` for why body rules are a separate, harder problem.

## Architecture

`mitmuxd` owns the proxy listener and the SQLite database; `mitmux` is
a thin client that only ever talks to the daemon over a Unix socket
(`internal/ipc`). This split is deliberate: a web UI, a CLI scanner, or
any other client could be bolted on later without touching the proxy
engine, and the TUI restarting (or crashing) never interrupts capture.

Capture fidelity is the other core design constraint: for HTTP/1.1
traffic, request and response bytes stored in history are exactly what
was read off the wire - captured via a `net.Conn` wrapper that records
bytes as `net/http`'s own (memory-safe, battle-tested) parser consumes
them, rather than re-serializing a parsed representation. For HTTP/2,
which has no meaningful single "raw bytes" form, the stored
representation is a reconstruction, and every stored entry says which
kind it is (`request_exact`/`response_exact` in the database, "exact"
vs. "reconstructed" in the UI). Repeater and Intruder both write raw
bytes straight to the wire for the same reason - the whole point of a
Repeater is that a deliberately malformed request reaches the target
unmodified.

Package layout:

```
cmd/mitmuxd/    daemon entrypoint
cmd/mitmux/     TUI entrypoint
internal/ca/    root CA + per-host leaf certificate generation
internal/proxy/ proxy engine: HTTP/CONNECT handling, capture, Repeater, Intruder
internal/rules/ match-and-replace engine
internal/store/ SQLite storage, FTS5 search
internal/ipc/   daemon <-> client protocol (JSON over a Unix socket)
```

## Development

```sh
go build ./...
go vet ./...
gofmt -l .          # should print nothing
go test ./...
```

There's no mock traffic layer - the test suite covers pure logic
(marker parsing, search-query parsing) that's worth locking down with
real tests rather than trusting by inspection. Everything that touches
the network, the daemon, or the TUI has been verified by actually
running it against real traffic during development; see commit
messages for what was checked and how.

## Known limitations

Deliberate scope decisions, not oversights - see `PLAN.md` for the
reasoning behind each:

- Match-and-replace: headers only, no body rules yet
- Intruder: Sniper attack only (no battering ram / pitchfork / cluster
  bomb), sequential sending, capped at 1000 requests per attack
- `mitmuxd -install-ca` prints per-OS trust-store install steps; it
  never runs them for you (see Quick start above for why)
- No WebSocket interception
- No client (mutual-TLS) certificate support
- Upstream proxy chaining (`-upstream-proxy`) is HTTP CONNECT only, no
  SOCKS5
- No active or passive vulnerability scanning, no plugin system - this
  is a manual-testing tool, not a scanner