CVE-2025-24150
Overview
Background
- Copy as cURL
- A Web Inspector Network-tab feature that serializes a recorded HTTP request into an equivalent curl command line for the user to paste into a terminal.
- escapeStringPosix
- A WebInspectorUI helper that wraps a string in POSIX single quotes and escapes embedded quotes so shell metacharacters in the value are not interpreted by the shell.
- curl --data-binary vs --data-raw
- Both send a request body, but
--data/--data-binarytreat a leading ‘@’ as a filename to read, whereas--data-rawsends the argument literally with no ‘@’ interpretation. - Argument injection
- An attack where attacker-controlled data becomes part of a command’s arguments and is interpreted by the invoked program in a way the caller did not intend, even without classic shell metacharacter injection.
Root Cause Analysis
Web Inspector’s Network tab offers a “Copy as cURL” action, implemented in WI.Resource’s command-generation code (Source/WebInspectorUI/UserInterface/Models/Resource.js). When the recorded request carries a body, the generated command appends the request body as a curl data option. For non-form-encoded bodies the old code emitted --data-binary <escaped-body>, where escapeStringPosix() wraps the body in POSIX single quotes to neutralize shell metacharacters. The violated invariant is that the copied command string, when pasted into a shell, must transmit exactly the bytes the page sent and must not cause curl to perform any additional filesystem or side-effecting action. The flaw is that curl’s --data / --data-binary options treat a leading @ in their argument as a request to read the argument’s remainder as a filename (or - as stdin), and shell single-quoting does NOT stop this: the quotes are stripped by the shell, and curl itself then interprets the resulting literal @... string as a file path. So a request body beginning with @ — fully attacker-controlled by the page under inspection (e.g. via fetch/XHR POST) — would make the pasted command read an arbitrary local file (like @/etc/passwd) and upload it, or otherwise act on a filename the victim never intended.
The fix replaces --data-binary with --data-raw; per curl semantics --data-raw is identical to --data except it explicitly does NOT interpret a leading @, so the body is always sent verbatim. The LayoutTest is updated in lockstep to expect --data-raw '{"update":"now"}'. This is not memory corruption; it is an argument-injection / command-construction logic bug where the escaping layer (shell quoting) did not cover the actual sink (curl’s own @-filename parsing).
--data-binary/--data options interpret a leading ‘@’ as a filename after the shell strips the quotes — switching to --data-raw moves the body to a sink that never performs that interpretation.Attack Path
- Serve a page with a crafted request body The attacker controls or influences a web page and causes it to issue an HTTP request whose body begins with ‘@’, e.g. a POST with body ‘@/etc/passwd’ or ‘@~/.ssh/id_rsa’, via fetch() or XMLHttpRequest.
- Victim opens Web Inspector on the request A developer or user inspecting the site opens the Network tab, which records the request and its body.
- Victim uses Copy as cURL
The victim right-clicks the request and selects Copy as cURL; the pre-patch code builds
--data-binary '@/etc/passwd'. - Victim pastes into a shell
The shell strips the single quotes, leaving curl with the literal argument
@/etc/passwd. - curl reads and exfiltrates a local file curl interprets the leading ‘@’ as a file reference, reads the named local file, and sends its contents to the request’s URL (attacker-controlled), disclosing arbitrary local files; other ‘@’/’-’ tricks can redirect input in unintended ways.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
WI.Resource.prototype (Copy as cURL command builder)Source/WebInspectorUI/UserInterface/Models/Resource.js |
modified | Changed the non-form-encoded body branch to push `--data-raw <escaped>` instead of `--data-binary <escaped>`, so curl no longer treats a leading '@' in the body as a filename. |
test (copy-as-curl layout test)LayoutTests/http/tests/inspector/network/copy-as-curl.html |
modified | Updated the expectation from `--data-binary '{...}'` to `--data-raw '{...}'` to match the corrected output. |
Audit Directions
- Other curl option builders in Resource.jsAudit the same Copy-as-cURL generator for any other option whose argument curl parses specially even when quoted; grep the file for ‘command.push(’ and every ‘–’ curl flag (e.g.
--data,-d,--form/-F,--header,--url,--config) and check whether an attacker-controlled leading ‘@’, ‘-’, or ‘=’ could trigger file reads or config loading. - escapeStringPosix reliance across WebInspectorUISearch the WebInspectorUI tree for callers of escapeStringPosix and any command/serialization export (Copy as fetch, Copy as PowerShell, HAR export) that assume shell quoting alone sanitizes values; verify each downstream consumer does not itself interpret sigils like ‘@’.
- Cross-tool 'copy as command' featuresLook for analogous ‘copy as <tool>’ serializers elsewhere (grep for ‘–data-binary’, ‘as-curl’, ‘copyAs’, ’toCurl’) in WebKit and confirm none reintroduce the ‘@’-filename or ‘-’-stdin interpretation for attacker-controlled bodies or headers.