在渗透测试与安全研究领域,能够精细控制HTTP/S流量的每一个字节往往意味着更大的发现漏洞的可能性。传统工具通常只能实现简单的请求重放或固定规则的修改,而Yakit的WebFuzzer模块通过热加载技术,特别是beforeRequest和afterRequest这两个魔术方法,将流量操控提升到了可编程的高度。本文将深入探讨如何利用这些功能实现动态、智能的请求与响应篡改。
热加载技术的核心在于允许用户在请求发出前和收到响应后插入自定义的Yaklang代码。这两个关键的魔术方法定义如下:
yak复制// 在请求发出前修改原始请求数据
beforeRequest = func(origin []byte) []byte {
return origin
}
// 在收到响应后修改原始响应数据
afterRequest = func(origin []byte) []byte {
return origin
}
与简单的fuzztag不同,这两个方法提供了对整个请求/响应生命周期的完全控制。它们的工作流程可以概括为:
请求阶段:
beforeRequest处理原始请求数据响应阶段:
afterRequest处理原始响应数据在许多测试场景中,我们需要为所有请求添加特定的认证头。传统方法可能需要手动修改每个请求或使用代理工具,而通过beforeRequest可以自动化这一过程:
yak复制beforeRequest = func(req) {
headers = "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx"
req = string(req)
// 检查是否已有Authorization头
if !str.Contains(req, "Authorization:") {
// 在第一个空行后插入头
parts = str.Split(req, "\r\n\r\n")
if len(parts) > 1 {
return []byte(parts[0] + "\r\n" + headers + "\r\n\r\n" + parts[1])
}
}
return []byte(req)
}
更高级的用法是根据请求URL动态修改参数。例如,对特定API端点添加测试参数:
yak复制beforeRequest = func(req) {
reqStr = string(req)
lines = str.Split(reqStr, "\r\n")
// 解析请求行
requestLine = lines[0]
parts = str.Split(requestLine, " ")
if len(parts) < 2 {
return []byte(reqStr)
}
method, path = parts[0], parts[1]
// 只处理/user/profile端点
if str.Contains(path, "/user/profile") {
// 添加测试参数
if str.Contains(path, "?") {
path = path + "&test=1"
} else {
path = path + "?test=1"
}
// 重建请求
lines[0] = sprintf("%s %s %s", [method, path, "HTTP/1.1"])
return []byte(str.Join(lines, "\r\n"))
}
return []byte(reqStr)
}
afterRequest的强大之处在于可以根据响应内容动态调整后续请求。这种响应驱动的测试模式可以极大提高测试效率。
许多Web应用会在会话过期时返回特定的响应。我们可以检测这种情况并自动刷新会话:
yak复制var (
sessionToken = "initial_token"
)
afterRequest = func(rsp) {
rspStr = string(rsp)
// 检测会话过期响应
if str.Contains(rspStr, "Session expired") {
// 调用刷新会话的API
newToken = yak.HTTPRequest("GET", "https://target.com/refresh", "--headers" ,
sprintf("Cookie: session=%s", sessionToken))~
// 更新全局token
if newToken.StatusCode == 200 {
sessionToken = newToken.Body
}
}
return []byte(rspStr)
}
beforeRequest = func(req) {
reqStr = string(req)
// 在所有请求中添加当前会话token
if str.Contains(reqStr, "Cookie:") {
reqStr = str.Replace(reqStr, "Cookie:.*", sprintf("Cookie: session=%s", sessionToken))
} else {
parts = str.Split(reqStr, "\r\n\r\n")
if len(parts) > 1 {
reqStr = parts[0] + "\r\n" + sprintf("Cookie: session=%s", sessionToken) + "\r\n\r\n" + parts[1]
}
}
return []byte(reqStr)
}
另一个常见场景是从响应中提取数据并注入到后续请求中:
yak复制var (
csrfToken = ""
)
afterRequest = func(rsp) {
rspStr = string(rsp)
// 从响应中提取CSRF token
match = re.Match(`name="csrf_token" value="([^"]+)"`, rspStr)
if len(match) > 1 {
csrfToken = match[1]
}
return []byte(rspStr)
}
beforeRequest = func(req) {
reqStr = string(req)
// 如果已获取CSRF token,注入到请求中
if csrfToken != "" {
if str.Contains(reqStr, "POST") && str.Contains(reqStr, "application/x-www-form-urlencoded") {
parts = str.Split(reqStr, "\r\n\r\n")
if len(parts) > 1 {
body = parts[1] + "&csrf_token=" + csrfToken
reqStr = parts[0] + "\r\n\r\n" + body
}
}
}
return []byte(reqStr)
}
结合beforeRequest和afterRequest可以实现智能的测试用例生成。例如,只在特定条件下注入测试payload:
yak复制var (
isVulnerable = false
)
afterRequest = func(rsp) {
rspStr = string(rsp)
// 检测可能的漏洞迹象
if str.Contains(rspStr, "error in your SQL syntax") {
isVulnerable = true
}
return []byte(rspStr)
}
beforeRequest = func(req) {
reqStr = string(req)
// 如果发现可能的漏洞,注入更复杂的payload
if isVulnerable {
parts = str.Split(reqStr, "\r\n\r\n")
if len(parts) > 1 {
param = re.Match(`(\w+)=[^&]*`, parts[1])
if len(param) > 1 {
newBody = str.Replace(parts[1], param[0], sprintf("%s=' OR 1=1--", param[1]))
reqStr = parts[0] + "\r\n\r\n" + newBody
}
}
}
return []byte(reqStr)
}
对于需要签名或加密的API,可以自动化这一过程:
yak复制import "encoding/base64"
import "crypto/hmac"
import "crypto/sha256"
beforeRequest = func(req) {
reqStr = string(req)
lines = str.Split(reqStr, "\r\n")
// 解析请求体
body = ""
for i, line := range lines {
if line == "" && i < len(lines)-1 {
body = str.Join(lines[i+1:], "\r\n")
break
}
}
// 计算HMAC签名
secret = "your_secret_key"
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(body))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
// 添加签名头
for i, line := range lines {
if line == "" {
lines = append(lines[:i], append([]string{sprintf("X-Signature: %s", signature)}, lines[i:]...)...)
break
}
}
return []byte(str.Join(lines, "\r\n"))
}
可以在处理请求和响应时记录关键信息用于后续分析:
yak复制var (
requestCount = 0
responseSizes = make([]int, 0)
)
beforeRequest = func(req) {
requestCount++
return req
}
afterRequest = func(rsp) {
responseSizes = append(responseSizes, len(rsp))
// 每10个请求输出一次统计
if len(responseSizes) % 10 == 0 {
total = 0
for _, size := range responseSizes {
total += size
}
avg = total / len(responseSizes)
printf("Processed %d requests, average response size: %d bytes\n", requestCount, avg)
}
return rsp
}
使用热加载功能时,调试和性能优化同样重要。以下是一些实用技巧:
可以在代码中添加调试输出,但要注意不要影响正常功能:
yak复制beforeRequest = func(req) {
reqStr = string(req)
printf("=== Request ===\n%s\n", reqStr)
return []byte(reqStr)
}
afterRequest = func(rsp) {
rspStr = string(rsp)
printf("=== Response (%d bytes) ===\n%.200s...\n", len(rspStr), rspStr)
return []byte(rspStr)
}
复杂的处理逻辑可能会影响测试速度,可以考虑以下优化:
yak复制var (
cache = make(map[string]string)
)
beforeRequest = func(req) {
reqStr = string(req)
// 检查缓存
if val, ok := cache[reqStr]; ok {
return []byte(val)
}
// 复杂的处理逻辑
processed = someComplexProcessing(reqStr)
// 缓存结果
cache[reqStr] = processed
return []byte(processed)
}
健壮的错误处理可以避免整个测试过程因一个小错误而中断:
yak复制beforeRequest = func(req) {
defer func {
if err := recover(); err != nil {
printf("Error in beforeRequest: %v\n", err)
return req
}
}()
// 正常的处理逻辑
return processedReq
}