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/ipc/server.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/ipc/server.go')
| -rw-r--r-- | internal/ipc/server.go | 44 |
1 files changed, 42 insertions, 2 deletions
diff --git a/internal/ipc/server.go b/internal/ipc/server.go index 1fe8d8f..50dff7f 100644 --- a/internal/ipc/server.go +++ b/internal/ipc/server.go @@ -18,6 +18,13 @@ type Repeater interface { Repeat(ctx context.Context, scheme, host string, raw []byte) (*store.Entry, error) } +// Intruder runs a Sniper attack over a §marked§ request template - +// implemented by *proxy.Server. +type Intruder interface { + Intrude(ctx context.Context, scheme, host string, template []byte, payloads []string, + onResult func(position int, payload string, entry *store.Entry, sendErr error) bool) error +} + // Hub fans out newly captured history entries to subscribed clients. type Hub struct { mu sync.Mutex @@ -62,12 +69,17 @@ type Server struct { db *store.Store hub *Hub repeater Repeater + intruder Intruder } // NewServer creates a control-protocol Server backed by db, broadcasting -// through hub and sending Repeater requests through rep. +// through hub and sending Repeater/Intruder requests through rep. func NewServer(db *store.Store, hub *Hub, rep Repeater) *Server { - return &Server{db: db, hub: hub, repeater: rep} + s := &Server{db: db, hub: hub, repeater: rep} + if in, ok := rep.(Intruder); ok { + s.intruder = in + } + return s } // Serve accepts connections on ln until it returns an error (e.g. the @@ -128,6 +140,34 @@ func (s *Server) handleConn(conn net.Conn) { } enc.Encode(Response{Type: "repeat", Detail: detailFromEntry(e)}) + case "intrude": + if s.intruder == nil { + enc.Encode(Response{Type: "error", Error: "intruder not available"}) + continue + } + err := s.intruder.Intrude(context.Background(), req.Scheme, req.Host, req.Raw, req.Payloads, + func(position int, payload string, entry *store.Entry, sendErr error) bool { + r := IntrudeResultMsg{Position: position, Payload: payload} + if sendErr != nil { + r.Error = sendErr.Error() + } + if entry != nil { + r.EntryID = entry.ID + r.StatusCode = entry.StatusCode + r.RespSize = len(entry.ResponseRaw) + r.Duration = entry.Duration + if entry.Error != "" && r.Error == "" { + r.Error = entry.Error + } + } + return enc.Encode(Response{Type: "intrude_result", IntrudeResult: &r}) == nil + }) + if err != nil { + enc.Encode(Response{Type: "error", Error: err.Error()}) + continue + } + enc.Encode(Response{Type: "intrude_done"}) + case "rules_list": rs, err := s.db.ListRules() if err != nil { |