diff options
| author | srdusr <[email protected]> | 2026-02-03 00:38:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-02-03 00:38:00 +0200 |
| commit | ce6ce32469da720105258cb66e0274b2b009cd1d (patch) | |
| tree | bf1766efcdb323755e5cf3128a74b137eeb34b88 /internal/proxy/repeat.go | |
| parent | c2443f27ef5a844f045c038c7689d217d1dbf0c4 (diff) | |
| download | mitmux-ce6ce32469da720105258cb66e0274b2b009cd1d.tar.gz mitmux-ce6ce32469da720105258cb66e0274b2b009cd1d.zip | |
Intruder-equivalent: Sniper attacks with § markers
Implements build-order step 7, the last (optional) item. Scoped to
Sniper only - one payload set, one §-marked position fuzzed at a time,
others held at their base value - since that covers most real Intruder
usage; battering ram / pitchfork / cluster bomb aren't implemented.
Sequential sending, capped at 1000 generated requests as a fixed safety
limit.
internal/proxy: repeat.go's Repeat() is refactored into a shared
sendRaw(..., source) primitive so Intrude can reuse the exact same
raw-byte send/record path with source="intruder" instead of
duplicating it. intrude.go adds ParseMarkers/buildRequest (marker
parsing and payload substitution, covered by intrude_test.go - this is
fiddly byte-splicing logic, worth locking down with real tests rather
than trusting it by inspection) and Intrude(), which walks positions ×
payloads calling sendRaw and streaming each result through a callback.
internal/ipc gains a dedicated streaming "intrude" connection (same
shape as Subscribe, but blocking sends rather than drop-on-slow-
consumer - each result is the attack's actual data, not a
notification). cmd/mitmux gains an Intruder view: editable request
template (ctrl+p inserts a § marker at the cursor - typing § directly
also works, ctrl+p just doesn't require a keyboard layout that can
produce it), editable payload list, and a live results table wired to
the existing detail view (selecting a row and hitting enter opens the
full request/response for that specific attack request).
Verified live against real external traffic: a Sniper attack against
httpbin.org/status/§200§ with payloads 200/404/500 produced exactly the
three corresponding real status codes back (not a canned/local result),
confirmed the three requests landed in history tagged source="intruder"
with the § markers correctly stripped from what was actually sent, and
confirmed opening a result row's full detail from the results table.
This closes out the full build order from PLAN.md (steps 1-7).
Diffstat (limited to 'internal/proxy/repeat.go')
| -rw-r--r-- | internal/proxy/repeat.go | 28 |
1 files changed, 18 insertions, 10 deletions
diff --git a/internal/proxy/repeat.go b/internal/proxy/repeat.go index cecf481..3ee7cea 100644 --- a/internal/proxy/repeat.go +++ b/internal/proxy/repeat.go @@ -25,38 +25,46 @@ import ( // HTTP/2's binary framing, so the connection is negotiated HTTP/1.1-only // rather than letting the server pick. func (s *Server) Repeat(ctx context.Context, scheme, host string, raw []byte) (*store.Entry, error) { + return s.sendRaw(ctx, scheme, host, raw, "repeater") +} + +// sendRaw is the shared raw-byte send/record primitive behind Repeat and +// Intrude - same wire behavior (exact bytes, HTTP/1.1-only, bounded by +// upstreamTimeout), tagged with whichever source called it so history +// can tell repeater sends from intruder attack requests apart. +func (s *Server) sendRaw(ctx context.Context, scheme, host string, raw []byte, source string) (*store.Entry, error) { started := time.Now() method, path := parseRequestLine(raw) conn, err := dialForRepeat(ctx, scheme, host) if err != nil { - return s.recordRepeat(started, time.Since(started), scheme, host, method, path, raw, nil, 0, err.Error()) + return s.recordRaw(started, time.Since(started), scheme, host, method, path, raw, nil, 0, err.Error(), source) } defer conn.Close() // See the matching comment in forward(): without this, a hung // server - or a user-edited request malformed enough that nothing - // ever replies - blocks this Repeat call, and the IPC connection - // handling it, forever. + // ever replies - blocks this call, and whatever's waiting on it + // (an IPC connection, or an entire Intruder attack), forever. conn.SetDeadline(time.Now().Add(upstreamTimeout)) if _, err := conn.Write(raw); err != nil { - return s.recordRepeat(started, time.Since(started), scheme, host, method, path, raw, nil, 0, err.Error()) + return s.recordRaw(started, time.Since(started), scheme, host, method, path, raw, nil, 0, err.Error(), source) } tee := newTeeConn(conn) resp, err := http.ReadResponse(bufio.NewReader(tee), &http.Request{Method: method}) duration := time.Since(started) if err != nil { - return s.recordRepeat(started, duration, scheme, host, method, path, raw, nil, 0, err.Error()) + return s.recordRaw(started, duration, scheme, host, method, path, raw, nil, 0, err.Error(), source) } defer resp.Body.Close() io.Copy(io.Discard, resp.Body) - return s.recordRepeat(started, duration, scheme, host, method, path, raw, tee.Take(), resp.StatusCode, "") + return s.recordRaw(started, duration, scheme, host, method, path, raw, tee.Take(), resp.StatusCode, "", source) } -func (s *Server) recordRepeat(started time.Time, duration time.Duration, scheme, host, method, path string, - reqRaw, respRaw []byte, status int, errMsg string) (*store.Entry, error) { +func (s *Server) recordRaw(started time.Time, duration time.Duration, scheme, host, method, path string, + reqRaw, respRaw []byte, status int, errMsg, source string) (*store.Entry, error) { e := &store.Entry{ StartedAt: started, Duration: duration, @@ -70,12 +78,12 @@ func (s *Server) recordRepeat(started time.Time, duration time.Duration, scheme, RequestExact: true, ResponseExact: respRaw != nil, Error: errMsg, - Source: "repeater", + Source: source, } if s.store != nil { id, err := s.store.Insert(e) if err != nil { - return nil, fmt.Errorf("store repeater entry: %w", err) + return nil, fmt.Errorf("store %s entry: %w", source, err) } e.ID = id if s.OnEntry != nil { |