diff options
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/mitmux/main.go | 106 |
1 files changed, 90 insertions, 16 deletions
diff --git a/cmd/mitmux/main.go b/cmd/mitmux/main.go index d11d18b..304d3fc 100644 --- a/cmd/mitmux/main.go +++ b/cmd/mitmux/main.go @@ -20,6 +20,7 @@ import ( "mitmux/internal/ca" "mitmux/internal/ipc" + "mitmux/internal/proxy" "mitmux/internal/rules" "mitmux/internal/scope" "mitmux/internal/store" @@ -199,6 +200,7 @@ type model struct { intruderResults table.Model intruderRows []ipc.IntrudeResultMsg intruderFocus intruderFocus + intruderMode proxy.AttackMode intruderRunning bool intruderCount int intruderCh <-chan ipc.IntrudeResultMsg @@ -331,12 +333,12 @@ func newModel(client *ipc.Client, subCh <-chan store.Summary, socketPath string) itmpl.ta.ShowLineNumbers = false ipayloads := newViTextarea() - ipayloads.ta.Placeholder = "payloads, one per line" + ipayloads.ta.Placeholder = "payloads, one per line (pitchfork/cluster bomb: separate one set per position with a line of ---)" ipayloads.ta.ShowLineNumbers = false iresultsCols := []table.Column{ - {Title: "Pos", Width: 4}, - {Title: "Payload", Width: 20}, + {Title: "#", Width: 4}, + {Title: "Payload(s)", Width: 24}, {Title: "Status", Width: 6}, {Title: "Size", Width: 8}, {Title: "Time", Width: 8}, @@ -708,6 +710,7 @@ func (m *model) enterIntruder(d *ipc.EntryDetail) { m.intruderRows = nil setTableRows(&m.intruderResults, nil) m.intruderFocus = focusTemplate + m.intruderMode = proxy.Sniper m.intruderRunning = false m.intruderCount = 0 m.payloadCase = payloadCaseNone @@ -718,7 +721,7 @@ func (m *model) enterIntruder(d *ipc.EntryDetail) { m.grepMatchInput.SetValue("") m.grepExtractInput.SetValue("") m.mode = viewIntruder - m.statusMsg = "wrap positions to fuzz in § (ctrl+g), fill payloads, ctrl+r to start" + m.statusMsg = "wrap positions to fuzz in § (ctrl+g), fill payloads, a to change attack mode, ctrl+r to start" } type intrudeStartedMsg struct { @@ -732,20 +735,58 @@ type intrudeResultMsg struct { ok bool } +// parsePayloadSets turns the Payloads textarea into one or more payload +// sets. Sniper and BatteringRam only ever need one shared set, so every +// non-empty line is a payload. Pitchfork and ClusterBomb are inherently +// per-position, so the same textarea instead holds several sets separated +// by a line containing exactly "---", in position order - proxy.Intrude +// validates the count matches the template's marked positions. +func parsePayloadSets(text string, mode proxy.AttackMode, caseRule payloadCaseRule, encodeRule payloadEncodeRule) [][]string { + lines := strings.Split(text, "\n") + if mode != proxy.Pitchfork && mode != proxy.ClusterBomb { + var set []string + for _, line := range lines { + if line != "" { + set = append(set, applyPayloadRules(line, caseRule, encodeRule)) + } + } + if set == nil { + return nil + } + return [][]string{set} + } + + var sets [][]string + var cur []string + flush := func() { + if cur != nil { + sets = append(sets, cur) + cur = nil + } + } + for _, line := range lines { + if strings.TrimSpace(line) == "---" { + flush() + continue + } + if line != "" { + cur = append(cur, applyPayloadRules(line, caseRule, encodeRule)) + } + } + flush() + return sets +} + func (m *model) startIntrude() tea.Cmd { scheme, host := m.intruderScheme, m.intruderHost // Same CRLF restoration as Repeater, same trade-off - see sendRepeat. template := []byte(strings.ReplaceAll(m.intruderTemplate.Value(), "\n", "\r\n")) - var payloads []string - for _, line := range strings.Split(m.intruderPayloads.Value(), "\n") { - if line != "" { - payloads = append(payloads, applyPayloadRules(line, m.payloadCase, m.payloadEncode)) - } - } + mode := m.intruderMode + payloadSets := parsePayloadSets(m.intruderPayloads.Value(), mode, m.payloadCase, m.payloadEncode) path := m.socketPath grepMatch, grepExtract := m.grepMatchSrc, m.grepExtractSrc return func() tea.Msg { - ch, closeFn, err := ipc.Intrude(path, scheme, host, template, payloads, grepMatch, grepExtract) + ch, closeFn, err := ipc.Intrude(path, scheme, host, template, mode, payloadSets, grepMatch, grepExtract) return intrudeStartedMsg{ch: ch, close: closeFn, err: err} } } @@ -1752,6 +1793,11 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.intruderTemplate.InsertRune('§') } return m, nil + case "a": + if !editing && !m.intruderRunning { + m.intruderMode = nextAttackMode(m.intruderMode) + return m, nil + } case "c": if !editing { m.payloadCase = (m.payloadCase + 1) % payloadCaseRule(len(payloadCaseNames)) @@ -2339,6 +2385,34 @@ func scopeRowsFor(rs []scope.Rule) []table.Row { return rows } +// nextAttackMode cycles Sniper -> BatteringRam -> Pitchfork -> ClusterBomb +// -> Sniper. +func nextAttackMode(mode proxy.AttackMode) proxy.AttackMode { + switch mode { + case proxy.Sniper: + return proxy.BatteringRam + case proxy.BatteringRam: + return proxy.Pitchfork + case proxy.Pitchfork: + return proxy.ClusterBomb + default: + return proxy.Sniper + } +} + +func attackModeLabel(mode proxy.AttackMode) string { + switch mode { + case proxy.BatteringRam: + return "battering ram" + case proxy.Pitchfork: + return "pitchfork" + case proxy.ClusterBomb: + return "cluster bomb" + default: + return "sniper" + } +} + func (m *model) intruderView() string { var b strings.Builder title := fmt.Sprintf(" intruder - %s://%s ", sanitizeLine(m.intruderScheme), sanitizeLine(m.intruderHost)) @@ -2382,8 +2456,8 @@ func (m *model) intruderView() string { if grepExtract == "" { grepExtract = "(none)" } - b.WriteString(fmt.Sprintf("payload rules: case=%s encode=%s · grep-match: %s · grep-extract: %s", - payloadCaseNames[m.payloadCase], payloadEncodeNames[m.payloadEncode], grepMatch, grepExtract)) + b.WriteString(fmt.Sprintf("attack: %s · payload rules: case=%s encode=%s · grep-match: %s · grep-extract: %s", + attackModeLabel(m.intruderMode), payloadCaseNames[m.payloadCase], payloadEncodeNames[m.payloadEncode], grepMatch, grepExtract)) b.WriteString("\n") } @@ -2404,7 +2478,7 @@ func (m *model) intruderView() string { if m.grepEditing != 0 { b.WriteString(helpStyle.Render("enter confirm · esc cancel · ctrl+c quit")) } else { - b.WriteString(helpStyle.Render("i to edit (vi keys) · tab switch pane · ctrl+g insert § · c/e cycle case/encode · m/v edit grep-match/extract · ctrl+r start · enter (results) view · esc back/stop · ? help · ctrl+c quit")) + b.WriteString(helpStyle.Render("i to edit (vi keys) · tab switch pane · ctrl+g insert § · a cycle attack mode · c/e cycle case/encode · m/v edit grep-match/extract · ctrl+r start · enter (results) view · esc back/stop · ? help · ctrl+c quit")) } return b.String() } @@ -2421,8 +2495,8 @@ func intrudeRowsFor(rs []ipc.IntrudeResultMsg) []table.Row { match = "✓" } rows[i] = table.Row{ - fmt.Sprintf("%d", r.Position), - sanitizeLine(r.Payload), + fmt.Sprintf("%d", r.Iteration), + sanitizeLine(strings.Join(r.Values, " | ")), status, humanBytes(r.RespSize), r.Duration.Round(time.Millisecond).String(), |