1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
| Plan: Display POC Search-Extracted Variables in Output
Context
Currently, when a POC succeeds in normal mode (rules-based), named capture groups extracted from HTTP responses via the search regex field are stored in
variableMap for use in subsequent rules but are never displayed to the user. The user wants these extracted values (e.g., access tokens, credentials)
to appear in the scan output below the POC result line:
[+] PocScan http://10.3.4.78:8848 poc-yaml-nacos-multi-check-auth-bypass
* accessToken: xxxxxxxxxx
* username: xxxxxxxxxx
* password: xxxxxxxxxx
Critical File
Only one file needs modification:
/Users/veno/Projects/fscan/WebScan/lib/check.go
Implementation
1. Add "sort" import
In the import block, add "sort" (alphabetically between "regexp" and "strings").
2. Modify executePoc signature
From:
func executePoc(oReq *http.Request, p *Poc) (bool, error, string)
To:
func executePoc(oReq *http.Request, p *Poc) (bool, error, string, map[string]string)
3. Declare searchVars after variableMap (line ~80)
variableMap := make(map[string]interface{})
defer func() { variableMap = nil }()
searchVars := make(map[string]string) // ADD THIS
variableMap["request"] = req
4. Populate searchVars in DealWithRule closure
Change the search block inside DealWithRule from:
if rule.Search != "" {
result := doSearch(rule.Search, GetHeader(resp.Headers)+string(resp.Body))
if len(result) > 0 { // 正则匹配成功
for k, v := range result {
variableMap[k] = v
}
} else {
return false, nil
}
}
To:
if rule.Search != "" {
result := doSearch(rule.Search, GetHeader(resp.Headers)+string(resp.Body))
if len(result) > 0 { // 正则匹配成功
for k, v := range result {
variableMap[k] = v
if k != "" { // only named groups; unnamed groups have empty-string key
searchVars[k] = v
}
}
} else {
return false, nil
}
}
5. Update all return statements in executePoc
┌──────┬────────────────────────────────────────┬───────────────────────────────────────┐
│ Line │ From │ To │
├──────┼────────────────────────────────────────┼───────────────────────────────────────┤
│ ~72 │ return false, err, "" │ return false, err, "", nil │
├──────┼────────────────────────────────────────┼───────────────────────────────────────┤
│ ~77 │ return false, err, "" │ return false, err, "", nil │
├──────┼────────────────────────────────────────┼───────────────────────────────────────┤
│ ~86 │ return false, nil, "" │ return false, nil, "", nil │
├──────┼────────────────────────────────────────┼───────────────────────────────────────┤
│ ~100 │ return success, nil, "" (Sets/cluster) │ return success, nil, "", nil │
├──────┼────────────────────────────────────────┼───────────────────────────────────────┤
│ ~193 │ return success, nil, name (Groups) │ return success, nil, name, searchVars │
├──────┼────────────────────────────────────────┼───────────────────────────────────────┤
│ ~198 │ return success, nil, "" (Rules final) │ return success, nil, "", searchVars │
└──────┴────────────────────────────────────────┴───────────────────────────────────────┘
6. Update CheckMultiPoc to display extracted variables
From:
isVul, _, name := executePoc(task.Req, task.Poc)
if isVul {
result := fmt.Sprintf("[+] PocScan %s %s %s", task.Req.URL, task.Poc.Name, name)
common.LogSuccess(result)
}
To:
isVul, _, name, extractedVars := executePoc(task.Req, task.Poc)
if isVul {
result := fmt.Sprintf("[+] PocScan %s %s %s", task.Req.URL, task.Poc.Name, name)
if len(extractedVars) > 0 {
keys := make([]string, 0, len(extractedVars))
for k := range extractedVars {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
result += fmt.Sprintf("\n * %s: %s", k, extractedVars[k])
}
}
common.LogSuccess(result)
}
Design Decisions
- Sort keys alphabetically: Go map iteration is non-deterministic; sorting ensures consistent, reproducible output.
- Single LogSuccess call: Builds the full multi-line string before calling LogSuccess to prevent interleaving from concurrent goroutines.
fmt.Println/color.Red in SaveLog handle multi-line strings correctly. WriteFile also works with multi-line strings (plain text appends result + "\n";
JSON output: Go's json.Marshal escapes embedded newlines).
- Exclude unnamed groups (k != ""): doSearch can return "" as a key for unnamed capture groups; these are internal and should not appear in output.
- set variables not in searchVars: set variables are added to variableMap directly (not through doSearch), so they can never enter searchVars.
- brute-force path unchanged: clusterpoc already has its own output with credentials in tmpMap.
- Groups mode returns searchVars: For consistency, the groups path also returns searchVars (line 193) so POCs using groups format also display extracted
variables.
Verification
Step 1: Create a test POC YAML
Create /Users/veno/Projects/fscan/WebScan/pocs/poc-yaml-test-search-extract.yml:
name: poc-yaml-test-search-extract
transport: http
rules:
- method: GET
path: /
expression: status == 200 && body.bcontains(b"fscan-test")
search: 'token=(?P<token>[a-z0-9]+).*?user=(?P<username>[a-zA-Z]+)'
Step 2: Start a local test HTTP server
Use Python to serve a response containing the expected fields:
python3 -c "
import http.server, socketserver
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
body = b'fscan-test token=abc123def user=admin'
self.send_response(200)
self.end_headers()
self.wfile.write(body)
def log_message(self, *a): pass
with socketserver.TCPServer(('', 18080), H) as s:
s.serve_forever()
" &
Step 3: Build and run
go build -o fscan main.go
./fscan -h 127.0.0.1:18080 -m webonly -pocname poc-yaml-test-search-extract
Expected output
[+] PocScan http://127.0.0.1:18080 poc-yaml-test-search-extract
* token: abc123def
* username: admin
Step 4: Cleanup
kill %1 # stop the python server
rm WebScan/pocs/poc-yaml-test-search-extract.yml
|