
Vulnerabilidade de Execução de Código no RTSPServer CVE-2018-4013
CVE-2018-4013
Existe uma vulnerabilidade explorável de execução de código na funcionalidade de análise de pacotes HTTP da biblioteca do servidor RTSP LIVE555. Um pacote especialmente criado pode causar um estouro de buffer baseado em pilha, resultando em execução de código. Um atacante pode enviar um pacote para acionar esta vulnerabilidade.
As Bibliotecas de Mídia LIVE555 são um conjunto leve de bibliotecas de streaming multimídia para RTSP/RTCP/RTSP/SIP, com suporte de código tanto para servidores quanto para clientes. Elas são utilizadas por reprodutores de mídia populares como VLC e MPlayer, além de uma infinidade de dispositivos embarcados (principalmente câmeras). Esta vulnerabilidade está no componente do servidor que interage com esses reprodutores de mídia, mas não afeta os reprodutores de mídia.
Uma das funcionalidades habilitadas pelo LIVE555 para seu servidor RTSP padrão é a capacidade de tunelar RTSP sobre HTTP, que é servida por uma porta diferente vinculada pelo servidor, tipicamente TCP 80, 8000 ou 8080, dependendo de quais portas estão disponíveis na máquina hospedeira. Esta porta pode suportar RTSP normal, mas em certos casos, o cliente HTTP pode negociar o túnel RTSP sobre HTTP. O código que lida com este recurso é:
// liveMedia/RTSPServer.cpp:607
void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
[...]
// The request was not (valid) RTSP, but check for a special case: HTTP commands
// (for setting up RTSP-over-HTTP tunneling):
char sessionCookie[RTSP_PARAM_STRING_MAX]; //[1]
char acceptStr[RTSP_PARAM_STRING_MAX]; //[2]
*fLastCRLF = '\0'; // temporarily, for parsing
parseSucceeded = parseHTTPRequestString(cmdName, sizeof cmdName,
urlSuffix, sizeof urlPreSuffix,
sessionCookie, sizeof sessionCookie,
acceptStr, sizeof acceptStr); //[3]
Conforme mostrado acima em [3], os cabeçalhos HTTP "Accept" e "x-sessioncookie" são os que decidem se é um túnel RTSP sobre HTTP ou não. Assim, os parâmetros são lidos dos bytes de entrada para os buffers sessionCookie [1] e acceptStr [2] na pilha (ambos de tamanho 200), e então analisados mais adiante.
O caminho do código leva à função parseHTTPRequestString:
Boolean RTSPServer:: RTSPClientConnection::parseHTTPRequestString(char* resultCmdName, unsigned resultCmdNameMaxSize,
char* eurlSuffix, unsigned urlSuffixMaxSize,
char* sessionCookie, unsigned sessionCookieMaxSize,
char* acceptStr, unsigned acceptStrMaxSize) {
[...]
lookForHeader("x-sessioncookie", &reqStr[i], reqStrSize-i, sessionCookie, sessionCookieMaxSize); // [1]
lookForHeader("Accept", &reqStr[i], reqStrSize-i, acceptStr, acceptStrMaxSize); //[2]
As únicas coisas realmente importantes a notar são que os arrays de char da função pai são novamente passados para uma nova função diretamente em [1] (sessionCookie) e [2] (acceptStr). Isso leva à função lookForHeader:
static void lookForHeader(char const* headerName, char const* source, unsigned
sourceLen, char* resultStr, unsigned resultMaxSize) {
resultStr[0] = '\0'; // by default, return an empty string
unsigned headerNameLen = strlen(headerName);
for (int i = 0; i < (int)(sourceLen-headerNameLen); ++i) {
if (strncmp(&source[i], headerName, headerNameLen) == 0 && source[i+headerNameLen] == ':') { // [1]
// We found the header. Skip over any whitespace, then copy the rest of the line to "resultStr":
for (i += headerNameLen+1; i < (int)sourceLen && (source[i] == ' ' || source[i] == '\t'); ++i) {}
for (unsigned j = i; j < sourceLen; ++j) { // [4]
if (source[j] == '\r' || source[j] == '\n') { // [2]
// We've found the end of the line. Copy it to the result (if it will fit):
if (j-i+1 > resultMaxSize) break;
char const* resultSource = &source[i];
char const* resultSourceEnd = &source[j];
while (resultSource < resultSourceEnd) *resultStr++ = *resultSource++; // [5]
*resultStr = '\0';
break; //[3]
}
}
}
}
}
O loop mais externo itera sobre nossos bytes de entrada até que o headerName seja encontrado. No caso deste programa, ele procura continuamente por “Accept:” e “x-sessioncookie:” com strncmp em [1]. Como o comentário observa, outro loop pula qualquer espaço em branco encontrado e então começa a procurar pelos caracteres de nova linha esperados ‘\r\n’ em [2]. Depois disso, o programa limita corretamente o tamanho da cópia para resultMaxSize, que é definido corretamente como 0xc8 (200) em ambas as chamadas para esta função.
Após a cópia, o break em [3] é atingido, que na verdade só sai do loop em [4], fazendo com que o código volte ao loop strncmp inicial mencionado acima. Assim, se houver outra string “Accept:” ou “x-sessioncookie” dentro do buffer, a cópia ocorre novamente, e se examinarmos o método real de cópia em [5], podemos ver que nosso ponteiro inicial (que aponta para um endereço no quadro de pilha da função handleRequestBytes) continua a incrementar, e enquanto o comprimento de qualquer cópia é limitado ao tamanho do buffer, quando não há limite no número de cópias que podem ocorrer em um endereço de destino cada vez maior, um estouro de buffer baseado em pilha pode ser facilmente acionado.
Saída de Crash
==38574==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffffffd878 at pc 0x555555aad1fb bp 0x7fffffffced0 sp 0x7fffffffcec8
WRITE of size 1 at 0x7fffffffd878 thread T0
#0 0x555555aad1fa in lookForHeader /root/boop/work_work/triages/live555/live/liveMedia/RTSPServer.cpp:398
#1 0x555555aad847 in RTSPServer::RTSPClientConnection::parseHTTPRequestString(char*, unsigned int, char*, unsigned int, char*, unsigned int, char*, unsigned int) /root/boop/work_work/triages/live555/live/liveMedia/RTSPServer.cpp:479
#2 0x555555ab82ac in RTSPServer::RTSPClientConnection::handleRequestBytes(int) /root/boop/work_work/triages/live555/live/liveMedia/RTSPServer.cpp:828
#3 0x555555aa9c17 in GenericMediaServer::ClientConnection::incomingRequestHandler() /root/boop/work_work/triages/live555/live/liveMedia/GenericMediaServer.cpp:246
#4 0x555555e0063b in BasicTaskScheduler::SingleStep(unsigned int) /root/boop/work_work/triages/live555/live/BasicUsageEnvironment/BasicTaskScheduler.cpp:153
#5 0x555555e12c75 in BasicTaskScheduler0::doEventLoop(char volatile*) /root/boop/work_work/triages/live555/live/BasicUsageEnvironment/BasicTaskScheduler0.cpp:80
#6 0x555555a9452c in main /root/boop/work_work/triages/live555/live/mediaServer/live555MediaServer.cpp:89
#7 0x7ffff550b2b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0)
#8 0x555555a978e9 in _start (/root/boop/work_work/triages/live555/live555MediaServer+0x5438e9)
Address 0x7fffffffd878 is located in stack of thread T0 at offset 2024 in frame
#0 0x555555ab6b9f in RTSPServer::RTSPClientConnection::handleRequestBytes(int) /root/boop/work_work/triages/live555/live/liveMedia/RTSPServer.cpp:607
This frame has 12 object(s):
[32, 33) 'reuseConnection'
[96, 97) 'deliverViaTCP'
[160, 164) 'contentLength'
[224, 232) 'proxyURLSuffix'
[288, 488) 'cmdName'
[544, 744) 'urlPreSuffix'
[800, 1000) 'urlSuffix'
[1056, 1256) 'cseq'
[1312, 1512) 'sessionIdStr'
[1568, 1768) 'sessionCookie'
[1824, 2024) 'acceptStr' <== Memory access at offset 2024 overflows this variable
[2080, 2480) 'urlTotalSuffix'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /root/boop/work_work/triages/live555/live/liveMedia/RTSPServer.cpp:398 in lookForHeader
Shadow bytes around the buggy address:
0x10007fff7ab0: f4 f4 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00
0x10007fff7ac0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f4
0x10007fff7ad0: f4 f4 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00
0x10007fff7ae0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f4
0x10007fff7af0: f4 f4 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00
=>0x10007fff7b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00[f4]
0x10007fff7b10: f4 f4 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00
0x10007fff7b20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7b30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7b40: 00 00 00 00 00 00 00 00 f4 f4 f3 f3 f3 f3 00 00
0x10007fff7b50: 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00
Descoberto por Lilith ¯_(ツ)_/¯ da Cisco Talos.