用户在使用 Yakit MITM 功能的时候,经常会遇到一些特殊需求:
这类需要动态修改流量并放行的需求如果手动操作的话,非常复杂。需要劫持到数据包,然后修改数据包,手动放行,不是不可以完成,而是这样的操作并不是自动化的,人工操作多了很容易疲倦漏掉一些关键信息。 熟悉 Yakit 的用户,尤其是一些深度用户非常熟悉这种操作,我们可以点击 “热加载”,在恰当的 Hook 点编写我们希望操作流量做的事儿,然后加载进引擎中,等待流量执行。 通过一个简单的图例展示热加载代码在流量劫持中的过程:
如果在测试过程中,IP 被 WAF 封禁了,阻碍了我们后续的工作,当然,通过代理池的手段,我们可以直接解决这个问题,但是通常在筹备代理池之前,我们会尝试修改 XFF 或者 XRI 来绕过 IP 限制。
这个原理十分简单,一般来说,我们后端需要通过 X-Forwarded-For 或 X-Real-IP 来识别客户端的真实 IP,那么在频繁进行测试中,WAF 会对过频访问的 IP 进行限制,那么我们修改 X-Forwarded-For 一般能起到绕过检测的作用。
当然,能否真的绕过取决于 WAF 部署的层数,如果在 TCP 层部署的 WAF 一般情况是无法通过这个手段绕过的,如果仅仅是七层 WAF,或 Nginx 强制覆盖了 XFF 等头,也会造成绕过失败。
对于用户来说,只需要为每一个请求新增 XFF 即可达到很好的效果,那么在 Yakit 中,我们如何操作呢?
如下图展示,我们使用 Yaklang 中的 fuzz 模块,构建了一个数据包,这个数据包会在新数据包的基础上随机使用一个 IP 作为 XFF 的值填入。
在修改好劫持代码之后,我们复制粘贴点击 “加载当前代码” 然后就可以看到 Initializing HotPatched MITM HOOKS 的标志,意味着我们的热加载代码被引擎接受了,并加载到了内存中。
点击 HTTP History,我们随便查看流经 Yakit 的流量列表,可以看到流量的 X-Forwarded-For 已经被成功修改
addXFF = func(packet) {
return fuzz.MustHTTPRequest(
packet,
).FuzzHTTPHeader(
"X-Forwarded-For",
"{{ri(1,255)}}.{{ri(0,255)}}.{{ri(0,255)}}.{{ri(0,255)}}",
).FirstHTTPRequestBytes()
}
我们在实现上述案例的时候,发现这个核心函数其实非常关键,那我们应该如何编写这种函数呢? 如果用户简单的通过 str.Replace... 或者 re 模块来写,是没有问题的;但是会给用户在使用上造成一定的困难:比如 str / re 并不会自动修复被损坏的数据包,需要通过 poc.FixHTTPRequest 来修复数据包。 实际上我们上述的使用中,并没有使用到一些基础的字符串处理,而是使用了 fuzz 模块。 顺便为大家介绍一下 fuzz 模块中修改数据包的接口和特色。
不同于其他模块的函数+参数风格,在 fuzz.HTTPRequest 和 fuzz.MustHTTPRequest函数使用中,我们修改数据包是通过一个接一个的函数调用来完成修改的。这种模式可能听起来有点陌生,我们简单通过几个例子即可很容易理解。 最简洁的链式调用 API 我们可以这样完成(Yaklang >= 1.1.5)
packet := `GET / HTTP/1.1
Host: www.example.com
`
// 开始进行链式调用
fuzz.MustHTTPRequest(
packet,
).FuzzHTTPHeader( // 修改数据包 Header
`Header1`,
"From fuzz.MustHTTPRequest",
).FuzzGetParams( // 修改数据包 Get 参数
"key", "value",
).Show()
/*
GET /?key=value HTTP/1.1
Host: www.example.com
Header1: From fuzz.MustHTTPRequest
*/
我们观察上述代码,发现通过 MustHTTPRequest 构建的请求直接可以调用后续方法进行修改数据包,通过 .Show() 来展示最后的修改结果。 如果担心代码不安全,我们也可以使用 fuzz.HTTPRequest 来显式接收错误:
注意:fuzz.HTTPRequest 和 fuzz.MustHTTPRequest 相比,除了返回值数量之外,是没有差别的;MustHTTPRequest 会隐藏错误,如果构建数据包失败,将会仅抛出警告。
/*
使用 fuzz.HTTPRequest 获取可供链式调用的对象,并同时支持错误处理
*/
freq, err = fuzz.HTTPRequest(`GET / HTTP/1.1
Host: www.example.com`)
die(err) // 遇到错误马上退出
freq.FuzzHTTPHeader(
"Header1",
"Header-Value-2",
).FuzzHTTPHeader(
"Header3",
"Header3-Value",
).Show()
/*
OUTPUT:
GET / HTTP/1.1
Host: www.example.com
Header1: Header2
Header3: Header3-Value
*/
Fuzztag 作为伴随 Yaklang 一直成长的特性,同时也作为 fuzz 原生支持的功能,在 fuzz.HTTPRequest 中也理所当然的被支持,所以我们可以使用如下 fuzztag 来随机生成一个 IP 地址{{randint(1,255)}}.{{randint(0,255)}}.{{randint(0,255)}}.{{randint(0,255)}},或者定制其他数据。
这也是我们 addXFF 函数添加随机 IP 进入 XFF 的依据,一般来说,在 Fuzz.. 函数被调用的“值”部分,是我们都做了 Fuzztag 的支持。
在 Fuzztag 引入数据包的修改机制之后,数据包就出现了问题,到底是一个数据包呢?还是被修改成多个数据包呢?实际上,确实是多个数据包,我们可以将渲染出的一整批数据包同时发送出去(使用并发池),也可以仅查看数据包的结果(.Show() 函数),同样的,如果我们使用的时候大概确定只会渲染一个数据包,那么就可以理所当然地使用 FirstFuzzHTTPRequest() 或 FirstHTTPRequestBytes() 直接操作第一个数据包。
type palm/common/mutate.(FuzzHTTPRequestIf) struct {
// 发送所有渲染后的数据包
func Exec(v1 ...func httpPoolConfigOption(v1: *mutate.httpPoolConfig) ) return(chan *mutate._httpResult, error)
// 仅执行第一个数据包
func ExecFirst(v1 ...func httpPoolConfigOption(v1: *mutate.httpPoolConfig) ) return(*mutate._httpResult, error)
// 获取第一个数据包的结果,继续进行 fuzz
func FirstFuzzHTTPRequest() return(*mutate.FuzzHTTPRequest)
// 把第一个数据包的原始报文取出来,bytes (注:可当作 string 使用)
func FirstHTTPRequestBytes() return([]uint8)
...
...
// 一般用于调试,查看数据包的构造结果
// 可以在任意地方调用
func Show() return(mutate.FuzzHTTPRequestIf)
}
我们在了解核心原理和 API 之后,其实很容易可以构造出符合自己要求的数据包修改函数,那么为了方便大家复制粘贴修改,我举例了一些比较有用的函数库,如需使用,可随意复制。
/*
修改 XFF:分别是用随机 IP 或本地 IP
*/
addXFF = func(req) {
ft = `{{ri(1,255)}}.{{ri(0,255)}}.{{ri(0,255)}}.{{ri(0,255)}}`
return fuzz.MustHTTPRequest(req).FuzzHTTPHeader("X-Forwarded-For", ft).FuzzHTTPHeader("X-Real-IP", ft).FirstHTTPRequestBytes()
}
addXFFLocalhost = func(req) {
ft = `127.0.0.1`
return fuzz.MustHTTPRequest(req).FuzzHTTPHeader("X-Forwarded-For", ft).FuzzHTTPHeader("X-Real-IP", ft).FirstHTTPRequestBytes()
}
// 为流量增加基础认证
addBasicAuth = func(req) {
user, name = "admin", "123456"
result = `Basic ` + codec.EncodeBase64("%v:%v" % [user, name])
// Authorization: Basic Y2xpOmNsaTEyMy5jb20=
return fuzz.MustHTTPRequest(req).FuzzHTTPHeader("Authorization", result).FirstHTTPRequestBytes()
}
// 为流量新增一个 Cookie
addCookie = func(req) {
key = "isAdmin"
value = "true"
return fuzz.MustHTTPRequest(req).FuzzCookie(key, value).FirstHTTPRequestBytes()
}
// 为流量新增多个 Cookie
addMultiCookie = func(req) {
key = "isAdmin"
value = "true"
return fuzz.MustHTTPRequest(req).FuzzCookie(key, value).FuzzCookie("key2", "HHHHH-NewKey").FirstHTTPRequestBytes()
}
// 寻找数据包中的 Get Query 参数为 abc,值修改为 456
// 否则直接返回
changeGetParams = func(req) {
freq = fuzz.MustHTTPRequest(req)
if freq.GetMethod() != "GET" {
return req
}
if freq.GetQueryValue("abc") != "" {
return freq.FuzzGetParams("abc", "456").FirstHTTPRequestBytes()
}
return freq.FirstHTTPRequestBytes()
}
MITM 热加载的实际用途其实远不止于此,大家可以任意发挥想象力实现流量修改,或按需定制符合自己公司实际业务场景的热加载代码。
Yak 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ