✓ProveIt
Examples / Report #1892351 Saved analysis

CVE-2023-27534: SFTP path ~ resolving discrepancy

curl · 2023-03-0513.3s analysis · $0.014 in model calls

What the report claims

libcurl's Curl_getworkingpath function incorrectly resolves '~' in SFTP paths, converting '/~/' to the remote user's home directory while not handling '/~/' or '~<something>' correctly, potentially allowing path traversal bypasses.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2023-27534 SFTP path ~ resolving discrepancy

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Code supports the claim

The code supports the reported flaw. A maintainer should review its impact and decide what to do next.

Why this finding?
  • SFTP path handling at lib/curl_path.c:64 accepts any '/~...' prefix, not just '/~/' or '/~', unlike the SCP handling at line 57 and the documented behavior.
  • This allows '/~a..' to be transformed into '/home/user/../other/file', creating a path-traversal primitive that can bypass application-level '..' filtering.
  • The flaw is reachable via attacker-controlled SFTP URL path and gives the attacker access to parent directories of the home directory.
How this analysis ran 5 claims · 7 citations verified

Checked against curl master on 2023-03-05 (e135bc9d31ad). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 2 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 1422 tokens · $0.0002
3 · Evidence
1 code snippets
real curl source at e135bc9d31ad · 2 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
6955 tokens · 10.2s · $0.0136
5 · Receipts
7 of 7 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the report. The code in lib/curl_path.c confirms the discrepancy: the SFTP branch (line 64) treats any path starting with '/~' as home-relative, while the SCP branch (line 57) and the documentation only specify '/~/' or '/~'. This allows '/~a..' to become '/home/user/../other/file', bypassing '..' filters. We will treat this as a valid security issue and work on a fix that restricts SFTP tilde expansion to '/~/' and '/~' only.

Inspect all claims and source code 5 claims · 1 code excerpts

Report claims

C1flaw supported

Curl_getworkingpath resolves '~' in SFTP paths to remote users' home directory in an undocumented way

Read the reasoning

The SFTP code at line 64 uses a broad check (second character is '~') that matches '/~a..', '/~user', etc., while the comment at line 50 and the SCP implementation suggest the intent was to handle only '/~/' or '/~'. This discrepancy is the undocumented behavior.

C2location not verifiable

The function is said to convert '/~/' to remote user's home directory

Read the reasoning

The report cites https://everything.curl.dev/usingcurl/scpsftp as stating '/~/' is converted to the home directory. The provided external sources (E1, E2) are unrelated pages and do not contain the curl documentation. Without the actual documentation excerpt, this claim about what the docs say cannot be verified from the given material.

C3trigger supported

Accessing 'sftp://host/~a../other/file' triggers the path resolution

Read the reasoning

The URL path 'sftp://host/~a../other/file' becomes working_path '/~a../other/file' after urldecode. The second character is '~', so the SFTP branch at line 64 executes.

C4impact supported

Allows an attacker with partial path access to gain access to untended remote system path locations

Read the reasoning

For '/~a../other/file', working_path+3 points to '..', so the result is '/home/user/../other/file'. An application that filters '..' path components but allows '~' prefixes would be bypassed, because '~a..' is a single path component that does not contain '..' until after curl's substitution. This is a plausible path-traversal vector.

C5reproduction supported

Remote path results as '/home/user/../other/file' when using '~a..' component

Read the reasoning

The concatenation yields '/home/user/' + '../other/file' = '/home/user/../other/file', exactly as the report states.

What the report says about other projects

not addressed The curl documentation states that '~' in SFTP paths is resolved to the remote user's home directory only when used as '/~/' or '/~' (full path).

Read the original report

Full report on HackerOne ↗

## Summary: libcurl `Curl_getworkingpath` function resolves `~` as remote users' home directory. This routine behaves in an undocumented way for `sftp` protocol. In particular it is said that `/~/` is converted to remote user's home directory (*1), while this isn't how the function actually behaves. This can lead to unexpected final path for the `sftp` access, and allow an attacker with partial path access to gain access to untended remote system path locations. ## Steps To Reproduce: 1. access `sftp://host/~a../other/file` 2. remote path will result as: `/home/user/../other/file` It's notable that when `~a..` path component is checked for path traversal via normal unix path resolving rules, the path component is **not** considered accessing a parent directory, and thus will bypass path sanitization operations attempting to disallow access to parent directory. As an additional remark, in regular UNIXy world `~user/` specifies another users' home directory, which clearly is not supported by `sftp`. This adds to potential confusion. ## Remediation - In `Curl_getworkingpath` convert `~` in `sftp` paths only when either `/~/` or `/~` (if it is the full path) is used. Reject `~<something>` paths (or don't convert them). ## Supporting Material/References: 1) https://everything.curl.dev/usingcurl/scpsftp ## Impact Bypassing application implemented path filtering.

Source code

lib/curl_path.clines 36–101 · definition of Curl_getworkingpath
36CURLcode Curl_getworkingpath(struct Curl_easy *data,
37 char *homedir, /* when SFTP is used */
38 char **path) /* returns the allocated
39 real path to work with */
40{
41 char *real_path = NULL;
42 char *working_path;
43 size_t working_path_len;
44 CURLcode result =
45 Curl_urldecode(data->state.up.path, 0, &working_path,
46 &working_path_len, REJECT_ZERO);
47 if(result)
48 return result;
49
50 /* Check for /~/, indicating relative to the user's home directory */
51 if(data->conn->handler->protocol & CURLPROTO_SCP) {
52 real_path = malloc(working_path_len + 1);
53 if(!real_path) {
54 free(working_path);
55 return CURLE_OUT_OF_MEMORY;
56 }
57 if((working_path_len > 3) && (!memcmp(working_path, "/~/", 3)))
C1: SCP branch by contrast checks specifically for '/~/' via memcmp(working_path, "/~/", 3)
58 /* It is referenced to the home directory, so strip the leading '/~/' */
59 memcpy(real_path, working_path + 3, working_path_len - 2);
60 else
61 memcpy(real_path, working_path, 1 + working_path_len);
62 }
63 else if(data->conn->handler->protocol & CURLPROTO_SFTP) {
64 if((working_path_len > 1) && (working_path[1] == '~')) {
C1: SFTP branch triggers on any path where working_path[1] == '~' (i.e., '/~...'), not only '/~/' or '/~'
C3: Condition (working_path_len > 1) && (working_path[1] == '~') is true for '/~a../other/file' (second char is '~')
65 size_t homelen = strlen(homedir);
66 real_path = malloc(homelen + working_path_len + 1);
67 if(!real_path) {
68 free(working_path);
69 return CURLE_OUT_OF_MEMORY;
70 }
71 /* It is referenced to the home directory, so strip the
C4: Code copies homedir (e.g., '/home/user'), adds '/', then appends working_path+3
72 leading '/' */
73 memcpy(real_path, homedir, homelen);
C5: Copies homedir (e.g., '/home/user') then adds '/' at lines 75-78
74 /* Only add a trailing '/' if homedir does not end with one */
75 if(homelen == 0 || real_path[homelen - 1] != '/') {
76 real_path[homelen] = '/';
77 homelen++;
78 real_path[homelen] = '\0';
79 }
80 if(working_path_len > 3) {
81 memcpy(real_path + homelen, working_path + 3,
C4: memcpy(real_path + homelen, working_path + 3, ...) copies from the 4th character onward
C5: Copies from working_path+3; for '/~a../other/file' that is '../other/file'
82 1 + working_path_len -3);
83 }
84 }
85 else {
86 real_path = malloc(working_path_len + 1);
87 if(!real_path) {
88 free(working_path);
89 return CURLE_OUT_OF_MEMORY;
90 }
91 memcpy(real_path, working_path, 1 + working_path_len);
92 }
93 }
94
95 free(working_path);
96
97 /* store the pointer for the caller to receive */
98 *path = real_path;
99
100 return CURLE_OK;
101}