背景
用户在使用 Yakit MITM 功能的时候,经常会遇到一些特殊需求:
我的数据包需要携带一些特征变量才能访问,但是浏览器无法做到,我可以批量修改流量新增某一个 Header 吗?
我可以在代理层面在所有流量中新增一个参数吗?
这类需要动态修改流量并放行的需求如果手动操作的话,非常复杂。需要劫持到数据包,然后修改数据包,手动放行,不是不可以完成,而是这样的操作并不是自动化的,人工操作多了很容易疲倦漏掉一些关键信息。
熟悉 Yakit 的用户,尤其是一些深度用户非常熟悉这种操作,我们可以点击 “热加载”,在恰当的 Hook 点编写我们希望操作流量做的事儿,然后加载进引擎中,等待流量执行。
通过一个简单的图例展示热加载代码在流量劫持中的过程:
实战案例:使用XFF绕过IP封禁
如果在测试过程中,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 的标志,意味着我们的热加载代码被引擎接受了,并加载到了内存中。
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
模块中修改数据包的接口和特色。
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 集成
Fuzztag 作为伴随 Yaklang 一直成长的特性,同时也作为fuzz 原生支持的功能,在fuzz.HTTPRequest 中也理所当然的被支持,所以我们可以使用如下 fuzztag 来随机生成一个 IP 地址:
{{randint(1,255)}}.{{randint(0,255)}}.{{randint(0,255)}}.{{randint(0,255)}}
或者定制其他数据。
这也是我们addXFF 函数添加随机 IP 进入 XFF 的依据,一般来说,在 Fuzz.. 函数被调用的“值”部分,是我们都做了 Fuzztag 的支持。
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()
}
增加 Basic Auth
// 为流量增加基础认证
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
// 为流量新增一个 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 热加载的实际用途其实远不止于此,大家可以任意发挥想象力实现流量修改,或按需定制符合自己公司实际业务场景的热加载代码。