CVE-2018-4013
LIVE555 RTSPサーバライブラリのHTTPパケット解析機能に、悪用可能なコード実行の脆弱性が存在します。特別に細工されたパケットによりスタックベースのバッファオーバーフローが発生し、コード実行につながる可能性があります。攻撃者はパケットを送信することでこの脆弱性を引き起こすことができます。
LIVE555 Media Librariesは、RTSP/RTCP/RTSP/SIP用の軽量なマルチメディアストリーミングライブラリ群であり、サーバとクライアントの両方をサポートするコードを備えています。VLCやMPlayerなどの一般的なメディアプレーヤーや、多数の組み込みデバイス(主にカメラ)で利用されています。この脆弱性は、これらのメディアプレーヤーとやり取りするサーバコンポーネントに存在しますが、メディアプレーヤー自体には影響を与えません。
LIVE555が標準RTSPサーバ向けに提供する機能の1つに、HTTP上でRTSPをトンネリングする機能があります。これはサーバがバインドする別のポート(通常はホストマシンで利用可能なポートに応じてTCP 80、8000、または8080)で提供されます。このポートは通常のRTSPをサポートできますが、特定の状況ではHTTPクライアントがRTSP-over-HTTPトンネルをネゴシエートできます。この機能を処理するコードは次のとおりです:
// 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]
上記の[3]に示すように、"Accept"および"x-sessioncookie" HTTPヘッダーが、RTSP-over-HTTPトンネルであるかどうかを決定します。したがって、パラメータは入力バイトからスタック上のsessionCookie [1]およびacceptStr [2]バッファ(両方ともサイズ200)に読み込まれ、その後さらに解析されます。
コードパスは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]
ここで重要なのは、親関数のchar配列が[1](sessionCookie)と[2](acceptStr)で再び新しい関数に直接渡される点です。これにより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]
}
}
}
}
}
最外側のループは、headerNameが見つかるまで入力バイトを繰り返し処理します。このプログラムの場合、[1]のstrncmpで"Accept:"と"x-sessioncookie:"を継続的に探します。コメントに記載されているように、別のループが空白をスキップし、その後[2]で期待される改行文字'\r\n'を探し始めます。この後、プログラムはコピーサイズをresultMaxSizeに正しく制限します。resultMaxSizeは、この関数への両方の呼び出しで0xc8(200)に正しく設定されています。
コピー後、[3]のbreakに到達します。これは実際には[4]のループのみを抜けるもので、コードは前述の最初のstrncmpループに戻ります。したがって、バッファ内に別の"Accept:"または"x-sessioncookie"文字列がある場合、再度コピーが行われます。[5]の実際のコピー方法を調べると、初期ポインタ(handleRequestBytes関数のスタックフレーム内のアドレスを指している)が増加し続けることがわかります。各コピーの長さはバッファサイズに制限されていますが、増加し続ける宛先アドレスに対して実行できるコピー回数に制限がない場合、スタックベースのバッファオーバーフローを簡単に引き起こすことができます。
クラッシュ出力
==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
Cisco TalosのLilith ¯_(ツ)_/¯ によって発見されました。