-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 NEFARIOUSPLAN-CANONICAL-V1 {"body_md":"## CVE-2023-50094 was two bugs, or maybe three, depending on who you ask\n\nGitHub advisory `GHSA-fx7f-f735-vgh4` describes CVE-2023-50094 as authenticated command injection in reNgine's WAF detector. The vulnerable code is in `web/api/views.py`, pre-patch:\n\n```python\nclass WafDetector(APIView):\n def get(self, request):\n url = req.query_params.get('url')\n response = {}\n response['status'] = False\n\n wafw00f_command = f'wafw00f {url}'\n output = subprocess.check_output(wafw00f_command, shell=True)\n```\n\n`shell=True` plus an f-string. An attacker with a valid session hits `/api/tools/waf_detector/?url=x;id` and the shell runs `id` on the container as root. The advisory credits `@n-thumann`.\n\nGitHub advisory `GHSA-96q4-fj2m-jqf7` describes CVE-2023-50094 as stored XSS via DNS record poisoning in the Vulnerability Page. The reporter is `@touhidshaikh`. Different file, different sink, different mechanism, same CVE number.\n\nThe independent writeup at `mattz.io/posts/cve-2023-50094/` attributes CVE-2023-50094 to a researcher who says they reported the WAF command injection via huntr.dev in February 2023 and went public in December 2023 after seventeen months without a response. That claim sits next to the GitHub advisory that credits `@n-thumann` for the same bug. One of those is the original disclosure. The other is a redisclosure, or a parallel discovery, or a credit dispute. The CVE record does not resolve it.\n\nThe changelog commit that makes the current state legible is `b382685`, authored by `@shelbyc` on August 30, 2024:\n\n```diff\n- * (Security) CVE-2024-41661 Stored Cross-Site Scripting (XSS) via DNS Record Poisoning\n+ * (Security) CVE-2023-50094 Stored Cross-Site Scripting (XSS) via DNS Record Poisoning\n...\n- * (Security) CVE-2024-41661 Fix Authenticated command injection in WAF detection tool\n+ * (Security) CVE-2023-50094 Fix Authenticated command injection in WAF detection tool\n```\n\nThe commit message explains: \"On 29 August 2024, MITRE informed GitHub that CVE-2024-41661, which was issued August 2024 is a duplicate of CVE-2023-50094.\" MITRE's position was that the earlier CVE number preempts the later. The effect on the record is that a CVE originally issued for one bug now names two different bugs in two different files reported by two different researchers. The CVE ID, going forward, is the set of all bugs that had to be filed under it.\n\n## The Nuclei template exploits a bug neither of those covers\n\nZierax's `CVE-2023-50094_POC` repository on GitHub is the basis for the Nuclei template. `poc.py`, line 30:\n\n```python\ndef modify_scan_engine(base_url, cookies, scan_engine_id):\n url = f\"{base_url}/api/scanengine/{scan_engine_id}/\"\n headers = {\"Content-Type\": \"application/json\"}\n data = {\"nmap_cmd\": payload}\n response = requests.patch(url, cookies=cookies, json=data, headers=headers)\n```\n\nThe payload modifies a stored scan engine's `nmap_cmd` field. Nothing runs yet. Line 59 of the same file prints: \"Payload injected. Start a scan using the modified Scan Engine.\" The actual command injection fires the next time someone runs a scan with this engine, at which point `web/reNgine/tasks.py` composes an `nmap` invocation by concatenating the stored `nmap_cmd` into a shell and executes.\n\nThis is a real bug in a real sink. It is not CVE-2023-50094. The first fifteen lines of Zierax's own README say so:\n\n```\n- **CVE ID**: TBD\n- **Affected Version**: reNgine v2.2.0\n...\n- **Author on exploit-db**: Caner Tercan\n- **Author of the POC**: Ziad (me)\n```\n\nThe author of the PoC, in their own documentation, writes the CVE field as unassigned and credits the original finding to a different author on exploit-db. The `CVE-2023-50094` label is in the repository name only.\n\nThe Nuclei template inherits the label and loses the PoC's disclaimer:\n\n```\nPOST /scan-engine/update HTTP/1.1\nHost: {{Hostname}}\nContent-Type: application/json\n\n{\"nmap_cmd\": 'curl {{interactsh-url}}'}\n```\n\nThe matcher requires `interactsh_protocol_2 == \"dns\"` AND `status_code_2 == 200`. Both must be true for a hit.\n\nNeither condition is reachable against reNgine. The root URLconf mounts `scanEngine.urls` under `/scanEngine/`, camelCase; the template uses `/scan-engine/` with a hyphen. The update view is registered as `/update/`, not `update`. The form the view instantiates is `UpdateEngineForm`, which accepts `yaml_configuration` and `engine_name` as Django-encoded fields, not a JSON body with an `nmap_cmd` key. The HTTP request the template sends gets a 404 from the Django router before any reNgine code reads the body. No subprocess runs. No DNS callback leaves the host. The matcher cannot fire against a real deployment.\n\nZierax's own PoC has the same problem. `/api/scanengine/{id}/` is not a registered API route in any released reNgine version, including the 2.2.0 the PoC claims to target. The REST router in `web/api/urls.py` lists every `ViewSet`: `listDatatableSubdomain`, `listTargets`, `listSubdomains`, `listEndpoints`, `listDirectories`, `listVulnerability`, and eleven more. None of them is `scanengine`. The scan-engine update remains a traditional Django form view at `/scanEngine//update//`.\n\nSo the template, authored November 5, 2024, attacks a URL that does not exist, in a codebase whose bug it names has already been patched, against a CVE that does not cover the sink the payload gestures at. It will never return a true positive. It will never return a false positive. It is inert.\n\nWhat the template does accomplish is indexing. Scanning infrastructure indexes Nuclei templates by the CVE ID in the filename. When CVE-2023-50094 appears in an advisory feed, scanner coverage automatically includes this template, because this template is `CVE-2023-50094.yaml`. The coverage metric goes up. The coverage is zero. The number going up is the product.\n\n## The same class, seven times in three years\n\nStep back from CVE-2023-50094 and read the reNgine `SECURITY.md` as a list. Counting only the command injection bugs against the commit history that patches them:\n\n| Patched | Location | Reporter | CVE |\n|-----------|--------------------|--------------------------------|------------------|\n| May 2022 | CMS Detector | `@ph33rr` | none |\n| May 2022 | Subdomain gathering | none listed | none |\n| May 2022 | Proxy | `@k0enm` | none |\n| May 2022 | YAML Engine | `@k0enm` + `@zongdeiqianxing` | none |\n| Jul 2024 | WAF Detector | `@n-thumann` | CVE-2023-50094 |\n| Jul 2024 | netlas whois | none listed | none |\n| Feb 2025 | `nmap_cmd` | `@gonzaless95` | none |\n\nSeven command injection fixes. One CVE for the class.\n\nThe commits rhyme. The CMS Detector fix in `8277cec0` replaces `os.system(cms_detector_command)` with a `subprocess.Popen` taking a pre-split argv. The WAF Detector fix in `109ae17` adds `shlex.quote` around the one parameter and switches from `shell=True` to an argv list. The netlas whois fix in `d7ca7414` the same day reroutes the `subprocess.check_output(command.split())` call through the central `run_command` utility, whose signature accepts a raw string and a `shell=True` flag. The `nmap_cmd` fix in `6ad0b191` seven months later adds a bespoke `is_valid_nmap_command` function that rejects any command not starting with `nmap` and denies the set `{';', '&', '|', '>', '<', '` + \"`\" + `', '$', '(', ')', '#', '\\\\'}`.\n\nNone of the fixes change the central primitive. `web/reNgine/tasks.py` on HEAD contains seventeen call sites that pass `shell=True` to `subprocess.Popen`. The signature of `run_command`, which is where the netlas whois fix routed its input, is unchanged: a raw string command and a flag to decide whether to pass it through a shell. Every caller that passes `shell=True` is a potential next entry in the table.\n\nThe maintainer's February 9, 2025 notice, posted at the top of `SECURITY.md`, is legible in this light:\n\n> reNgine is currently undergoing a major refactoring to address all XSS-related vulnerabilities. While we are committed to security, we are temporarily suspending new XSS vulnerability reports until this refactoring is complete.\n\nThe notice is about XSS, not command injection, but the admission shape is the same. The patch-as-you-find-it model has stopped converging on XSS. The maintainer is saying out loud that the class has exhausted patch-level fixes and needs refactoring. The command injection class has produced six patches and a seventh instance that scanning tooling now points at without a CVE number.\n\nThis is [design-debt-driver](/patterns/design-debt-driver) in the shape [the pattern's mechanism](/patterns/design-debt-driver) describes literally. reNgine's architecture lets user input route into shell commands at dozens of places. Each place is a bug the first time it is read by an outside researcher. Each place gets its own patch. The class does not retire. Attackers know where the next one lives. Defenders track the CVE list and find out later.\n\n## The CVE number doesn't select a bug. It selects a drawer.\n\nThe scanner that picked this candidate up reported `CVE-2023-50094` with \"nuclei template+2, CVSS8.8+1, AV:N+1\" as the reasons. The CVSS vector on the CVE record is `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`. That vector was computed for `@n-thumann`'s WAF detector bug. It was not recomputed after `@touhidshaikh`'s XSS was merged in; XSS via a stored DNS record does not have the same confidentiality/integrity/availability profile as a root shell on a container. The vector also does not describe `@gonzaless95`'s `nmap_cmd` bug, which requires a different set of privileges to reach and hits a different impact surface. It does not describe whatever Zierax's template thinks it is testing. A CVSS vector is attached to a CVE number; the CVE number is a drawer; the vector describes one item in the drawer and nothing else.\n\nThe PoC repository says `TBD`. The Nuclei template says `CVE-2023-50094`. The GitHub advisory for the WAF detector bug says `CVE-2023-50094`. The GitHub advisory for the DNS-record XSS also says `CVE-2023-50094`. The changelog before August 29, 2024 said `CVE-2024-41661`. The reporter in the CVE record is not the reporter on the blog. The reporter on the blog is not the reporter on the patch. The reporter on the patch for the bug the Nuclei template is actually trying to hit has no CVE at all.\n\nA CVE ID is supposed to be a pointer. This one points at a bag of bugs separated by several reporters, two years, six files, and one MITRE merge.\n\nThe patch for any one entry in that bag closes one instance. The architecture the bag shares, seventeen `shell=True` call sites routed through a utility whose signature takes a string and a shell flag, ships untouched.\n\nPoC: [Zierax/CVE-2023-50094_POC](https://github.com/Zierax/CVE-2023-50094_POC) and [projectdiscovery/nuclei-templates CVE-2023-50094.yaml](https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2023/CVE-2023-50094.yaml)","closing_line":"reNgine is not running out of command injection. reNgine is running out of CVE numbers to put them under.","hook_md":"The Nuclei template `CVE-2023-50094.yaml` authenticates to reNgine, sends `POST /scan-engine/update` with JSON `{\"nmap_cmd\": \"curl {{interactsh-url}}\"}`, and treats a DNS callback as confirmation. The path `/scan-engine/update` does not exist in reNgine; the real scan-engine update path is `/scanEngine//update/` and takes Django-form `yaml_configuration`, not JSON `nmap_cmd`. The sink the payload gestures at, `nmap_cmd` concatenated into a subprocess call, was patched seven months after this template was authored, by a different reporter, under no CVE at all. The PoC repository the template was derived from says `CVE ID: TBD` on line 15 of its own README and attributes the original finding to an unrelated author on exploit-db.\n\nCVE-2023-50094 is not a bug. It is a drawer. Enough different bugs have been filed under it that the ID no longer selects one.","post_id":41,"slug":"rengine-cve-2023-50094-is-a-drawer","title":"CVE-2023-50094: The CVE Number Is a Drawer, and reNgine Put Seven Bugs In It","type":"initial","unreadable_sentence":"reNgine is not running out of command injection. reNgine is running out of CVE numbers to put them under."} -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQRf0htP5+SjynlxywneZjl4jgkQJgUCaf4WmQAKCRDeZjl4jgkQ JoBoAP4jWLfHyEvyDL8NCdFTtiPI7z34hUaICYc4FMV7PK20IQEA9/vt18qhAh3m Lf3v4PA1J+ZIku24pbfGqWDgBmaArQg= =ENcu -----END PGP SIGNATURE-----