前言

Yakit作为基于Yaklang的安全测试平台,其Web Fuzzer模块凭借Fuzz Tag机制已实现基础参数爆破、编码变换等核心功能。

然而,面对复杂业务逻辑(如动态Token校验、多级编码叠加、非对称加密验签)时,传统标签组合常因逻辑嵌套导致编码失效或性能瓶颈。Yakit热加载模块的诞生,可以很好且有效的解决这一问题。热加载中内置了多个魔术函数(mirrorHTTPFlowhijackHTTPRequesthijackSaveHTTPFlow….),通过对这些热加载函数进行重写,可以实现复杂业务逻辑的处理。

下面列出yakit热加载中的代码模版:

// 请求 -> hijackHTTPRequest -> 前端劫持 -> beforeRequest -> 服务器响应 ->  hijackResponse -> 后端劫持 -> afterRequest -> 客户端看到的响应 -> hijackSaveHTTPFlow

// mirrorHTTPFlow 会镜像所有的流量到这里,包括被过滤器过滤的请求
// !!! 一般插件不要实现这个接口
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorHTTPFlow = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}

// mirrorFilteredHTTPFlow 会镜像过滤后的流量到这里
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorFilteredHTTPFlow = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}

// mirrorNewWebsite 会镜像过滤后的流量到这里,每个网站只会触发一次
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorNewWebsite = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}

// mirrorNewWebsitePath 会镜像过滤后的流量到这里,每个网站的相同路径只会触发一次
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorNewWebsitePath = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}

// mirrorNewWebsitePathParams 会镜像过滤后的流量到这里,每个网站的参数相同的请求只会触发一次
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorNewWebsitePathParams = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}

// hijackHTTPRequest 会在过滤后的请求到达Yakit MITM前被调用,可以通过该函数提前将请求修改或丢弃
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// forward(req) 提交修改后的请求,如果未被调用,则使用原始的请求
// drop() 丢弃请求
hijackHTTPRequest = func(isHttps, url, req, forward /*func(modifiedRequest []byte)*/, drop /*func()*/) {
// Example:
// if str.Contains(string(req), "/should_modify") {
// modified = str.ReplaceAll(string(req), "/should_modify", "/modified")
// forward(poc.FixHTTPRequest(modified))
// }

// if str.Contains(string(req), "/drop") {
// drop()
// }
}


// hijackHTTPResponse 会在过滤后的响应到达Yakit MITM前被调用,可以通过该函数提前将响应修改或丢弃
// isHttps 请求是否为https请求
// url 网站URL
// rsp 响应
// forward(req) 提交修改后的响应,如果未被调用,则使用原始的响应
// drop() 丢弃响应
hijackHTTPResponse = func(isHttps /*bool*/, url /*string*/, rsp /*[]byte*/, forward /*func(modifiedResponse []byte)*/, drop /*func()*/) {
// Example:
// if str.Contains(string(rsp), "凝聚磅礴的中国文学力量") {
// modified = poc.FixHTTPResponse(str.ReplaceAll(rsp, "凝聚磅礴的中国文学力量", "AAAAAAAAAAAAAAAA"))
// forward(modified)
// }
}

// hijackHTTPResponseEx 是hijackHTTPResponse的扩展,能够获取到响应对应的请求,会在过滤后的响应到达Yakit MITM前被调用,可以通过该函数提前将响应修改或丢弃
// !!! 通常实现hijackHTTPResponse 或 hijackHTTPResponseEx 其中一个函数即可
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// forward(req) 提交修改后的响应,如果未被调用,则使用原始的响应
// drop() 丢弃响应
hijackHTTPResponseEx = func(isHttps /*bool*/, url /*string*/, req/*[]byte*/, rsp /*[]byte*/, forward /*func(modifiedResponse []byte)*/, drop /*func()*/) {
// Example:
// if str.Contains(string(rsp), "凝聚磅礴的中国文学力量") {
// modified = poc.FixHTTPResponse(str.ReplaceAll(rsp, "凝聚磅礴的中国文学力量", "AAAAAAAAAAAAAAAA"))
// forward(modified)
// }
}

// beforeRequest 会在请求到达服务器之前被调用,可以通过该函数对请求做最后一次修改
// isHttps 请求是否为https请求
// oreq 原始请求
// req hijackRequest修改后的请求
// 返回值: 修改后的请求,如果没有返回值则使用hijackRequest修改后的请求
beforeRequest = func(ishttps /*bool*/, oreq /*[]byte*/, req/*[]byte*/){
// Example:
// if str.Contains(string(req), "凝聚磅礴的中国文学力量") {
// modified = poc.FixHTTPRequest(str.ReplaceAll(req, "凝聚磅礴的中国文学力量", "AAAAAAAAAAAAAAAA"))
// return []byte(modified)
// }
}

// afterRequest 会在响应到达客户端之前被调用,可以通过该函数对响应做最后一次修改
// isHttps 请求是否为https请求
// oreq 原始请求
// req hijackRequest修改后的请求
// orsp 原始响应
// rsp hijackHTTPResponse/hijackHTTPResponseEx修改后的响应
// 返回值: 修改后的响应,如果没有返回值则使用hijackHTTPResponse/hijackHTTPResponseEx修改后的响应
afterRequest = func(ishttps, oreq/*原始请求*/ ,req/*hiajck修改之后的请求*/ ,orsp/*原始响应*/ ,rsp/*hijack修改后的响应*/){
// Example:
// if str.Contains(string(rsp), "凝聚磅礴的中国文学力量") {
// modified = poc.FixHTTPRequest(str.ReplaceAll(rsp, "凝聚磅礴的中国文学力量", "AAAAAAAAAAAAAAAA"))
// return []byte(modified)
// }
}

// hijackSaveHTTPFlow 会在流量被存储到数据库前被调用,可以通过该函数对入库前的流量进行修改,例如修改请求/响应,添加tag/染色等
// flow 流量结构体,可以通过鼠标悬浮提示查看其拥有的字段并对其进行修改
// modify(modified) 提交修改后的流量结构体,如果未被调用,则使用原始的流量结构体
// drop() 丢弃流量
hijackSaveHTTPFlow = func(flow /* *yakit.HTTPFlow */, modify /* func(modified *yakit.HTTPFlow) */, drop/* func() */) {
// flow.Request 转义后的请求
// flow.Response 转义后的响应
// 对于转义后的请求和响应,需要通过以下方式拿到原始的请求/响应
// req = str.Unquote(flow.Request)~
// rsp = str.Unquote(flow.Response)~
// 对于修改后的请求和响应,需要通过以下方式再将其转义回去
// flow.Request = str.Quote(req)
// flow.Response = str.Quote(rsp)
// flow.AddTag("tag") // 添加tag
// flow.Red() // 染红色
//
// Example:
// responseBytes, _ = codec.StrconvUnquote(flow.Response)
// if str.MatchAnyOfRegexp(responseBytes, "/admin/", "accessKey") {
// flow.Red();
// modify(flow)
// }
}

模块详解

流量劫持

// 请求 -> hijackHTTPRequest -> 前端劫持 -> beforeRequest -> 服务器响应 ->  hijackResponse -> 后端劫持 -> afterRequest -> 客户端看到的响应 -> hijackSaveHTTPFlow

Yakit的热加载机制通过多模块协同实现请求-响应全链路劫持与处理,其核心处理流程可细分为以下阶段(按时间顺序排列)

请求处理阶段

hijackRequest劫持(手动劫持场景)

// hijackHTTPRequest 会在过滤后的请求到达Yakit MITM前被调用,可以通过该函数提前将请求修改或丢弃
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// forward(req) 提交修改后的请求,如果未被调用,则使用原始的请求
// drop() 丢弃请求
hijackHTTPRequest = func(isHttps, url, req, forward /*func(modifiedRequest []byte)*/, drop /*func()*/) {
// Example:
// if str.Contains(string(req), "/should_modify") {
// modified = str.ReplaceAll(string(req), "/should_modify", "/modified")
// forward(poc.FixHTTPRequest(modified))
// }

// if str.Contains(string(req), "/drop") {
// drop()
// }
}

hijackHTTPRequest相当于请求预处理,可以在客户端的流量到达yakit MITM之前被调用。它处于请求处理流程的早期阶段,可能用于底层流量的修改或丢弃请求。hijackHTTP模块在请求处理流程中处于客户端与服务端之间的Yakit MITM代理阶段。这可能意味着它用于全局的请求过滤或修改,比如统一添加请求头、修改特定路径,或者根据条件丢弃请求。

使用的场景如下:

  • 请求修改
    • 针对请求包存在加密的场景,可以使用该模块,对请求数据包做解密,使其明文展示到mitm中
    • 替换特定参数,如针对数据包添加固定请求头等(也可以在beforeRequest实现)
  • 请求丢弃
    • 通过drop函数可以直接终止请求链路

其实总结一下,hijackHTTPRequest有点类似于burp的Interpect模块,可以直接修改请求并将修改后的请求发送到服务端。

image-20250619175335958

beforeRequest预处理(自动劫持场景)

// beforeRequest 会在请求到达服务器之前被调用,可以通过该函数对请求做最后一次修改
// isHttps 请求是否为https请求
// oreq 原始请求
// req hijackRequest修改后的请求
// 返回值: 修改后的请求,如果没有返回值则使用hijackRequest修改后的请求
beforeRequest = func(ishttps /*bool*/, oreq /*[]byte*/, req/*[]byte*/){
// Example:
// if str.Contains(string(req), "凝聚磅礴的中国文学力量") {
// modified = poc.FixHTTPRequest(str.ReplaceAll(req, "凝聚磅礴的中国文学力量", "AAAAAAAAAAAAAAAA"))
// return []byte(modified)
// }
}

通俗来说,就是在流量离开yakit之前,还可以对流量做修改,适用场景:

  • 参数加密:AES/DES/Base64等算法处理请求体
  • 签名生成:动态基于时间戳/请求体/请求url生成对应签名
  • 流量标记:可以添加标识符,用于后续流量分析

服务器交互阶段

hijackResponse响应处理

// hijackHTTPResponse 会在过滤后的响应到达Yakit MITM前被调用,可以通过该函数提前将响应修改或丢弃
// isHttps 请求是否为https请求
// url 网站URL
// rsp 响应
// forward(req) 提交修改后的响应,如果未被调用,则使用原始的响应
// drop() 丢弃响应
hijackHTTPResponse = func(isHttps /*bool*/, url /*string*/, rsp /*[]byte*/, forward /*func(modifiedResponse []byte)*/, drop /*func()*/) {
// Example:
// if str.Contains(string(rsp), "凝聚磅礴的中国文学力量") {
// modified = poc.FixHTTPResponse(str.ReplaceAll(rsp, "凝聚磅礴的中国文学力量", "AAAAAAAAAAAAAAAA"))
// forward(modified)
// }
}

主要用来动态修改服务器返回的内容,在响应到达Yakit MITM前被调用。适用场景:

  • 响应包解密

    对比前面的hijackHTTPRequest,该模块最实用的功能就是对响应的内容进行解密处理,使得在mitm中展示出来的为明文数据。

  • 逻辑绕过(修改响应包内容,如验证码验证结果,同理也可以在afterRequest中操作)

响应处理阶段

afterRequest后处理

// afterRequest 会在响应到达客户端之前被调用,可以通过该函数对响应做最后一次修改
// isHttps 请求是否为https请求
// oreq 原始请求
// req hijackRequest修改后的请求
// orsp 原始响应
// rsp hijackHTTPResponse/hijackHTTPResponseEx修改后的响应
// 返回值: 修改后的响应,如果没有返回值则使用hijackHTTPResponse/hijackHTTPResponseEx修改后的响应
afterRequest = func(ishttps, oreq/*原始请求*/ ,req/*hiajck修改之后的请求*/ ,orsp/*原始响应*/ ,rsp/*hijack修改后的响应*/){
// Example:
// if str.Contains(string(rsp), "凝聚磅礴的中国文学力量") {
// modified = poc.FixHTTPRequest(str.ReplaceAll(rsp, "凝聚磅礴的中国文学力量", "AAAAAAAAAAAAAAAA"))
// return []byte(modified)
// }
}

类比beforeRequest,主要是在响应到达客户端之前被调用。使用场景:

  • 数据加密

    如果前面使用了hijackHTTPResponse做解密处理,可以对解密后的内容做重加密,便于客户端识别加载

流量展示

hijackSaveHTTPFlow

// hijackSaveHTTPFlow 会在流量被存储到数据库前被调用,可以通过该函数对入库前的流量进行修改,例如修改请求/响应,添加tag/染色等
// flow 流量结构体,可以通过鼠标悬浮提示查看其拥有的字段并对其进行修改
// modify(modified) 提交修改后的流量结构体,如果未被调用,则使用原始的流量结构体
// drop() 丢弃流量
hijackSaveHTTPFlow = func(flow /* *yakit.HTTPFlow */, modify /* func(modified *yakit.HTTPFlow) */, drop/* func() */) {
// flow.Request 转义后的请求
// flow.Response 转义后的响应
// 对于转义后的请求和响应,需要通过以下方式拿到原始的请求/响应
// req = str.Unquote(flow.Request)~
// rsp = str.Unquote(flow.Response)~
// 对于修改后的请求和响应,需要通过以下方式再将其转义回去
// flow.Request = str.Quote(req)
// flow.Response = str.Quote(rsp)
// flow.AddTag("tag") // 添加tag
// flow.Red() // 染红色
//
// Example:
// responseBytes, _ = codec.StrconvUnquote(flow.Response)
// if str.MatchAnyOfRegexp(responseBytes, "/admin/", "accessKey") {
// flow.Red();
// modify(flow)
// }
}

hijackSaveHTTPFlow会在流量被存储到数据库前被调用。他本身不属于流量劫持的某个阶段,所以通过该函数对入库前的数据包修改,既可以显示数据包为明文也不会影响请求(不过这快只能看不能修改,因为不在流量劫持阶段)。适用场景:

  • 数据包明文显示

    适用于查看明文数据但不做修改的需求。

  • 添加tag/染色

    可以对数据包进行打标签操作,方便识别

流量镜像

这些模块在平常渗透测试中用到的比较少,这款只是简单列一下:

// 请求 -> hijackHTTPRequest -> 前端劫持 -> beforeRequest -> 服务器响应 ->  hijackResponse -> 后端劫持 -> afterRequest -> 客户端看到的响应 -> hijackSaveHTTPFlow

// mirrorHTTPFlow 会镜像所有的流量到这里,包括被过滤器过滤的请求
// !!! 一般插件不要实现这个接口
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorHTTPFlow = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}

// mirrorFilteredHTTPFlow 会镜像过滤后的流量到这里
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorFilteredHTTPFlow = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}

// mirrorNewWebsite 会镜像过滤后的流量到这里,每个网站只会触发一次
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorNewWebsite = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}

// mirrorNewWebsitePath 会镜像过滤后的流量到这里,每个网站的相同路径只会触发一次
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorNewWebsitePath = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}

// mirrorNewWebsitePathParams 会镜像过滤后的流量到这里,每个网站的参数相同的请求只会触发一次
// isHttps 请求是否为https请求
// url 网站URL
// req 请求
// rsp 响应
// body 响应体
mirrorNewWebsitePathParams = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {

}
  • mirrorHTTPFlow:镜像全流量
  • mirrorFilteredHTTPFlow:镜像过滤流量
  • mirrorNewWebsite:每新出现一个网站,这个网站的第一个请求,将会在这里被调用!
  • mirrorNewWebsitePath:每新出现一个网站路径,关于这个网站路径的第一个请求,将会在这里被传入回调
  • mirrorNewWebsitePathParams:每新出现一个网站路径且带有一些参数,参数通过常见位置和参数名去重,去重的第一个 HTTPFlow 在这里被调用

总结

其实语法和使用这块都殊途同归,重要的是要搞清楚各个函数在什么时候使用最合理。