CVE-VINEXT-CIVETWEB-PUT-SSI-RCE: Verified Repro With Script Download
CVE-VINEXT-CIVETWEB-PUT-SSI-RCE: CivetWeb PUT + SSI exec RCE
CVE-VINEXT-CIVETWEB-PUT-SSI-RCE is verified against civetweb/civetweb · github. Vulnerability class: RCE. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00229.
What Is CVE-VINEXT-CIVETWEB-PUT-SSI-RCE?
It is a high-severity authenticated remote code execution in CivetWeb that combines its HTTP PUT upload feature with Server-Side Include (SSI) #exec processing. An authenticated user can upload a .shtml file that runs a shell command when fetched. Pruva reproduced it (reproduction REPRO-2026-00229).
CVE-VINEXT-CIVETWEB-PUT-SSI-RCE Severity
CVE-VINEXT-CIVETWEB-PUT-SSI-RCE is rated high severity.
High — serious impact or readily exploitable. Prioritize remediation.
How to Reproduce CVE-VINEXT-CIVETWEB-PUT-SSI-RCE
pruva-verify REPRO-2026-00229 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00229/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-VINEXT-CIVETWEB-PUT-SSI-RCE
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
<!--#exec "id; uname -a" --> uploaded via authenticated HTTP PUT as /pwn.shtml
- HTTP PUT /pwn.shtml followed by HTTP GET /pwn.shtml on a CivetWeb instance with put_delete_auth_file and default ssi_pattern
Alternate triggers for CivetWeb authenticated PUT + SSI #exec RCE: chunked PUT, WebDAV MOVE, and .shtm extension
How the agent worked
Root Cause and Exploit Chain for CVE-VINEXT-CIVETWEB-PUT-SSI-RCE
CivetWeb’s default ssi_pattern causes files ending in .shtml or .shtm to be processed as Server Side Includes (SSI). When the put_delete_auth_file option is enabled, CivetWeb accepts authenticated HTTP PUT uploads. Because files uploaded via PUT live in the configured document_root, a subsequent GET request for a .shtml upload is handled by the SSI engine. The SSI parser calls do_ssi_exec() on <!--#exec "..."--> tags, which passes the command string directly to popen(). An authenticated attacker can therefore upload a .shtml file containing an SSI #exec directive and execute arbitrary shell commands on the server by requesting that file.
- Package/component affected: CivetWeb HTTP server (
civetwebexecutable,src/civetweb.c). - Affected versions: The ticket names commit
588860e3. That specific commit has a build-breaking syntax error inget_request()and does not compile. The immediately preceding parent commit (588860e3^1, also known as3309a6c) is the last working master revision and still contains the vulnerabledo_ssi_exec/handle_put_filecode path. The same behavior is present in588860e3once the unrelated syntax error is corrected. - Risk level and consequences: High. Any user with a valid digest credential for the
put_delete_auth_filerealm can upload and execute arbitrary shell commands as the CivetWeb process user, leading to full host compromise.
Impact Parity
- Disclosed/claimed maximum impact: Authenticated remote code execution via PUT upload of
.shtmlfollowed by GET. - Reproduced impact from this run: The reproduction script authenticated with digest credentials, PUT an
.shtmlpayload containing<!--#exec "id; uname -a" -->, and a subsequent GET returned the output ofidanduname -ain the HTTP response body. This proves the server executed attacker-controlled shell commands through the real HTTP API. - Parity:
full - Not demonstrated: N/A — the claimed code-execution path was demonstrated end-to-end through the real HTTP API.
Root Cause
The root cause is the interaction of two default/documented features without sufficient isolation:
- Authenticated PUT/DELETE support. When
put_delete_auth_fileis configured, CivetWeb allows any user with a valid digest credential to write arbitrary files into thedocument_root(handle_put_file()insrc/civetweb.c). - SSI
#execprocessing. The defaultssi_patternis**.shtml$|**.shtm$. When a requested file matches this pattern,send_ssi_file()scans the file for<!--#...-->tags. If the tag starts withexecandNO_POPENis not defined, it callsdo_ssi_exec(conn, buf + 9):
#if !defined(NO_POPEN)
static void
do_ssi_exec(struct mg_connection *conn, char *tag)
{
char cmd[1024] = "";
struct mg_file file = STRUCT_FILE_INITIALIZER;
if (sscanf(tag, " \"%1023[^\"]\"", cmd) != 1) {
mg_cry_internal(conn, "Bad SSI #exec: [%s]", tag);
} else {
cmd[1023] = 0;
if ((file.access.fp = popen(cmd, "r")) == NULL) {
mg_cry_internal(conn,
"Cannot SSI #exec: [%s]: %s",
cmd,
strerror(ERRNO));
} else {
send_file_data(conn, &file, 0, INT64_MAX, 0); /* send static file */
pclose(file.access.fp);
}
}
}
#endif /* !NO_POPEN */
The command string is extracted from the SSI tag and passed unmodified to popen(), which runs it with the privileges of the CivetWeb process. There is no additional authorization check for #exec beyond the SSI file pattern match, and no restriction on which files created via PUT may be treated as SSI.
- Link to fix commit: No upstream fix commit was identified in the repository at the time of this run. The current master (
588860e3) still contains thedo_ssi_execcode path and is only prevented from compiling by an unrelated syntax error inget_request().
Reproduction Steps
- Build the vulnerable server: Run
bundle/repro/reproduction_steps.sh. The script readsbundle/project_cache_context.json, clones or reuses the CivetWeb repository from the project cache, resolves the ticket-named commit588860e3, and uses its working parent588860e3^1(3309a6c) because the named commit does not compile. It builds two binaries: one default (vulnerable) and one with-DNO_POPEN(control). - Start the server: The script creates a digest password file (
admin:mydomain.com:<md5>) and starts CivetWeb withlistening_ports,document_root,put_delete_auth_file, andauthentication_domain. - Upload the payload: The script performs an authenticated HTTP PUT to
/pwn.shtmlwith the body<!--#exec "id; uname -a" -->. - Trigger execution: The script sends an authenticated HTTP GET to
/pwn.shtml. On the vulnerable build, the response body contains the output of the executed commands. On the-DNO_POPENbuild, the response body is empty because the#execdirective is not implemented. - Expected evidence:
bundle/artifacts/vulnerable-attempt1/get_body.txtcontainsuid=...andLinux ....bundle/artifacts/fixed-attempt1/get_body.txtdoes not contain either string.bundle/logs/reproduction_steps.logshows two vulnerable attempts returning command output and two fixed attempts returning no command output.
Evidence
- Log file:
bundle/logs/reproduction_steps.log - Vulnerable GET response body:
bundle/artifacts/vulnerable-attempt1/get_body.txt:uid=1000(vscode) gid=1000(vscode) groups=1000(vscode),962(962) Linux d778bdddc001 7.0.14-arch1-1 #1 SMP PREEMPT_DYNAMIC Sat, 27 Jun 2026 16:15:10 +0000 x86_64 GNU/Linux - Vulnerable GET response headers:
bundle/artifacts/vulnerable-attempt1/get_headers.txtshowsHTTP/1.1 200 OKandContent-Type: text/html. - Fixed GET response body:
bundle/artifacts/fixed-attempt1/get_body.txtis empty, confirming the#execpath is disabled whenNO_POPENis defined. - HTTP headers:
bundle/artifacts/vulnerable-attempt1/put_headers.txtandget_headers.txtshowHTTP/1.1 200 OKfor both PUT and GET. - Server logs:
bundle/artifacts/vulnerable-attempt1/server.logandbundle/artifacts/fixed-attempt1/server.logshow the CivetWeb startup and shutdown. - Environment: Reproduced on Linux x86_64 with gcc, using CivetWeb built from source at commit
3309a6c(588860e3^1).
Recommendations / Next Steps
- Short-term mitigation: Build CivetWeb with
-DNO_POPENto disable SSI#execentirely, or disable PUT/DELETE by not settingput_delete_auth_file. Alternatively, restrictssi_patternso that untrusted upload directories cannot match it. - Proper fix: Remove or gate the SSI
#execdirective behind an explicit opt-in option (e.g.,ssi_exec_enabled yes) that defaults tono. When enabled, restrict it to a dedicated, non-upload directory and/or require additional authorization. Alternatively, do not allow files uploaded via PUT to matchssi_patternunless explicitly whitelisted. - Upgrade guidance: There is no known upstream fixed version at the time of this report. Users should apply the
-DNO_POPENbuild flag or disable PUT/DELETE until a patched release is available. - Testing recommendations: Add an integration test that uploads an
.shtmlfile with#execand verifies that no command is executed, and that the response body does not contain shell command output.
Additional Notes
- Idempotency: The reproduction script was run twice consecutively from the same project-cache state and produced identical confirmation results in both runs.
- Commit discrepancy: The ticket names commit
588860e3as the vulnerable version. That commit has a build-breaking syntax error insrc/civetweb.c(get_request():if (h_chunk != NULL) && ...instead ofif ((h_chunk != NULL) && ...)plus an undeclaredclvariable). The reproduction therefore uses the immediately preceding working commit588860e3^1(3309a6c), which is the parent of588860e3and contains the same vulnerable SSI code path. - Limitations: The reproduction demonstrates authenticated RCE against a locally running CivetWeb instance. Actual deployments may differ in user privilege, authentication domain, or file-system layout, but the underlying SSI
#execmechanism is the same.
Variant Analysis & Alternative Triggers for CVE-VINEXT-CIVETWEB-PUT-SSI-RCE
The original reproduction confirmed that an authenticated CivetWeb user can upload a .shtml file containing <!--#exec "command"--> via HTTP PUT and then execute arbitrary shell commands by retrieving the file. This variant stage demonstrates that the same root cause (the SSI #exec sink calling popen()) is reachable through multiple alternate triggers: (1) Transfer-Encoding: chunked PUT framing, (2) WebDAV MOVE of an uploaded file to an SSI-matched extension, and (3) the .shtm extension matched by the default ssi_pattern. All three variants reproduced the same authenticated RCE on the vulnerable build. None of them bypassed the NO_POPEN control build, because the NO_POPEN flag removes the only command-execution sink. Therefore, this stage finds confirmed alternate triggers but no true bypass.
Fix Coverage / Assumptions
The reproduction stage did not use an upstream fix; it used a control build of the same vulnerable commit (3309a6c) compiled with -DNO_POPEN. That flag removes do_ssi_exec() and the #exec dispatch in send_ssi_file() in src/civetweb.c.
The control fix relies on this invariant:
The only way an attacker-controlled file can cause arbitrary command execution is through the SSI
#execdirective, which callspopen(). Ifpopen()is removed at compile time, the RCE cannot occur.
What the control fix covers:
- All
.shtml/.shtmfiles served by the SSI engine, regardless of how they were uploaded. - All HTTP body framing styles for PUT (
Content-LengthandTransfer-Encoding: chunked). - All WebDAV
MOVE/COPYoperations that rename files into SSI-matched extensions.
What the control fix does NOT cover (and what a real patch should address):
- The underlying trust boundary: authenticated users can still write arbitrary files that are later served by the same process.
- Other script-handling paths (CGI, Lua server pages, Duktape) that could execute uploaded content if the corresponding feature/interpreter is enabled.
- Path traversal in
SSI #include(a separate LFI concern, not RCE).
There is no upstream fix at the time of this analysis. The repository HEAD is at the same vulnerable commit 3309a6c, and the next commit 588860e3 ("Refactor request handling: don't allow chunked encoding and content length") has a syntax error in get_request() and does not compile.
Variant / Alternate Trigger
Three distinct triggers were tested against both the vulnerable and the NO_POPEN control builds.
Variant 1 — Chunked PUT framing
- Entry point:
PUT /pwn.shtmlwithTransfer-Encoding: chunkedand a body of<!--#exec "id; uname -a" -->. - Code path:
handle_request()→is_put_or_delete_request→put_file()→forward_body_data()(handles chunked) → GET triggerssend_ssi_file()→do_ssi_exec()→popen(). - Why it is a variant: The original reproduction used a
Content-LengthPUT. The vulnerableforward_body_data()supports chunked bodies, so the same malicious file reaches the same sink via a different HTTP framing.
Variant 2 — WebDAV MOVE
- Entry point:
PUT /pwn.txtwith the#execpayload, thenMOVE /pwn.txtwithDestination: /pwn.shtml. Requiresenable_webdav yes. - Code path:
PUTgoes throughput_file();MOVEis handled bydav_move_file()insrc/civetweb.c. The target path matches the defaultssi_pattern, so the subsequent GET callssend_ssi_file()→do_ssi_exec()→popen(). - Why it is a variant: It uses a different HTTP method (
MOVE) and a two-step workflow to place the payload in an SSI-matched file.
Variant 3 — .shtm extension
Entry point:
PUT /pwn.shtmwith the#execpayload, thenGET /pwn.shtm.Code path: The default
ssi_patternis**.shtml$|**.shtm$, sosend_ssi_file()processes the file and reaches the samedo_ssi_exec()sink.Why it is a variant: The same sink is reached through a different extension that is enabled by default.
Package/component affected: CivetWeb HTTP server (
src/civetweb.c,civetwebexecutable).Affected versions: The tested vulnerable revision is
3309a6c(master at the time of analysis). The next commit588860e3does not compile. No upstream fix exists yet.Risk level and consequences: High. Any authenticated user who can write files (via
PUTor WebDAVMOVE/COPY) can plant an SSI-matched file and execute arbitrary shell commands as the CivetWeb process user.
Impact Parity
- Disclosed/claimed maximum impact: Authenticated remote code execution via PUT upload of
.shtmlfollowed by GET. - Reproduced impact from this variant run: All three alternate triggers produced a
GETresponse body containing the output ofidanduname -aon the vulnerable build, proving command execution. - Parity:
fullfor the alternate triggers (same RCE impact as the original claim). - Not demonstrated: Persistence, lateral movement, or privilege escalation. No true bypass of the
NO_POPENcontrol was found.
Root Cause
The root cause is unchanged from the original reproduction: the default ssi_pattern causes files ending in .shtml or .shtm to be processed by send_ssi_file(), which unconditionally dispatches <!--#exec ...--> directives to do_ssi_exec(). That function passes the attacker-controlled command string directly to popen(). Because CivetWeb's put_file() accepts any file content (including SSI directives) when put_delete_auth_file is configured, the same process later serves and executes that content.
The NO_POPEN control mitigates this by compiling out the entire do_ssi_exec() function, so the SSI engine silently drops #exec directives. This is a compile-time removal of the sink, not a runtime fix for the trust-boundary issue.
Reproduction Steps
Run the stage script:
bash bundle/vuln_variant/reproduction_steps.sh
The script:
- Resolves or builds the vulnerable
civetwebbinary (commit3309a6c). - Resolves or builds the
NO_POPENcontrol binary from the same commit. - For each variant, starts a fresh CivetWeb instance on an isolated port, performs the authenticated upload, and then performs the GET.
- Checks whether the GET response body contains the output of
id; uname -a. - Repeats the same tests against the
NO_POPENbinary. - Exits
1(no bypass) because all variants work on the vulnerable build and are blocked by the control build.
Expected evidence on the vulnerable build:
vuln_variant/artifacts/vulnerable-chunked/get_body.txtcontains the command output.vuln_variant/artifacts/vulnerable-webdav/get_body.txtcontains the command output.vuln_variant/artifacts/vulnerable-shtm/get_body.txtcontains the command output.
Expected evidence on the fixed/control build:
- The corresponding
fixed-*/get_body.txtfiles are empty (the#execdirective is ignored).
Evidence
- Log:
bundle/logs/vuln_variant.log - Runtime manifest:
bundle/vuln_variant/runtime_manifest.json - Variant 1 (chunked PUT) evidence:
bundle/vuln_variant/artifacts/vulnerable-chunked/get_body.txt:uid=1000(vscode) gid=1000(vscode) groups=1000(vscode),962(962) Linux d778bdddc001 7.0.14-arch1-1 #1 SMP PREEMPT_DYNAMIC Sat, 27 Jun 2026 16:15:10 +0000 x86_64 GNU/Linuxbundle/vuln_variant/artifacts/fixed-chunked/get_body.txtis empty.
- Variant 2 (WebDAV MOVE) evidence:
bundle/vuln_variant/artifacts/vulnerable-webdav/get_body.txtcontains the sameid/uname -aoutput.bundle/vuln_variant/artifacts/fixed-webdav/get_body.txtis empty.
- Variant 3 (
.shtm) evidence:bundle/vuln_variant/artifacts/vulnerable-shtm/get_body.txtcontains the sameid/uname -aoutput.bundle/vuln_variant/artifacts/fixed-shtm/get_body.txtis empty.
Environment details captured:
- Vulnerable source commit:
3309a6cac05335aa4371a0c3750b42fbe05d3cb4 - Fixed/control build: same commit, compiled with
-DNO_POPEN - CivetWeb version string:
CivetWeb V1.17
Recommendations / Next Steps
To produce a complete fix for the underlying issue, not just the NO_POPEN control:
- Disable SSI
#execby default or guard it behind a dedicated runtime option (e.g.,enable_ssi_exec) that defaults tono. This preserves#includefor static includes while removing the command-execution surface. - Decouple upload destinations from script/SSI paths. If a directory is writable via PUT, do not serve SSI or CGI from that directory unless explicitly enabled.
- Apply the same authorization to GET of SSI/CGI files as to PUT. The current code authorizes PUT via
put_delete_auth_file, but GET of the resulting.shtmlrelies on other auth mechanisms that may not be configured. - Harden
do_ssi_includeby applyingis_in_script_pathor similar checks to prevent path traversal viafile=andvirtual=(a separate LFI issue).
Additional Notes
- Idempotency: The script was run twice in a row and produced the same results both times (exit code
1, all variants reproduced on vulnerable, none on fixed). - Trust boundary: The attack requires valid credentials for the
put_delete_auth_filerealm. The WebDAV variant additionally requires the administrator to setenable_webdav yes, which is disabled by default. - No bypass: The
NO_POPENcontrol is sufficient to stop the SSI#execRCE. A true bypass would require an alternative command-execution sink reachable from an uploaded file, none of which are enabled in the default build.
CVE-VINEXT-CIVETWEB-PUT-SSI-RCE Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
git --version && which curl && which jq && which make && which gcc && which cmake && which opensslgit version 2.55.0 /usr/bin/curl /usr/bin/jq /usr/bin/make /usr/bin/gcc /usr/bin/cmake /usr/bin/openssl
Artifacts and Evidence for CVE-VINEXT-CIVETWEB-PUT-SSI-RCE
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-VINEXT-CIVETWEB-PUT-SSI-RCE
FAQ: CVE-VINEXT-CIVETWEB-PUT-SSI-RCE
What configuration makes CivetWeb exploitable here?
How severe is the CivetWeb PUT + SSI #exec issue?
How can I reproduce it?
References for CVE-VINEXT-CIVETWEB-PUT-SSI-RCE
Authoritative sources for CVE-VINEXT-CIVETWEB-PUT-SSI-RCE — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.