srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/internal/ipc/server.go
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2026-02-03 00:38:00 +0200
committersrdusr <[email protected]>2026-02-03 00:38:00 +0200
commitce6ce32469da720105258cb66e0274b2b009cd1d (patch)
treebf1766efcdb323755e5cf3128a74b137eeb34b88 /internal/ipc/server.go
parentc2443f27ef5a844f045c038c7689d217d1dbf0c4 (diff)
downloadmitmux-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.go44
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 {