srdusr
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--PLAN.md28
-rw-r--r--README.md33
-rw-r--r--cmd/mitmux/main.go161
-rw-r--r--cmd/mitmux/payload_rules.go61
-rw-r--r--cmd/mitmux/payload_rules_test.go31
-rw-r--r--internal/ipc/ipc.go46
-rw-r--r--internal/ipc/server.go30
7 files changed, 362 insertions, 28 deletions
diff --git a/PLAN.md b/PLAN.md
index 2dac629..d874f69 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -124,10 +124,30 @@ the correct tab (send results carry the tab index they belong to), and
the status line/response pane it's shown in only updates live if that
tab is still the one on screen.
-Still open from "worth considering": Intruder payload processing
-(encoding/case rules) and grep-match/grep-extract on results, CA install
-UX per OS, multiple proxy listeners and upstream proxy chaining. None of
-these are started yet.
+Shipped since: Intruder payload processing and grep-match/grep-extract.
+Payload processing - an optional case rule (upper/lower) and an optional
+encode rule (URL/Base64/Hex/HTML), cycled with `c`/`e` - is applied
+client-side to each payload line before it ever crosses the IPC socket,
+case first then encode (encoding an already-case-folded value is safe;
+the reverse would corrupt e.g. Base64 padding), since it's a pure string
+transform with no proxy-side state involved and reuses the Decoder's own
+`urlEncodeAll`. Grep-match/grep-extract are optional Go regexps
+(`m`/`v` to edit, both gated to normal mode and both revert-on-esc /
+validate-on-enter the same way the history list's `/` search box
+works), evaluated server-side in `internal/ipc/server.go`'s "intrude"
+handler against each result's actual response bytes - chosen over a
+client-side implementation because the daemon already has `entry.
+ResponseRaw` in hand right where the result is built, and Burp's own
+grep options work the same way (matched against the real response, not
+a client-refetched copy). Grep-match flags a result (shown as a Match
+column); grep-extract captures the first submatch (or the whole match
+if the pattern has no capturing group) into an Extract column. Both are
+configured once before `ctrl+r` starts an attack and apply for that run
+only - matching Burp, which doesn't retroactively re-grep already-fired
+requests if you change the options mid-attack.
+
+Still open from "worth considering": CA install UX per OS, multiple
+proxy listeners and upstream proxy chaining. Neither is started yet.
Skipped deliberately (from the research, matches this tool's stated
scope): active/passive vulnerability scanning, plugin marketplace,
diff --git a/README.md b/README.md
index c91e455..67536c7 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,11 @@ list of what's deliberately not implemented (and why), see
- **Intruder** (Sniper only): mark positions in a request template
with `§markers§`, supply a payload list, fuzz one position at a time
against a shared payload set. Results land in the same history table
- as everything else, searchable the same way.
+ as everything else, searchable the same way. Payload processing
+ (optional case and encode rules, applied to every payload before it's
+ sent) and grep-match/grep-extract (flag or pull text out of each
+ result's response with a regexp) are both configurable before starting
+ an attack - see [Intruder](#intruder) below.
- **Match-and-replace**: header rewrite rules (add, remove, or modify)
for requests and/or responses, applied live as traffic passes
through. History still shows what was actually sent/received on each
@@ -218,6 +222,33 @@ closes the active one. All three only fire in normal mode, so they
don't interfere with typing (`[`/`]` show up in JSON bodies constantly,
and `ctrl+w` is the editor's own delete-word-backward while composing).
+### Intruder
+
+Beyond marking `§positions§` and supplying payloads, two more things are
+configurable before `ctrl+r` starts the attack - both normal-mode-only
+shortcuts, available from any pane:
+
+- `c` / `e` cycle **payload processing**: an optional case rule
+ (off/upper/lower) and an optional encode rule (off/URL/Base64/Hex/
+ HTML), shown in the status line above the results table. Applied to
+ every payload, case first then encode, right before it's substituted
+ into the request - case-folding an already-encoded value would
+ corrupt it (e.g. uppercasing Base64 padding), so case always runs on
+ the original text first.
+- `m` / `v` edit **grep-match** / **grep-extract**, each a Go regexp
+ evaluated against every result's actual response bytes (same `enter`
+ confirms / `esc` cancels pattern as the history list's `/` search - an
+ invalid regexp is rejected with an error rather than silently
+ accepted). Grep-match flags a result (a `Match` column) if the pattern
+ is found anywhere in the response; grep-extract captures the first
+ submatch - or the whole match, if the pattern has no capturing group -
+ into an `Extract` column. Both are optional and independent; leave
+ either blank to skip that check.
+
+Both settings apply for the attack you're about to start - changing
+them mid-run doesn't retroactively re-evaluate requests already sent,
+matching Burp's own behavior.
+
### Match-and-replace rules
Press `m` from the history view. Rules match request or response
diff --git a/cmd/mitmux/main.go b/cmd/mitmux/main.go
index 31491e9..1d6d3c5 100644
--- a/cmd/mitmux/main.go
+++ b/cmd/mitmux/main.go
@@ -8,6 +8,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "regexp"
"strings"
"time"
@@ -171,6 +172,24 @@ type model struct {
intruderCh <-chan ipc.IntrudeResultMsg
intruderClose func() error
+ // Payload processing: case/encode rules applied to each payload
+ // line client-side before it's sent (see payload_rules.go).
+ payloadCase payloadCaseRule
+ payloadEncode payloadEncodeRule
+
+ // Grep-match/grep-extract: optional regexps evaluated server-side
+ // against each result's response bytes (see proxy request "intrude"
+ // handling in internal/ipc/server.go). grepEditing mirrors the
+ // searching/searchInput pattern used by the history list's '/':
+ // 0 = not editing, 1 = editing the match pattern, 2 = editing the
+ // extract pattern; the *Src fields hold the last-confirmed pattern,
+ // the *Input fields are the live edit buffer.
+ grepEditing int
+ grepMatchSrc string
+ grepExtractSrc string
+ grepMatchInput textinput.Model
+ grepExtractInput textinput.Model
+
daemonStatus *ipc.StatusMsg
prevMode viewMode // for the ? help screen's "esc back" target
@@ -242,11 +261,13 @@ func newModel(client *ipc.Client, subCh <-chan store.Summary, socketPath string)
iresultsCols := []table.Column{
{Title: "Pos", Width: 4},
- {Title: "Payload", Width: 24},
+ {Title: "Payload", Width: 20},
{Title: "Status", Width: 6},
- {Title: "Size", Width: 10},
+ {Title: "Size", Width: 8},
{Title: "Time", Width: 8},
- {Title: "Error", Width: 20},
+ {Title: "Match", Width: 5},
+ {Title: "Extract", Width: 18},
+ {Title: "Error", Width: 14},
}
iresults := table.New(table.WithColumns(iresultsCols), table.WithFocused(true))
iresults.SetStyles(st)
@@ -255,6 +276,11 @@ func newModel(client *ipc.Client, subCh <-chan store.Summary, socketPath string)
din.ta.Placeholder = "text to encode/decode"
din.ta.ShowLineNumbers = false
+ gmIn := textinput.New()
+ gmIn.Placeholder = "regexp - flags a result if it matches the response"
+ geIn := textinput.New()
+ geIn.Placeholder = "regexp - extracts first capture group (or whole match) from the response"
+
return &model{
client: client,
subCh: subCh,
@@ -271,6 +297,8 @@ func newModel(client *ipc.Client, subCh <-chan store.Summary, socketPath string)
intruderTemplate: itmpl,
intruderPayloads: ipayloads,
intruderResults: iresults,
+ grepMatchInput: gmIn,
+ grepExtractInput: geIn,
decoderInput: din,
}
}
@@ -477,6 +505,13 @@ func (m *model) enterIntruder(d *ipc.EntryDetail) {
m.intruderFocus = focusTemplate
m.intruderRunning = false
m.intruderCount = 0
+ m.payloadCase = payloadCaseNone
+ m.payloadEncode = payloadEncodeNone
+ m.grepEditing = 0
+ m.grepMatchSrc = ""
+ m.grepExtractSrc = ""
+ m.grepMatchInput.SetValue("")
+ m.grepExtractInput.SetValue("")
m.mode = viewIntruder
m.statusMsg = "wrap positions to fuzz in § (ctrl+g), fill payloads, ctrl+r to start"
}
@@ -499,12 +534,13 @@ func (m *model) startIntrude() tea.Cmd {
var payloads []string
for _, line := range strings.Split(m.intruderPayloads.Value(), "\n") {
if line != "" {
- payloads = append(payloads, line)
+ payloads = append(payloads, applyPayloadRules(line, 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)
+ ch, closeFn, err := ipc.Intrude(path, scheme, host, template, payloads, grepMatch, grepExtract)
return intrudeStartedMsg{ch: ch, close: closeFn, err: err}
}
}
@@ -649,14 +685,18 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.ruleMatch.Width = formWidth
m.ruleReplace.Width = formWidth
- itmplHeight := (h - 8) / 3
+ // h-9 rather than h-8: one extra line reserved for the payload
+ // rules / grep-match-extract status line in intruderView.
+ itmplHeight := (h - 9) / 3
ipayloadsHeight := itmplHeight
m.intruderTemplate.SetWidth(msg.Width)
m.intruderTemplate.SetHeight(itmplHeight)
m.intruderPayloads.SetWidth(msg.Width)
m.intruderPayloads.SetHeight(ipayloadsHeight)
m.intruderResults.SetWidth(msg.Width)
- m.intruderResults.SetHeight(h - 8 - itmplHeight - ipayloadsHeight)
+ m.intruderResults.SetHeight(h - 9 - itmplHeight - ipayloadsHeight)
+ m.grepMatchInput.Width = msg.Width - 2
+ m.grepExtractInput.Width = msg.Width - 2
return m, nil
case listLoadedMsg:
@@ -1080,6 +1120,51 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd
case viewIntruder:
+ // Editing a grep pattern is a modal overlay on top of the
+ // normal template/payloads/results panes, same pattern as
+ // the history list's '/' search: enter commits (after
+ // validating the regexp compiles), esc discards the edit and
+ // reverts to the last-confirmed pattern.
+ if m.grepEditing != 0 {
+ input := &m.grepMatchInput
+ if m.grepEditing == 2 {
+ input = &m.grepExtractInput
+ }
+ switch msg.String() {
+ case "enter":
+ v := input.Value()
+ if v != "" {
+ if _, err := regexp.Compile(v); err != nil {
+ m.statusMsg = "invalid regexp: " + err.Error()
+ return m, nil
+ }
+ }
+ if m.grepEditing == 1 {
+ m.grepMatchSrc = v
+ } else {
+ m.grepExtractSrc = v
+ }
+ m.grepEditing = 0
+ input.Blur()
+ m.statusMsg = ""
+ return m, nil
+ case "esc":
+ if m.grepEditing == 1 {
+ input.SetValue(m.grepMatchSrc)
+ } else {
+ input.SetValue(m.grepExtractSrc)
+ }
+ m.grepEditing = 0
+ input.Blur()
+ return m, nil
+ case "ctrl+c":
+ return m, tea.Quit
+ }
+ var cmd tea.Cmd
+ *input, cmd = input.Update(msg)
+ return m, cmd
+ }
+
editing := (m.intruderFocus == focusTemplate && m.intruderTemplate.Mode() == viInsert) ||
(m.intruderFocus == focusPayloads && m.intruderPayloads.Mode() == viInsert)
switch msg.String() {
@@ -1116,6 +1201,30 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.intruderTemplate.InsertRune('§')
}
return m, nil
+ case "c":
+ if !editing {
+ m.payloadCase = (m.payloadCase + 1) % payloadCaseRule(len(payloadCaseNames))
+ return m, nil
+ }
+ case "e":
+ if !editing {
+ m.payloadEncode = (m.payloadEncode + 1) % payloadEncodeRule(len(payloadEncodeNames))
+ return m, nil
+ }
+ case "m":
+ if !editing {
+ m.grepEditing = 1
+ m.grepMatchInput.SetValue(m.grepMatchSrc)
+ m.grepMatchInput.CursorEnd()
+ return m, m.grepMatchInput.Focus()
+ }
+ case "v":
+ if !editing {
+ m.grepEditing = 2
+ m.grepExtractInput.SetValue(m.grepExtractSrc)
+ m.grepExtractInput.CursorEnd()
+ return m, m.grepExtractInput.Focus()
+ }
case "tab":
m.intruderFocus = (m.intruderFocus + 1) % 3
if m.intruderFocus == focusTemplate {
@@ -1344,6 +1453,8 @@ func (m *model) helpView() string {
"ctrl+g (Intruder template only) insert a § marker at cursor",
"]/[ (Repeater only) next/previous tab",
"ctrl+w (Repeater only) close current tab",
+ "c / e (Intruder only) cycle payload case / encode rule",
+ "m / v (Intruder only) edit grep-match / grep-extract regexp",
)
section("Rules",
"a add rule enter / e edit selected",
@@ -1588,6 +1699,30 @@ func (m *model) intruderView() string {
b.WriteString("\n")
b.WriteString(m.intruderPayloads.View())
b.WriteString("\n")
+
+ switch m.grepEditing {
+ case 1:
+ b.WriteString("grep-match (regexp): ")
+ b.WriteString(m.grepMatchInput.View())
+ b.WriteString("\n")
+ case 2:
+ b.WriteString("grep-extract (regexp): ")
+ b.WriteString(m.grepExtractInput.View())
+ b.WriteString("\n")
+ default:
+ grepMatch := m.grepMatchSrc
+ if grepMatch == "" {
+ grepMatch = "(none)"
+ }
+ grepExtract := m.grepExtractSrc
+ 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("\n")
+ }
+
b.WriteString(m.intruderResults.View())
b.WriteString("\n")
@@ -1602,7 +1737,11 @@ func (m *model) intruderView() string {
b.WriteString(statusStyle.Render(m.statusMsg))
b.WriteString("\n")
}
- b.WriteString(helpStyle.Render("i to edit (vi keys) · tab switch pane · ctrl+g insert § · ctrl+r start · enter (results) view · esc back/stop · ? help · ctrl+c quit"))
+ 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"))
+ }
return b.String()
}
@@ -1613,12 +1752,18 @@ func intrudeRowsFor(rs []ipc.IntrudeResultMsg) []table.Row {
if r.StatusCode == 0 {
status = "ERR"
}
+ match := ""
+ if r.GrepMatch {
+ match = "✓"
+ }
rows[i] = table.Row{
fmt.Sprintf("%d", r.Position),
r.Payload,
status,
humanBytes(r.RespSize),
r.Duration.Round(time.Millisecond).String(),
+ match,
+ r.GrepExtract,
r.Error,
}
}
diff --git a/cmd/mitmux/payload_rules.go b/cmd/mitmux/payload_rules.go
new file mode 100644
index 0000000..6302f54
--- /dev/null
+++ b/cmd/mitmux/payload_rules.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "encoding/base64"
+ "encoding/hex"
+ "html"
+ "strings"
+)
+
+// payloadCaseRule is an optional case transform applied to each Intruder
+// payload before it's sent - Burp calls this class of feature "payload
+// processing". Applied client-side, before the payload list ever crosses
+// the IPC socket, since it's a pure string transform with no proxy-side
+// state involved.
+type payloadCaseRule int
+
+const (
+ payloadCaseNone payloadCaseRule = iota
+ payloadCaseUpper
+ payloadCaseLower
+)
+
+var payloadCaseNames = []string{"off", "upper", "lower"}
+
+// payloadEncodeRule is an optional encoding applied to each payload after
+// the case rule, right before it's substituted into the request template.
+type payloadEncodeRule int
+
+const (
+ payloadEncodeNone payloadEncodeRule = iota
+ payloadEncodeURL
+ payloadEncodeBase64
+ payloadEncodeHex
+ payloadEncodeHTML
+)
+
+var payloadEncodeNames = []string{"off", "URL", "Base64", "Hex", "HTML"}
+
+// applyPayloadRules runs the case rule, then the encode rule, over one raw
+// payload line. Order matters: case-folding an already-encoded payload
+// (e.g. uppercasing "aGVsbG8=") would corrupt it, so case always runs
+// first, against the original text.
+func applyPayloadRules(s string, c payloadCaseRule, e payloadEncodeRule) string {
+ switch c {
+ case payloadCaseUpper:
+ s = strings.ToUpper(s)
+ case payloadCaseLower:
+ s = strings.ToLower(s)
+ }
+ switch e {
+ case payloadEncodeURL:
+ s = urlEncodeAll(s)
+ case payloadEncodeBase64:
+ s = base64.StdEncoding.EncodeToString([]byte(s))
+ case payloadEncodeHex:
+ s = hex.EncodeToString([]byte(s))
+ case payloadEncodeHTML:
+ s = html.EscapeString(s)
+ }
+ return s
+}
diff --git a/cmd/mitmux/payload_rules_test.go b/cmd/mitmux/payload_rules_test.go
new file mode 100644
index 0000000..466f14f
--- /dev/null
+++ b/cmd/mitmux/payload_rules_test.go
@@ -0,0 +1,31 @@
+package main
+
+import "testing"
+
+func TestApplyPayloadRules(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ c payloadCaseRule
+ e payloadEncodeRule
+ want string
+ }{
+ {"no rules", "Hello World", payloadCaseNone, payloadEncodeNone, "Hello World"},
+ {"upper only", "Hello World", payloadCaseUpper, payloadEncodeNone, "HELLO WORLD"},
+ {"lower only", "Hello World", payloadCaseLower, payloadEncodeNone, "hello world"},
+ {"url encode only", "a b/c", payloadCaseNone, payloadEncodeURL, "a%20b%2Fc"},
+ {"base64 encode only", "hello", payloadCaseNone, payloadEncodeBase64, "aGVsbG8="},
+ {"hex encode only", "hi", payloadCaseNone, payloadEncodeHex, "6869"},
+ {"html encode only", "<x>", payloadCaseNone, payloadEncodeHTML, "&lt;x&gt;"},
+ {"upper then url encode", "a b", payloadCaseUpper, payloadEncodeURL, "A%20B"},
+ {"lower then base64 - case runs before encode", "HELLO", payloadCaseLower, payloadEncodeBase64, "aGVsbG8="},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := applyPayloadRules(tt.input, tt.c, tt.e)
+ if got != tt.want {
+ t.Errorf("applyPayloadRules(%q, %v, %v) = %q, want %q", tt.input, tt.c, tt.e, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/internal/ipc/ipc.go b/internal/ipc/ipc.go
index 7d81473..270b8e4 100644
--- a/internal/ipc/ipc.go
+++ b/internal/ipc/ipc.go
@@ -38,6 +38,16 @@ type Request struct {
// turn (Sniper-style - see proxy.Intrude).
Payloads []string `json:"payloads,omitempty"`
+ // For "intrude": optional Go regexps evaluated against each result's
+ // response bytes. GrepMatch flags whether it matched at all;
+ // GrepExtract additionally captures text (first submatch if the
+ // pattern has a capturing group, else the whole match) into the
+ // result. Either or both may be empty to skip that check. Compiled
+ // and validated once, server-side, before the attack starts - a bad
+ // pattern fails the same way a bad marker or empty payload set does.
+ GrepMatch string `json:"grep_match,omitempty"`
+ GrepExtract string `json:"grep_extract,omitempty"`
+
// For "rules_save": add (Rule.ID == 0) or update (Rule.ID != 0) a
// match-and-replace rule. For "rules_delete"/"rules_toggle": RuleID
// (and RuleEnabled for toggle) identify the target.
@@ -72,13 +82,15 @@ type StatusMsg struct {
// IntrudeResultMsg is one completed Intruder attack request.
type IntrudeResultMsg struct {
- Position int `json:"position"`
- Payload string `json:"payload"`
- EntryID int64 `json:"entry_id"`
- StatusCode int `json:"status_code"`
- RespSize int `json:"resp_size"`
- Duration time.Duration `json:"duration"`
- Error string `json:"error,omitempty"`
+ Position int `json:"position"`
+ Payload string `json:"payload"`
+ EntryID int64 `json:"entry_id"`
+ StatusCode int `json:"status_code"`
+ RespSize int `json:"resp_size"`
+ Duration time.Duration `json:"duration"`
+ Error string `json:"error,omitempty"`
+ GrepMatch bool `json:"grep_match,omitempty"`
+ GrepExtract string `json:"grep_extract,omitempty"`
}
// EntryDetail is a full history entry, raw bytes included.
@@ -307,18 +319,22 @@ func Subscribe(path string) (<-chan store.Summary, func() error, error) {
// Intrude starts a Sniper attack (see proxy.Intrude): template must
// contain at least one §marked§ position, fuzzed in turn through
-// payloads. 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) 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) (<-chan IntrudeResultMsg, func() error, error) {
+// 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) {
conn, err := net.Dial("unix", path)
if err != nil {
return nil, nil, fmt.Errorf("dial %s: %w", path, err)
}
- if err := json.NewEncoder(conn).Encode(Request{Type: "intrude", Scheme: scheme, Host: host, Raw: template, Payloads: payloads}); err != nil {
+ req := Request{Type: "intrude", Scheme: scheme, Host: host, Raw: template, Payloads: payloads, 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 28279a7..9602912 100644
--- a/internal/ipc/server.go
+++ b/internal/ipc/server.go
@@ -6,6 +6,7 @@ import (
"io"
"log"
"net"
+ "regexp"
"sync"
"mitmux/internal/rules"
@@ -155,6 +156,23 @@ func (s *Server) handleConn(conn net.Conn) {
enc.Encode(Response{Type: "error", Error: "intruder not available"})
continue
}
+ var grepMatchRe, grepExtractRe *regexp.Regexp
+ if req.GrepMatch != "" {
+ re, err := regexp.Compile(req.GrepMatch)
+ if err != nil {
+ enc.Encode(Response{Type: "error", Error: "grep-match: " + err.Error()})
+ continue
+ }
+ grepMatchRe = re
+ }
+ if req.GrepExtract != "" {
+ re, err := regexp.Compile(req.GrepExtract)
+ if err != nil {
+ enc.Encode(Response{Type: "error", Error: "grep-extract: " + err.Error()})
+ continue
+ }
+ 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}
@@ -169,6 +187,18 @@ func (s *Server) handleConn(conn net.Conn) {
if entry.Error != "" && r.Error == "" {
r.Error = entry.Error
}
+ if grepMatchRe != nil {
+ r.GrepMatch = grepMatchRe.Match(entry.ResponseRaw)
+ }
+ if grepExtractRe != nil {
+ if m := grepExtractRe.FindSubmatch(entry.ResponseRaw); m != nil {
+ if len(m) > 1 {
+ r.GrepExtract = string(m[1])
+ } else {
+ r.GrepExtract = string(m[0])
+ }
+ }
+ }
}
return enc.Encode(Response{Type: "intrude_result", IntrudeResult: &r}) == nil
})