AI LOAD INSTRUCTION: This is the "module-level Web Fuzzer" topic in the three-tier hot loading system. Web Fuzzer hot loading acts on the packet-sending flow of a single Fuzzer Tab; the most typical uses are "intranet encryption countermeasures" (request encryption, response decryption, signature injection) and "automated decision-making" (retry, business failure determination). First read the Hook signatures and return value semantics, then look at
examples/examples named by Hook below, each of which can beyak <file>self-tested. When handling "various frontend encryption HACKs / letting users see plaintext", go directly to the combination recipe in Section 5 (examples/combo-*.yak); before writing any hot loading, be sure to read Section 6 "Concurrency and Global Variables" to avoid race crashes.
0. Related Routes
- Main entry and three-tier system:yak
- MITM hot loading (proxy-side hijacking/mirroring/storage):mitm-hotpatch
- Global hot loading (executes before modules, shared by MITM/Fuzzer):global-hotpatch
1. Hook Signatures and Return Value Semantics
The hooks of Web Fuzzer hot loading differ critically from those of MITM: hijacking-type hooks submit modifications through the return value (not forward/drop).
| Hook | Signature | Function | Submission method |
|---|---|---|---|
beforeRequest |
(https, originReq, req) -> req |
Rewrite before request goes out (encryption/signature/headers) | return new request |
afterRequest |
(https, originReq, req, originRsp, rsp) -> rsp |
Rewrite before response is displayed (decryption/formatting) | return new response |
retryHandler |
(https, retryCount, req, rsp, retry) |
Decide the next step when retry is triggered | retry([newReq]) |
customFailureChecker |
(https, req, rsp, fail) |
Custom "failure" determination | fail("原因") |
mirrorHTTPFlow |
(req, rsp, params) -> params |
Extract parameters for {{params(name)}} |
return map |
mockHTTPRequest |
(https, url, req, mockResponse) |
Use a local response when offline/no service | mockResponse(rspStr) |
Note: the Web Fuzzer's
mirrorHTTPFlow(req, rsp, params)differs from MITM'smirrorHTTPFlow(isHttps, url, req, rsp, body)signature, do not mix them up.
2. Core Scenario One: Intranet Encryption Countermeasures
Modern Web applications often encrypt or sign requests/responses. By consolidating the encryption/decryption/signature logic into hot loading, you can directly fuzz the plaintext in the Fuzzer; packets are automatically encrypted when sent and automatically decrypted when displayed.
flowchart LR
Plain["Fuzzer 里写明文请求"] -->|beforeRequest 加密/签名| Wire["真正出站的密文/带签名请求"]
Wire --> Server["目标服务器"]
Server -->|密文响应| After["afterRequest 解密"]
After --> View["Fuzzer 里看到明文响应"]
| Scenario | Hook | Example |
|---|---|---|
| timestamp+nonce+HMAC signature injection | beforeRequest |
examples/before-request.yak |
| AES-CBC response decryption (Fuzzer sees plaintext) | afterRequest |
examples/after-request.yak |
The same applies to Chinese national cryptography (SM4-CBC / SM2 / SM3); just replace codec.AESCBC* with codec.SM4* / codec.Sm3 .
3. Core Scenario Two: Automated Decision-Making (Retry / Failure Determination)
The two functions introduced in article 043 automate and codify "manually analyzing the response and then deciding what to do":
| Scenario | Hook | Example |
|---|---|---|
| 405→POST / 429 backoff / 401 give up / 5xx retry | retryHandler |
examples/retry-handler.yak |
| 200 OK but body contains failure keywords → judged as failure | customFailureChecker |
examples/custom-failure-checker.yak |
| Use a local response for integration testing when the target is not online | mockHTTPRequest |
examples/mock-http-request.yak |
retryHandlerMust be used together with yakit Fuzzer's retry configuration (enable retry + specify failure status codes).customFailureCheckerSolves the brute-force filtering problem of "protocol-layer 200, business-layer failure".mockHTTPRequestLets payload rendering/retry/failure determination run through integration testing even without a real service.
4. Core scenario 3: fuzztag and correlated testing
| Scenario | Mechanism | Example |
|---|---|---|
| Dynamically compute hash payload | `{{yak(hash | md5,admin)}}` |
| Multi-step request to extract token/csrf | mirrorHTTPFlow returns a map → {{params(name)}} |
examples/mirror-http-flow.yak |
An ordinary function defined in hot-loading f = func(param){ return [...] } can be used as the {{yak(f|参数)}} tag and called; each element of the returned array is a payload.
5. Frontend encryption countermeasure combination recipes (let users see plaintext)
A single hook solves a single-point problem; a real "frontend encryption HACK" often requires combining multiple hooks: encrypt/sign on the request side, decrypt on the response side, so that in the Fuzzer you always face plaintext. This is exactly the core approach for bypassing frontend encryption to perform brute-forcing/testing.
flowchart LR
P["你写的明文 payload"] -->|beforeRequest / fuzztag 加密+签名| W["出站密文(过验签)"]
W --> S["目标服务器"]
S -->|密文响应| D["afterRequest / mirrorHTTPFlow 解密"]
D --> V["Fuzzer 里看到明文, 一眼判断成功"]
| Combination recipe | Hooks involved | Frontend protection being countered | Example |
|---|---|---|---|
| AES-CBC write plaintext/see plaintext round trip | beforeRequest + afterRequest |
CryptoJS AES-CBC static key | examples/combo-aescbc-plaintext-roundtrip.yak |
| Encryption + HMAC signature verification dual protection | beforeRequest + afterRequest |
AES encryption combined with signature to prevent tampering | examples/combo-sign-and-encrypt.yak |
| RSA-OAEP + AES-GCM hybrid encryption | {{yak(...)}} fuzztag + mirrorHTTPFlow |
Dynamic public key + asymmetric wrapping of symmetric key | examples/combo-rsa-aesgcm-hybrid.yak |
Key points:
- Request sideUse
beforeRequest(whole-packet encryption/signature) or{{yak(fn|...)}}fuzztag (encrypt only a certain field). - Response sideUse
afterRequest(directly change the response to plaintext echo) ormirrorHTTPFlow(extract the decryption result as a variable, together with the sequence{{params(name)}}, and see success at a glance in the results table). - For dynamic keys/public keys use Web Fuzzer sequence: Step0 obtains the key → data extractor stores
publicKey/privateKey→ Step1{{params(publicKey)}}reference (see the header comment of the hybrid encryption example). - Common algorithm APIs:
codec.AESCBCEncryptWithPKCS7Padding/codec.AESGCMEncryptWithNonceSize12/codec.RSAEncryptWithOAEP/codec.HmacSha256, for Chinese national cryptography replace withcodec.Sm4*/codec.SM2*。
Why "plaintext password transmission" is worth testing: see article 183—frontend encryption only raises the threshold; the password is still essentially recoverable in communication; after decrypting the link into plaintext, conventional injection/brute-force/logic testing all become usable again.
6. Concurrency and global variables: use "read-only constants" as much as possible, otherwise it will crash
This is the crash pitfall most easily encountered in hot-loading. MITM / Web Fuzzer will concurrently call the same hook (the same script instance) for multiple requests. If your hook reads and writes the same top-level mutable global variable(for example, append to a global slice, assigning to a global map, accumulating a global counter), multiple goroutines writing at the same time will trigger data race, causing at best garbled results and at worst a direct panic crash.
Rules:
- Put configuration/keys/IV/rule tables, etc. at the top level, but treat them as "read-only constants": assign once at load time, and inside the hook never modify. This usage is naturally concurrency-safe (all encryption combination examples in this section are written this way).
- Do not write shared mutable globals inside hooks. When accumulation/collection is needed:
- Prefer to keep state in function-local or pass it out via return values (
mirrorHTTPFlowReturn map,afterRequestreturn package). - If you really need to aggregate across requests, use a concurrency-safe container/lock:
sync.Map、sync.NewMutex()lock, or yakit'sdb.*/risk.*persist to storage, rather than bareappendglobal slice.
- Prefer to keep state in function-local or pass it out via return values (
// 反例(并发会崩): 多个请求同时 append 同一个全局 slice
RESULTS = []
mirrorHTTPFlow = func(req, rsp, params) { RESULTS = append(RESULTS, req); return {} } // 危险!
// 正例: 顶层只放只读常量, 结果通过返回值带出
AES_KEY = codec.DecodeHex("3132...")~ // 只读常量, 不再修改
afterRequest = func(https, oreq, req, orsp, rsp) {
return poc.ReplaceHTTPPacketBody(rsp, decrypt(AES_KEY, poc.GetHTTPPacketBody(rsp)))
}
This library's
mirror-*single-hook examples use global slice/map accumulation for "self-test assertability"—that is the single-threaded command-line self-test demo style; when putting it into a real concurrent MITM/Fuzzer, please change it according to the rules above to read-only constants + return values/locking.
7. Standard style: hook function + YAK_MAIN self-test
beforeRequest = func(https, originReq, req) {
// 改写 req
return req
}
func runSelfTest() {
// 构造 mock 请求, 调用 hook, assert 返回值
}
if YAK_MAIN {
runSelfTest()
}
YAK_MAIN Distinguish the runtime environment:
yak xxx.yakCommand line:YAK_MAIN = true, run the self-test.- yakit Fuzzer hot-load window:
YAK_MAIN = false, only register the hook.
Self-test mock method for each Hook
| Hook | Self-test verification method |
|---|---|
beforeRequest / afterRequest |
Construct a mock request/response, assert the returned package content (you can perform reverse decryption on the encrypted result to verify the roundtrip) |
retryHandler |
Custom retry callback(func(args...)), assert the call count and the new request content |
customFailureChecker |
Custom fail callback, assert whether it is marked as failed |
mirrorHTTPFlow |
Call directly, assert the extracted fields of the returned map |
mockHTTPRequest |
Custom mockResponse callback, verify the target is triggered |
8. Common codec / poc API quick reference
| Purpose | API |
|---|---|
| AES-CBC encrypt/decrypt | codec.AESCBCEncrypt(key, data, iv)~ / codec.AESCBCDecrypt(key, cipher, iv)~ |
| Base64 encode/decode | codec.EncodeBase64(b) / codec.DecodeBase64(s)~ |
| HMAC-SHA256 (returns bytes) | codec.EncodeToHex(codec.HmacSha256(key, data)) |
| Hash | codec.Md5(s) / codec.Sha256(s) / codec.EncodeToHex(codec.Sm3(s)) |
| Status code | poc.GetStatusCodeFromResponse(rsp) |
| Change method | poc.ReplaceHTTPPacketMethod(req, "POST") |
| Change body / header | poc.ReplaceHTTPPacketBody / poc.ReplaceHTTPPacketHeader |
| Get request method/path | poc.GetHTTPRequestMethod(req) / poc.GetHTTPRequestPath(req) |
9. Verification
cd /Users/v1ll4n/Projects/yaklang
go run common/yak/cmd/yak.go skills/webfuzzer-hotpatch/examples/before-request.yak
# 与 Yakit gRPC 同款执行路径 (MutateHookCaller); 验证 {{yak(...)}} fuzztag:
go build -o /tmp/yak ./common/yak/cmd/yak.go
printf 'GET / HTTP/1.1\r\nHost: t.example.com\r\n\r\n' > /tmp/req.txt
/tmp/yak hotpatch-webfuzzer --script skills/webfuzzer-hotpatch/examples/fuzztag-handle.yak \
--request /tmp/req.txt --fuzztag '{{yak(hash|md5,hello)}}'
Each example should: complete within 10 seconds, all asserts pass, all logs in English, and show ... self test passed。
Reference sources
- yak-project-public 043 (2025-07-25) Web Fuzzer new hot-load functions—retry control and error handling
- yak-project-public 183 (2023-09-14) Advanced penetration testing techniques: analyzing signature verification and front-end encryption (part 1) (HMAC signature verification + AES-CBC, source of the combined recipe)
- yak-project-public 177 (2023-10-11) Advanced penetration testing techniques (part 2): countering front-end dynamic keys and asymmetric encryption protection (RSA-OAEP + AES-GCM hybrid, source of the hybrid encryption combination)
- yak-project-public 084 (2024-12-05) Advanced penetration testing techniques (part 3): vulnerability testing after front-end encryption
- yak-project-public 270 (2022-07-28) Front-end AES-ECB encryption web security testing in practice
- yak-project-public 282/285 (2022-05) Yakit Web Fuzzer ultimate capability enhancement: hot-load Fuzz
- yak-project-public 167 (2023-11-17) Web Fuzzer advanced
- Engine implementation:
common/yak/script_engine_for_fuzz.go