Lucene search

K
talosTalos IntelligenceTALOS-2023-1902
HistoryMay 01, 2024 - 12:00 a.m.

Tinyproxy HTTP request parsing uninitialized memory vulnerability

2024-05-0100:00:00
Talos Intelligence
www.talosintelligence.com
6
tinyproxy
http
vulnerability
disclosure
http request
heap
security
cwe-457
memory
uninitialized variable
vulnerability
cve-2023-40533
talos
cvssv3
parsing
memory allocation

7.7 High

AI Score

Confidence

High

0.0004 Low

EPSS

Percentile

9.1%

Talos Vulnerability Report

TALOS-2023-1902

Tinyproxy HTTP request parsing uninitialized memory vulnerability

May 1, 2024
CVE Number

CVE-2023-40533

SUMMARY

An uninitialized memory use vulnerability exists in Tinyproxy 1.11.1 while parsing HTTP requests. In certain configurations, a specially crafted HTTP request can result in disclosure of data allocated on the heap, which could contain sensitive information. An attacker can make an unauthenticated HTTP request to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Tinyproxy 1.11.1

PRODUCT URLS

Tinyproxy - <https://tinyproxy.github.io/&gt;

CVSSv3 SCORE

5.9 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

CWE

CWE-457 - Use of Uninitialized Variable

DETAILS

Tinyproxy is a lightweight open-source HTTP proxy daemon focused on simplicity and efficiency.

Tinyproxy performs the handling of each incoming request in process_request().

static struct request_s *process_request (struct conn_s *connptr,
                                          orderedmap hashofheaders)
{
        char *url;
        struct request_s *request;
		...
	    request-&gt;method = (char *) safemalloc (request_len);
        url = (char *) safemalloc (request_len);                                    (1)
        request-&gt;protocol = (char *) safemalloc (request_len);
		...
        ret = sscanf (connptr-&gt;request_line, "%[^ ] %[^ ] %[^ ]",                   (2)
                      request-&gt;method, url, request-&gt;protocol);
					  
        if (ret == 2 && !strcasecmp (request-&gt;method, "GET")) {
			...
        } else if (ret == 3 && !strncasecmp (request-&gt;protocol, "HTTP/", 5)) {
			...
        } else {
		    ...
			indicate_http_error (connptr, 400, "Bad Request",                       (3)
								 "detail", "Request has an invalid format",
								 "url", url, NULL);
			goto fail;
        }
}

At (1) we see memory being allocated for url. Note that here the custom function safemalloc() is used instead of the standard malloc(). However, upon closer inspection, safemalloc() is a very thin wrapper around malloc() without any special safety checks.

#define safemalloc(x) debugging_malloc(x, __FILE__, __LINE__)
...
void *debugging_malloc (size_t size, const char *file, unsigned long line)
{
        void *ptr;

        assert (size &gt; 0);

        ptr = malloc (size);
        fprintf (stderr, "{malloc: %p:%lu} %s:%lu\n", ptr,
                 (unsigned long) size, file, line);
        return ptr;
}

In order to parse the first line of the HTTP request, Tinyproxy uses the standard sscanf() function at (2). For the specific format string passed, sscanf() expects 3 non-whitespace tokens separated by a space. The tokens are saved in the request-&gt;method, url and request-&gt;protocol respectively. So for a standard HTTP request:

GET / HTTP/1.1

The contents of the pointers would be:

request-&gt;method = "GET"
url = "/"
request-&gt;protocol = "HTTP/1.1"

The ret variable holds the number of tokens that were successfully parsed by sscanf(). If only 1 token (or less) was successfully parsed, execution proceeds to (3) to indicate an error to the client. In essence, this is any request that does not contain a space character in the first line. In that case, the contents of the memory pointed to by url are uninitialized. The pointer is then used in indicate_http_error().

    int
    indicate_http_error (struct conn_s *connptr, int number,
                         const char *message, ...)
    {
            ...
            while ((key = va_arg (ap, char *))) {
                    val = va_arg (ap, char *);

                    if (add_error_variable (connptr, key, val) == -1) {                   (4)
                            va_end (ap);
                            return (-1);
                    }
            }
            ...
    }

    int add_error_variable (struct conn_s *connptr, const char *key, const char *val)
    {
            ...
            k = safestrdup(key);
            v = safestrdup(val);

            if(htab_insert (connptr-&gt;error_variables, k, HTV_P(v)))                       (5)
                    return 1;
            ...
    }

At the add_error_variable() call at (4) the uninitialized contents of the url pointer are copied to the connptr-&gt;error_variables key-value structure. Since the contents of url can be uninitialized, an attacker can make Tinyproxy leak sensitive data to the network, such as user history, passwords or pointers which could assist in further exploitation by defeating ASLR.

Tinyproxy can be configured to send a custom error page in the case of a request that can’t be handled. A template HTML file can be used to send specific errors to the client. In the example template provided we see:

&lt;head&gt;
&lt;title&gt;{errno} {cause}&lt;/title&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
&lt;/head&gt;
&lt;body&gt;
<p>Here are the error variables:</p>

<dt>request</dt>
<dd>{request}</dd>

<dt>date</dt>
<dd>{date}</dd>

<dt>url</dt>
<dd>{url}</dd>
...

When such a template is used, the code replaces the directives enclosed in braces, like {url}, with the values of their respective keys in the connptr-&gt;error_variables. send_html_file() compiles a regex to search for directives in braces. Then, in varsubs_sendline(), for every directive that was encountered, its respective value is returned to the client.

int
send_html_file (FILE *infile, struct conn_s *connptr)
{
        regex_t re;
        char *inbuf = safemalloc (4096);
        (void) regcomp(&re, "{[a-z]\\{1,32\\}}", 0);

        while (fgets (inbuf, 4096, infile)) {
                varsubst_sendline(connptr, &re, inbuf);
        }

        regfree (&re);
        safefree (inbuf);
        return 1;
}

static void varsubst_sendline(struct conn_s *connptr, regex_t *re, char *p) {
    int fd = connptr-&gt;client_fd;
    while(*p) {
        ...
        int st = regexec(re, p, 1, &match, 0);
        if (st == 0) {
                ...
                varval = lookup_variable(connptr-&gt;error_variables, varname);
                if(varval) write_message(fd, "%s", varval);
                ...
        }
    }
}

Crash Information

ASAN reports a heap-buffer overflow on the uninitialized data because the strdup() function attempts to copy data beyond the uninitialized buffer until a NULL byte is found.

    ==736052==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5020000330f2 at pc 0x55d34a85e9a5 bp 0x7f26730a13b0 sp 0x7f26730a0b78
    READ of size 3 at 0x5020000330f2 thread T1
        #0 0x55d34a85e9a4 in strdup (/home/dtatsis/tinyproxy/tinyproxy_asan+0xb99a4) (BuildId: ae105f90caef08e593b35a5c1436cf6cf98a5040)
        #1 0x55d34a8bf98d in add_error_variable /home/dtatsis/tinyproxy/src/html-error.c:228:13
        #2 0x55d34a8c05a4 in indicate_http_error /home/dtatsis/tinyproxy/src/html-error.c:298:21
        #3 0x55d34a8c6bbe in process_request /home/dtatsis/tinyproxy/src/reqs.c:377:17
        #4 0x55d34a8c4ab6 in handle_connection /home/dtatsis/tinyproxy/src/reqs.c:1708:19
        #5 0x55d34a8b7bc8 in child_thread /home/dtatsis/tinyproxy/src/child.c:56:2
        #6 0x55d34a87466a in asan_thread_start(void*) asan_interceptors.cpp.o
        #7 0x7f267368cac2 in start_thread nptl/pthread_create.c:442:8
        #8 0x7f267371ea3f  misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

    0x5020000330f2 is located 0 bytes after 2-byte region [0x5020000330f0,0x5020000330f2)
    allocated by thread T1 here:
        #0 0x55d34a87694e in malloc (/home/dtatsis/tinyproxy/tinyproxy_asan+0xd194e) (BuildId: ae105f90caef08e593b35a5c1436cf6cf98a5040)
        #1 0x55d34a8c65eb in process_request /home/dtatsis/tinyproxy/src/reqs.c:342:24
        #2 0x55d34a8c4ab6 in handle_connection /home/dtatsis/tinyproxy/src/reqs.c:1708:19
        #3 0x55d34a8b7bc8 in child_thread /home/dtatsis/tinyproxy/src/child.c:56:2
        #4 0x55d34a87466a in asan_thread_start(void*) asan_interceptors.cpp.o

    Thread T1 created by T0 here:
        #0 0x55d34a85c3fd in pthread_create (/home/dtatsis/tinyproxy/tinyproxy_asan+0xb73fd) (BuildId: ae105f90caef08e593b35a5c1436cf6cf98a5040)
        #1 0x55d34a8b7901 in child_main_loop /home/dtatsis/tinyproxy/src/child.c:211:21
        #2 0x55d34a8d44cc in main /home/dtatsis/tinyproxy/src/main.c:407:9
        #3 0x7f2673621d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

In a non-ASAN build, we see heap data being sent back to the client:

<h1>Bad Request</h1>

<p>Request has an invalid format</p>

<p>Here are the error variables:</p>

<dl>
  ...
  <dt>url</dt>
  <dd>οΏ½
       ;Gt
  </dd>

Exploit Proof of Concept

We saw that the code expects the input to be 3 tokens of alphanumeric characters separated by whitespace. In order to leave the url parameter uninitialized and leak data to the network, we must send only one such token; even a single character will do.

A\n
\n

The information leak happens on a response to an erroneous request, so a valid HTTP request is not needed to exploit the vulnerability.

VENDOR RESPONSE

No maintainer response, no patch available.

TIMELINE

2023-12-20 - Initial Vendor Contact
2023-12-22 - Vendor Disclosure
2024-01-10 - Request for confirmation
2024-03-07 - Status update request / publication date announced
2024-05-01 - Public Release

Credit

Discovered by Dimitrios Tatsis of Cisco Talos.


Vulnerability Reports Next Report

TALOS-2023-1846

Previous Report

TALOS-2023-1889

7.7 High

AI Score

Confidence

High

0.0004 Low

EPSS

Percentile

9.1%