
ثغرة تنفيذ التعليمات البرمجية في RTSPServer CVE-2018-4013
CVE-2018-4013
توجد ثغرة قابلة للاستغلال لتنفيذ التعليمات البرمجية في وظيفة تحليل حزم HTTP في مكتبة خادم RTSP الخاصة بـ LIVE555. يمكن أن تسبب حزمة مصممة خصيصًا تجاوز سعة في مخزن مؤقت قائم على المكدس، مما يؤدي إلى تنفيذ التعليمات البرمجية. يمكن للمهاجم إرسال حزمة لاستغلال هذه الثغرة.
مكتبات LIVE555 هي مجموعة خفيفة من مكتبات بث الوسائط المتعددة لبروتوكولات RTSP/RTCP/RTSP/SIP، مع دعم برمجي لكلٍّ من الخوادم والعملاء. تُستخدم من قبل مشغلات وسائط شهيرة مثل VLC وMPlayer، بالإضافة إلى العديد من الأجهزة المدمجة (الكاميرات بشكل أساسي). تقع هذه الثغرة في مكوّن الخادم الذي يتفاعل مع مشغلات الوسائط هذه، لكنها لا تؤثر على مشغلات الوسائط.
إحدى الوظائف التي يوفرها LIVE555 لخادم RTSP القياسي الخاص به هي القدرة على تمرير RTSP عبر HTTP عبر نفق، والتي تُقدَّم عبر منفذ مختلف يربطه الخادم، عادةً 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]، فإن ترويسات HTTP “Accept” و “x-sessioncookie” هي التي تقرر ما إذا كان الاتصال نفق 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. في حالة هذا البرنامج، يبحث باستمرار عن “Accept:” و “x-sessioncookie:” باستخدام strncmp عند [1]. وكما تشير التعليقة، تتجاوز حلقة أخرى أي مسافات بيضاء موجودة، ثم تبدأ في البحث عن أحرف السطر الجديد المتوقعة ‘\r\n’ عند [2]. بعد ذلك، يحد البرنامج بشكل صحيح حجم النسخ إلى resultMaxSize، والذي يُضبط بشكل صحيح على 0xc8 (200) في كلتا الاستدعاءتين لهذه الدالة.
بعد النسخ، يتم الوصول إلى break عند [3]، وهو في الواقع لا يكسر سوى الحلقة عند [4]، مما يتسبب في قفز الكود عائدًا إلى حلقة strncmp الأولية المذكورة أعلاه. وبالتالي، إذا كانت هناك سلسلة “Accept:” أو “x-sessioncookie” أخرى داخل المخزن المؤقت، يتم النسخ مرة أخرى، وإذا تفحصنا الطريقة الفعلية للنسخ عند [5]، يمكننا أن نرى أن المؤشر الأولي (الذي يشير إلى عنوان في إطار المكدس الخاص بالدالة handleRequestBytes) يستمر في التزايد، وبينما يكون طول أي نسخة معينة محدودًا بحجم المخزن المؤقت، فإنه في غياب حد لعدد النسخ التي يمكن أن تحدث على عنوان وجهة يتزايد باستمرار، يمكن تشغيل تجاوز سعة في مخزن مؤقت قائم على المكدس بسهولة.
Crash Output
==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
اكتُشفت بواسطة Lilith ¯_(ツ)_/¯ من Cisco Talos.