srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/internal/ipc
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2026-06-11 00:37:00 +0200
committersrdusr <[email protected]>2026-06-11 00:37:00 +0200
commit6114567258bcad0517a0d881168711aaacdba5d5 (patch)
tree6bb9f3af9cfe0c857140a51f7992e3853cb2a236 /internal/ipc
parenta2362fc08c31b23fb971279f8b160555123ad2f6 (diff)
downloadmitmux-6114567258bcad0517a0d881168711aaacdba5d5.tar.gz
mitmux-6114567258bcad0517a0d881168711aaacdba5d5.zip
Intruder: Battering ram, Pitchfork, and Cluster bomb attack modes
Generalizes Intrude beyond Sniper to all four of Burp's attack modes (proxy.AttackMode). Sniper and Battering ram only ever need one shared payload set; Pitchfork and Cluster bomb are inherently per-position, so they take one payload set per §marked§ position instead. Request-set generation (intrudeValues) is pure and side-effect free, so the total request count is validated against the existing 1000 cap before anything is dispatched - Cluster bomb's product is checked incrementally, one payload set at a time, so a pathological product bails out before ever trying to enumerate it. This also makes the combinatorics unit-testable without a live target. IntrudeResultMsg now reports Values (one substitution per marked position, in order) instead of a single Position/Payload pair, since three of the four modes touch multiple positions per request. TUI: `a` cycles the attack mode. Pitchfork/Cluster bomb reuse the existing single Payloads pane rather than a new multi-widget editor - sets are separated by a `---` delimiter line, in position order. Verified live against a real daemon: all four modes produce the expected substitution values and request counts, and pitchfork correctly rejects a payload-set count that doesn't match the template's marked positions.
Diffstat (limited to 'internal/ipc')
-rw-r--r--internal/ipc/ipc.go48
-rw-r--r--internal/ipc/server.go14
2 files changed, 37 insertions, 25 deletions
diff --git a/internal/ipc/ipc.go b/internal/ipc/ipc.go
index 89a8343..d581313 100644
--- a/internal/ipc/ipc.go
+++ b/internal/ipc/ipc.go
@@ -12,6 +12,7 @@ import (
"sync"
"time"
+ "mitmux/internal/proxy"
"mitmux/internal/rules"
"mitmux/internal/scope"
"mitmux/internal/store"
@@ -35,9 +36,13 @@ type Request struct {
Host string `json:"host,omitempty"`
Raw []byte `json:"raw,omitempty"`
- // For "intrude": the payload set, applied to each marked position in
- // turn (Sniper-style - see proxy.Intrude).
- Payloads []string `json:"payloads,omitempty"`
+ // For "intrude": the attack mode (proxy.Sniper and so on - empty
+ // defaults to Sniper) and its payload sets. Sniper and BatteringRam
+ // only ever use PayloadSets[0] (one shared set); Pitchfork and
+ // ClusterBomb require exactly one set per §marked§ position, in
+ // order - see proxy.Intrude.
+ Mode proxy.AttackMode `json:"mode,omitempty"`
+ PayloadSets [][]string `json:"payload_sets,omitempty"`
// For "intrude": optional Go regexps evaluated against each result's
// response bytes. GrepMatch flags whether it matched at all;
@@ -118,10 +123,14 @@ type StatusMsg struct {
HistoryCount int64 `json:"history_count"`
}
-// IntrudeResultMsg is one completed Intruder attack request.
+// IntrudeResultMsg is one completed Intruder attack request. Values holds
+// what was substituted into each §marked§ position for this request, in
+// position order - for Sniper, every entry but the one fuzzed position
+// equals that position's base value; for the other three modes every
+// entry is an actual payload.
type IntrudeResultMsg struct {
- Position int `json:"position"`
- Payload string `json:"payload"`
+ Iteration int `json:"iteration"`
+ Values []string `json:"values"`
EntryID int64 `json:"entry_id"`
StatusCode int `json:"status_code"`
RespSize int `json:"resp_size"`
@@ -468,23 +477,24 @@ func Subscribe(path string) (<-chan store.Summary, func() error, error) {
return ch, conn.Close, nil
}
-// Intrude starts a Sniper attack (see proxy.Intrude): template must
-// contain at least one §marked§ position, fuzzed in turn through
-// payloads. grepMatch/grepExtract are optional Go regexps evaluated
-// server-side against each result's response bytes (empty string
-// disables either check) - see IntrudeResultMsg. Unlike Subscribe's live
-// feed, no result is ever dropped for a slow consumer - each one is the
-// attack's actual data, not a notification with the real thing
-// recoverable elsewhere. A setup error (bad markers, empty payload set,
-// too many requests, an unparseable grep regexp) is returned directly
-// rather than through the channel. The returned channel closes when the
-// attack finishes or the connection is closed early.
-func Intrude(path, scheme, host string, template []byte, payloads []string, grepMatch, grepExtract string) (<-chan IntrudeResultMsg, func() error, error) {
+// Intrude starts an attack (see proxy.Intrude and proxy.AttackMode):
+// template must contain at least one §marked§ position. mode selects how
+// payloadSets combine across positions; "" defaults to Sniper.
+// grepMatch/grepExtract are optional Go regexps evaluated server-side
+// against each result's response bytes (empty string disables either
+// check) - see IntrudeResultMsg. Unlike Subscribe's live feed, no result
+// is ever dropped for a slow consumer - each one is the attack's actual
+// data, not a notification with the real thing recoverable elsewhere. A
+// setup error (bad markers, empty payload set, too many requests, an
+// unparseable grep regexp) is returned directly rather than through the
+// channel. The returned channel closes when the attack finishes or the
+// connection is closed early.
+func Intrude(path, scheme, host string, template []byte, mode proxy.AttackMode, payloadSets [][]string, grepMatch, grepExtract string) (<-chan IntrudeResultMsg, func() error, error) {
conn, err := net.Dial("unix", path)
if err != nil {
return nil, nil, fmt.Errorf("dial %s: %w", path, err)
}
- req := Request{Type: "intrude", Scheme: scheme, Host: host, Raw: template, Payloads: payloads, GrepMatch: grepMatch, GrepExtract: grepExtract}
+ req := Request{Type: "intrude", Scheme: scheme, Host: host, Raw: template, Mode: mode, PayloadSets: payloadSets, GrepMatch: grepMatch, GrepExtract: grepExtract}
if err := json.NewEncoder(conn).Encode(req); err != nil {
conn.Close()
return nil, nil, err
diff --git a/internal/ipc/server.go b/internal/ipc/server.go
index c83254e..c890a54 100644
--- a/internal/ipc/server.go
+++ b/internal/ipc/server.go
@@ -9,6 +9,7 @@ import (
"regexp"
"sync"
+ "mitmux/internal/proxy"
"mitmux/internal/rules"
"mitmux/internal/scope"
"mitmux/internal/store"
@@ -20,11 +21,12 @@ 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 -
+// Intruder runs an attack (Sniper, Battering ram, Pitchfork, or Cluster
+// bomb - see proxy.AttackMode) 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
+ Intrude(ctx context.Context, scheme, host string, template []byte, mode proxy.AttackMode, payloadSets [][]string,
+ onResult func(iteration int, values []string, entry *store.Entry, sendErr error) bool) error
}
// Hub fans out newly captured history entries to subscribed clients.
@@ -174,9 +176,9 @@ func (s *Server) handleConn(conn net.Conn) {
}
grepExtractRe = re
}
- 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}
+ err := s.intruder.Intrude(context.Background(), req.Scheme, req.Host, req.Raw, req.Mode, req.PayloadSets,
+ func(iteration int, values []string, entry *store.Entry, sendErr error) bool {
+ r := IntrudeResultMsg{Iteration: iteration, Values: values}
if sendErr != nil {
r.Error = sendErr.Error()
}