From c3f04e76ae2b1c3ee9283010538769d54a878a59 Mon Sep 17 00:00:00 2001 From: Ercan Ermis Date: Tue, 17 Mar 2026 09:47:24 +0100 Subject: [PATCH] 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 --- lib/ftp.c | 13 +++++++++++-- tests/data/Makefile.am | 3 ++- tests/data/test1152 | 27 +++++---------------------- tests/data/test3217 | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/data/test3218 | 42 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 tests/data/test3217 create mode 100644 tests/data/test3218 diff --git a/lib/ftp.c b/lib/ftp.c index 8570ad0984..2597c10cce 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -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) { diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index fe74ef6e2f..08550da33b 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -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 \ diff --git a/tests/data/test1152 b/tests/data/test1152 index 673d5e0cd6..ad548e64f1 100644 --- a/tests/data/test1152 +++ b/tests/data/test1152 @@ -3,8 +3,7 @@ FTP -PASV -LIST +FAILURE # Server-side @@ -12,20 +11,6 @@ LIST REPLY PWD 257 "just one - - -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 - # Client-side @@ -34,7 +19,7 @@ dr-xr-xr-x 5 0 1 512 Oct 1 1997 usr ftp -FTP with uneven quote in PWD response +FTP with unclosed quote in PWD response ftp://%HOSTIP:%FTPPORT/test-%TESTNUMBER/ @@ -43,15 +28,13 @@ ftp://%HOSTIP:%FTPPORT/test-%TESTNUMBER/ # Verify data after the test has been "shot" + +8 + USER anonymous PASS ftp@example.com PWD -CWD test-%TESTNUMBER -EPSV -TYPE A -LIST -QUIT diff --git a/tests/data/test3217 b/tests/data/test3217 new file mode 100644 index 0000000000..df7c890eba --- /dev/null +++ b/tests/data/test3217 @@ -0,0 +1,42 @@ + + + + +FTP +PASV +RETR +FAILURE + + +# Server-side + + +REPLY PWD 257 %hex["/%03"]hex% + + + +# Client-side + + +ftp + + +FTP with control characters in PWD response + + +ftp://%HOSTIP:%FTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +8 + + +USER anonymous +PASS ftp@example.com +PWD + + + diff --git a/tests/data/test3218 b/tests/data/test3218 new file mode 100644 index 0000000000..0c98b202ec --- /dev/null +++ b/tests/data/test3218 @@ -0,0 +1,42 @@ + + + + +FTP +PASV +RETR +FAILURE + + +# Server-side + + +REPLY PWD 257 %hex["/%0d"]hex% + + + +# Client-side + + +ftp + + +FTP with CR control character in PWD response path + + +ftp://%HOSTIP:%FTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +8 + + +USER anonymous +PASS ftp@example.com +PWD + + +