ftp: reject PWD responses containing control characters

A malicious or compromised FTP server could include control characters
(e.g. bare \r, or bytes 0x01-0x1f/0x7f) inside the quoted directory path
of its 257 PWD response. That string is stored verbatim as
ftpc->entrypath and later sent unescaped in a CWD command on connection
reuse via Curl_pp_sendf(), which performs no sanitization before
appending \r\n.

Reject the entire path if any control character is encountered during
extraction so that tainted data never reaches a subsequent FTP command.

Add test case 3217 and 3218 to verify. Adjusted test 1152 accordingly.

Closes #20949
This commit is contained in:
Ercan Ermis 2026-03-17 09:47:24 +01:00 committed by Daniel Stenberg
parent 650b33a3db
commit c3f04e76ae
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
5 changed files with 102 additions and 25 deletions

View File

@ -68,6 +68,7 @@
#include "curlx/strdup.h"
#include "curlx/strerr.h"
#include "curlx/strparse.h"
#include "curl_ctype.h"
#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
@ -3076,10 +3077,18 @@ static CURLcode ftp_pwd_resp(struct Curl_easy *data,
break; /* get out of this loop */
}
}
else
else {
if(ISCNTRL(*ptr)) {
/* control characters have no business in a path */
curlx_dyn_free(&out);
return CURLE_WEIRD_SERVER_REPLY;
}
result = curlx_dyn_addn(&out, ptr, 1);
if(result)
}
if(result) {
curlx_dyn_free(&out);
return result;
}
}
}
if(entry_extracted) {

View File

@ -281,7 +281,8 @@ test3032 test3033 test3034 test3035 test3036 \
test3100 test3101 test3102 test3103 test3104 test3105 \
\
test3200 test3201 test3202 test3203 test3204 test3205 test3206 test3207 test3208 \
test3209 test3210 test3211 test3212 test3213 test3214 test3215 test3216 \
test3209 test3210 test3211 test3212 test3213 test3214 test3215 test3216 test3217 \
test3218 \
test4000 test4001
EXTRA_DIST = $(TESTCASES) DISABLED data-xml1 data320.html \

View File

@ -3,8 +3,7 @@
<info>
<keywords>
FTP
PASV
LIST
FAILURE
</keywords>
</info>
# Server-side
@ -12,20 +11,6 @@ LIST
<servercmd>
REPLY PWD 257 "just one
</servercmd>
<data mode="text">
total 20
drwxr-xr-x 8 98 98 512 Oct 22 13:06 .
drwxr-xr-x 8 98 98 512 Oct 22 13:06 ..
drwxr-xr-x 2 98 98 512 May 2 1996 curl-releases
-r--r--r-- 1 0 1 35 Jul 16 1996 README
lrwxrwxrwx 1 0 1 7 Dec 9 1999 bin -> usr/bin
dr-xr-xr-x 2 0 1 512 Oct 1 1997 dev
drwxrwxrwx 2 98 98 512 May 29 16:04 download.html
dr-xr-xr-x 2 0 1 512 Nov 30 1995 etc
drwxrwxrwx 2 98 1 512 Oct 30 14:33 pub
dr-xr-xr-x 5 0 1 512 Oct 1 1997 usr
</data>
</reply>
# Client-side
@ -34,7 +19,7 @@ dr-xr-xr-x 5 0 1 512 Oct 1 1997 usr
ftp
</server>
<name>
FTP with uneven quote in PWD response
FTP with unclosed quote in PWD response
</name>
<command>
ftp://%HOSTIP:%FTPPORT/test-%TESTNUMBER/
@ -43,15 +28,13 @@ ftp://%HOSTIP:%FTPPORT/test-%TESTNUMBER/
# Verify data after the test has been "shot"
<verify>
<errorcode>
8
</errorcode>
<protocol crlf="yes">
USER anonymous
PASS ftp@example.com
PWD
CWD test-%TESTNUMBER
EPSV
TYPE A
LIST
QUIT
</protocol>
</verify>
</testcase>

42
tests/data/test3217 Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="US-ASCII"?>
<testcase>
<info>
<keywords>
FTP
PASV
RETR
FAILURE
</keywords>
</info>
# Server-side
<reply>
<servercmd>
REPLY PWD 257 %hex["/%03"]hex%
</servercmd>
</reply>
# Client-side
<client>
<server>
ftp
</server>
<name>
FTP with control characters in PWD response
</name>
<command>
ftp://%HOSTIP:%FTPPORT/%TESTNUMBER
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<errorcode>
8
</errorcode>
<protocol crlf="yes">
USER anonymous
PASS ftp@example.com
PWD
</protocol>
</verify>
</testcase>

42
tests/data/test3218 Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="US-ASCII"?>
<testcase>
<info>
<keywords>
FTP
PASV
RETR
FAILURE
</keywords>
</info>
# Server-side
<reply>
<servercmd>
REPLY PWD 257 %hex["/%0d"]hex%
</servercmd>
</reply>
# Client-side
<client>
<server>
ftp
</server>
<name>
FTP with CR control character in PWD response path
</name>
<command>
ftp://%HOSTIP:%FTPPORT/%TESTNUMBER
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<errorcode>
8
</errorcode>
<protocol crlf="yes">
USER anonymous
PASS ftp@example.com
PWD
</protocol>
</verify>
</testcase>