]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
digest: fix OWS and escaped quote handling
authortrxvorr <trxvorr@users.noreply.github.com>
Sun, 28 Dec 2025 18:28:18 +0000 (23:58 +0530)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 30 Dec 2025 22:22:26 +0000 (23:22 +0100)
The migration to the strparse API introduced regressions in Digest
authentication parsing where Optional Whitespace (OWS) after commas was
not skipped, and escaped quotes in values were not correctly parsed.

This change ensures whitespace is skipped before key lookups and escaped
characters are properly handled and unescaped in quoted values.

Reported-by: herdiyanitdev on hackerone
Closes #20102

lib/curlx/strparse.c
lib/curlx/strparse.h
lib/vauth/digest.c
tests/data/Makefile.am
tests/data/test1664
tests/data/test2091 [new file with mode: 0644]

index aa78921f5011034c7b111e7a94fd3f075ade367c..f11b58b7eee5e24474be34e836a9c79b0533b18e 100644 (file)
@@ -89,7 +89,7 @@ int curlx_str_untilnl(const char **linep, struct Curl_str *out,
   return STRE_OK;
 }
 
-/* Get a "quoted" word. No escaping possible.
+/* Get a "quoted" word. Escaped quotes are supported.
    return non-zero on error */
 int curlx_str_quotedword(const char **linep, struct Curl_str *out,
                          const size_t max)
@@ -103,6 +103,11 @@ int curlx_str_quotedword(const char **linep, struct Curl_str *out,
     return STRE_BEGQUOTE;
   s++;
   while(*s && (*s != '\"')) {
+    if(*s == '\\' && s[1]) {
+      s++;
+      if(++len > max)
+        return STRE_BIG;
+    }
     s++;
     if(++len > max)
       return STRE_BIG;
index 27fea2957e09d0caa83b84151b993ac88c1c9619..ae8fd19d955e2b53d01b8b81458a849f7e467313 100644 (file)
@@ -62,7 +62,7 @@ int curlx_str_until(const char **linep, struct Curl_str *out, const size_t max,
 int curlx_str_untilnl(const char **linep, struct Curl_str *out,
                       const size_t max);
 
-/* Get a "quoted" word. No escaping possible.
+/* Get a "quoted" word. Escaped quotes are supported.
    return non-zero on error */
 int curlx_str_quotedword(const char **linep, struct Curl_str *out,
                          const size_t max);
index 40e8873913ec0d219df2cae5de74420689c50852..a1fd248617a40759a8a032fdd58957d4b185d356 100644 (file)
@@ -192,6 +192,9 @@ static bool auth_digest_get_key_value(const char *chlg, const char *key,
   do {
     struct Curl_str data;
     struct Curl_str name;
+
+    curlx_str_passblanks(&chlg);
+
     if(!curlx_str_until(&chlg, &name, 64, '=') &&
        !curlx_str_single(&chlg, '=')) {
       /* this is the key, get the value, possibly quoted */
@@ -204,11 +207,22 @@ static bool auth_digest_get_key_value(const char *chlg, const char *key,
 
       if(curlx_str_cmp(&name, key)) {
         /* if this is our key, return the value */
-        if(curlx_strlen(&data) >= buflen)
+        size_t len = curlx_strlen(&data);
+        const char *src = curlx_str(&data);
+        size_t i;
+        size_t outlen = 0;
+
+        if(len >= buflen)
           /* does not fit */
           return FALSE;
-        memcpy(buf, curlx_str(&data), curlx_strlen(&data));
-        buf[curlx_strlen(&data)] = 0;
+
+        for(i = 0; i < len; i++) {
+          if(src[i] == '\\' && i + 1 < len) {
+            i++; /* skip backslash */
+          }
+          buf[outlen++] = src[i];
+        }
+        buf[outlen] = 0;
         return TRUE;
       }
       if(curlx_str_single(&chlg, ','))
index 74c9aa8f3733840d7d62a5380005dfd8a530cc11..de75499a183e85ddc9f68e9090116dc4b95b6966 100644 (file)
@@ -252,7 +252,7 @@ test2056 test2057 test2058 test2059 test2060 test2061 test2062 test2063 \
 test2064 test2065 test2066 test2067 test2068 test2069 test2070 test2071 \
 test2072 test2073 test2074 test2075 test2076 test2077 test2078 test2079 \
 test2080 test2081 test2082 test2083 test2084 test2085 test2086 test2087 \
-test2088 test2089 test2090 \
+test2088 test2089 test2090 test2091 \
 test2100 test2101 test2102 test2103 test2104 \
 \
 test2200 test2201 test2202 test2203 test2204 test2205 \
index a1a2027427521537ae35672a48db1e24abff4a37..42c4982d57ed609b007118f134b4f8cef08fed79 100644 (file)
@@ -49,7 +49,7 @@ curlx_str_quotedword
 5: (" "word"") 3, "" [0], line 0
 6: (""perfect"") 0, "perfect" [7], line 9
 7: (""p r e t"") 0, "p r e t" [7], line 9
-8: (""perfec\"") 0, "perfec\" [7], line 9
+8: (""perfec\"") 1, "" [0], line 0
 9: ("""") 0, "" [0], line 2
 10: ("") 3, "" [0], line 0
 11: (""longerth"") 1, "" [0], line 0
diff --git a/tests/data/test2091 b/tests/data/test2091
new file mode 100644 (file)
index 0000000..33ef0b2
--- /dev/null
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+HTTP Digest auth
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data1 crlf="headers">
+HTTP/1.1 401 Authorization Required swsclose
+Server: Apache/1.3.27 (Darwin) PHP/4.1.2
+WWW-Authenticate: Digest realm="OWS Realm", nonce="1053604145"
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 26
+
+This is not the real page
+</data1>
+
+<data1001 crlf="headers">
+HTTP/1.1 200 OK
+Server: Apache/1.3.27 (Darwin) PHP/4.1.2
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 23
+
+This IS the real page!
+</data1001>
+
+<data3 crlf="headers">
+HTTP/1.1 401 Authorization Required swsclose
+Server: Apache/1.3.27 (Darwin) PHP/4.1.2
+WWW-Authenticate: Digest realm="My \"Cool\" Realm", nonce="1053604146"
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 26
+
+This is not the real page
+</data3>
+
+<data1003 crlf="headers">
+HTTP/1.1 200 OK
+Server: Apache/1.3.27 (Darwin) PHP/4.1.2
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 23
+
+This IS the real page!
+</data1003>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+!SSPI
+crypto
+digest
+</features>
+<name>
+HTTP Digest auth with OWS and escaped quotes
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER0001 -u testuser:testpass --digest --next
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER0003 -u testuser:testpass --digest
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol crlf="headers">
+GET /%TESTNUMBER0001 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+GET /%TESTNUMBER0001 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Authorization: Digest username="testuser", realm="OWS Realm", nonce="1053604145", uri="/%TESTNUMBER0001", response="b6c8f707f7781c272e79489771185713"
+User-Agent: curl/%VERSION
+Accept: */*
+
+GET /%TESTNUMBER0003 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+GET /%TESTNUMBER0003 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Authorization: Digest username="testuser", realm="My \"Cool\" Realm", nonce="1053604146", uri="/%TESTNUMBER0003", response="f10c1586b83b6e5927fef54748f88d36"
+User-Agent: curl/%VERSION
+Accept: */*
+
+</protocol>
+</verify>
+</testcase>