curl · Report #3973143
22: FTP wildcard matching decodes server-provided filenames, enabling directory traversal
How it was found
Summary
When CURLOPT_WILDCARDMATCH is enabled, libcurl concatenates the server-provided LIST filename directly onto the wildcard path and then URL-decodes the combined string. A filename containing percent-encoded path separators (e.g. %2e%2e%2fsecret.txt) is decoded into ../secret.txt, causing libcurl to traverse out of the intended directory and retrieve a different file. The transfer succeeds with CURLE_OK — the application has no indication it received the wrong file.
Affected versions
curl 7.21.0 through 8.21.0, plus tested 8.22.0 prerelease/development versions.
Root cause
In lib/ftp.c, wc_statemach() builds the download path by concatenating the untrusted LIST filename directly:
char *tmp_path = curl_maprintf("%s%s", wildcard->path, finfo->filename);
This combined string is then installed as ftp->path and passed to ftp_parse_url_path(), which URL-decodes the entire path via Curl_urldecode() before splitting it into directory components. A literal LIST filename %2e%2e%2fsecret.txt becomes ../secret.txt after decoding, introducing a CWD .. that escapes the wildcard directory.
Impact
On a shared FTP service, an unprivileged tenant who can create a filename in a wildcarded directory can cause a more-privileged client to silently retrieve a file outside that directory. The traversal does not bypass FTP server authentication — the victim's credentials must already authorize the read — but it breaks the application's scoping assumption about which files a wildcard transfer returns.
This is a Low severity issue: CURLOPT_WILDCARDMATCH is opt-in, and exploitation requires a shared FTP environment with specific access conditions. It is not API misuse — the URL ftp://host/shared/* is the documented usage pattern.
Reproduction
- Build an affected curl release, starting with 7.21.0, with FTP and wildcard-matching support enabled.
- Configure an FTP account that can list and retrieve files from a directory such as
/sharedand can also read a distinct target file outside it, such as/secret.txt. - Give an unprivileged test user or tenant the ability to create filenames inside
/sharedbut not to read/secret.txt. - In
/shared, create a file whose literal name contains percent-encoded traversal characters, such as%2e%2e%2fsecret.txt. - Give the literal file content that is clearly distinguishable from
/secret.txt. If the FTP listing reports file sizes and curl enforces them, make both files the same size so the result is not obscured by a partial-transfer error. - Ensure the FTP server's
LISTresponse returns the literal percent-bearing filename exactly as stored. - Create a libcurl transfer for a wildcard URL whose final component matches that filename, such as
ftp://host/shared/*. - Enable
CURLOPT_WILDCARDMATCHand authenticate using the broader-read FTP account. - Perform the transfer while recording the FTP control commands and downloaded bytes.
- Observe the vulnerable result: after listing
/shared, curl URL-decodes the matched filename into../secret.txt, issues a parent-directory change followed byRETR secret.txt, and returns the contents of/secret.txt. - Confirm that
curl_easy_perform()reports success, leaving the application unaware that the retrieved object was outside the wildcarded directory. - As a control, disable wildcard matching and directly request the literal percent-bearing filename by encoding each percent sign as
%25, for example/shared/%252e%252e%252fsecret.txt. - Confirm that the control remains in
/shared, retrieves the literal attacker-created file, and does not issue a parent-directory change. - Confirm a fix by encoding the server-provided listing filename as one opaque URL path segment before combining it with the wildcard path and reparsing it.
- Repeat the wildcard transfer and verify that curl retrieves the literal file in
/shared, never issuesCWD .., and never returns/secret.txt.
Expected result
A filename obtained from an FTP directory listing must remain one opaque filename. Percent sequences in that filename must not introduce directory components when curl retrieves the matched file.
Vulnerable result
Curl concatenates the untrusted listing filename with the wildcard path and URL-decodes the result. Encoded traversal characters become ../, causing retrieval outside the application-selected directory.
Suggested fix
Percent-encode the server-provided finfo->filename as a single opaque path segment before concatenating it with wildcard->path. At minimum, encode literal % as %25 and / as %2F so that ftp_parse_url_path() decodes it back to exactly the original filename without introducing new path components.
Impact
Summary:
On a shared FTP service, an attacker who can create filenames inside a wildcarded directory can cause a more-privileged curl client to retrieve a file outside that directory. If the application stores, processes, or exposes the returned bytes, this can disclose files accessible to the victim’s FTP account, although it does not bypass the server’s underlying permissions.