diff --git a/Python/EyeWitness.py b/Python/EyeWitness.py index b6cd6f93..20b3bace 100755 --- a/Python/EyeWitness.py +++ b/Python/EyeWitness.py @@ -80,6 +80,14 @@ def create_cli_parser(): a timeout'.replace(' ', ''), type=int, help='Max retries on timeouts') + cred_validation_options = parser.add_argument_group('Credential Validation Options') + cred_validation_options.add_argument('--enable-validation', + default=None, action='store_true', + help='Test default credentials and validate the host is vulnerable.') + cred_validation_options.add_argument('--validation-output', metavar='Output log file of validated credentials', + default=None, + help='Specify a file to log valid credentials (for easy automation).') + report_options = parser.add_argument_group('Report Output Options') report_options.add_argument('-d', metavar='Directory Name', default=None, @@ -256,6 +264,8 @@ def exitsig(*args): if cli_parsed.web: create_driver = selenium_module.create_driver capture_host = selenium_module.capture_host + auth_host = selenium_module.auth_host + test_realm = selenium_module.test_realm if not cli_parsed.show_selenium: display = Display(visible=0, size=(1920, 1080)) display.start() @@ -269,8 +279,20 @@ def exitsig(*args): web_index_head = create_web_index_head(cli_parsed.date, cli_parsed.time) driver = create_driver(cli_parsed) - result, driver = capture_host(cli_parsed, http_object, driver) - result = default_creds_category(result) + result, is_protected = test_realm(cli_parsed, http_object, driver) + if not result: + result, driver = capture_host(cli_parsed, http_object, driver) + result = default_creds_category(result) + if cli_parsed.enable_validation: + auth_host(cli_parsed, result, driver, is_protected) + else: + print("[!] HTTP Authentication Realm Detected") + result, driver = capture_host(cli_parsed, result, driver) + if result.default_creds: + result = default_creds_category(result) + if cli_parsed.enable_validation: + auth_host(cli_parsed, result, driver, is_protected) + if cli_parsed.resolve: result.resolved = resolve_host(result.remote_system) driver.quit() @@ -291,6 +313,8 @@ def worker_thread(cli_parsed, targets, lock, counter, user_agent=None): if cli_parsed.web: create_driver = selenium_module.create_driver capture_host = selenium_module.capture_host + auth_host = selenium_module.auth_host + test_realm = selenium_module.test_realm with lock: driver = create_driver(cli_parsed, user_agent) @@ -320,16 +344,42 @@ def worker_thread(cli_parsed, targets, lock, counter, user_agent=None): http_object.resolved = resolve_host(http_object.remote_system) if user_agent is None: - http_object, driver = capture_host( - cli_parsed, http_object, driver) - if http_object.category is None and http_object.error_state is None: + + # head call and detect WWW-Authenticate realm info + # if found, attempt manual auth + + http_object2, is_protected = test_realm(cli_parsed, http_object, driver) + if not http_object2: + http_object, driver = capture_host(cli_parsed, http_object, driver) http_object = default_creds_category(http_object) - manager.update_http_object(http_object) + if cli_parsed.enable_validation: + auth_host(cli_parsed, http_object, driver, is_protected) + manager.update_http_object(http_object) + else: + print("[!] HTTP Authentication Realm Detected") + http_object2, driver = capture_host(cli_parsed, http_object2, driver) + if http_object2.default_creds: + http_object2 = default_creds_category(http_object2) + if cli_parsed.enable_validation: + auth_host(cli_parsed, http_object2, driver, is_protected) + manager.update_http_object(http_object2) + else: - ua_object, driver = capture_host( - cli_parsed, http_object, driver) - if http_object.category is None and http_object.error_state is None: + + ua_object, is_protected = test_realm(cli_parsed, http_object, driver) + if not ua_object: + ua_object, driver = capture_host(cli_parsed, http_object, driver) ua_object = default_creds_category(ua_object) + if cli_parsed.enable_validation: + auth_host(cli_parsed, ua_object, driver, is_protected) + else: + print("[!] HTTP Authentication Realm Detected") + ua_object, driver = capture_host(cli_parsed, ua_object, driver) + if ua_object.default_creds: + ua_object = default_creds_category(ua_object) + if cli_parsed.enable_validation: + auth_host(cli_parsed, ua_object, driver, is_protected) + manager.update_ua_object(ua_object) counter[0].value += 1 diff --git a/Python/modules/helpers.py b/Python/modules/helpers.py index bdfbffd7..b67e84c9 100644 --- a/Python/modules/helpers.py +++ b/Python/modules/helpers.py @@ -648,7 +648,7 @@ def do_jitter(cli_parsed): sleep_value = sleep_value * .01 sleep_value = 1 - sleep_value sleep_value = sleep_value * cli_parsed.jitter - print("[*] Sleeping for " + str(sleep_value) + " seconds..") + print("[*] Sleeping for " + "{:.2f}".format(sleep_value) + " seconds..") try: time.sleep(sleep_value) except KeyboardInterrupt: @@ -726,9 +726,24 @@ def default_creds_category(http_object): for sig in signatures: # Find the signature(s), split them into their own list if needed # Assign default creds to its own variable + # !! added support for description field and cred field, so that creds can be + # !! automatically checked + sig = sig.rstrip("\n") sig_cred = sig.split('|') + if len(sig_cred) > 3: + # character '|' is contained in the page_sig, rejoin and work backwards + tmp_sig = sig_cred[0:len(sig_cred)-2] + sig = "|".join(tmp_sig) + sig_cred2 = [ sig, sig_cred[len(sig_cred) - 2], sig_cred[len(sig_cred) - 1] ] + sig_cred = sig_cred2 + page_sig = sig_cred[0].split(";") - cred_info = sig_cred[1].strip() + desc = sig_cred[1].strip() + try: + cred_info = sig_cred[2] + except Exception as e: + # default to description, assume description is missing + cred_info = desc # Set our variable to 1 if the signature was not identified. If it is # identified, it will be added later on. Find total number of @@ -739,10 +754,12 @@ def default_creds_category(http_object): # web page needed to make a signature Delimete the "signature" # by ";" before the "|", and then have the creds after the "|" if all([x.lower() in http_object.source_code.decode().lower() for x in page_sig]): + http_object._description = desc + if http_object.default_creds is None: http_object.default_creds = cred_info else: - http_object.default_creds += '\n' + cred_info + http_object.default_creds += ';' + cred_info for cat in categories: # Find the signature(s), split them into their own list if needed diff --git a/Python/modules/objects.py b/Python/modules/objects.py index 13df9091..158c3481 100644 --- a/Python/modules/objects.py +++ b/Python/modules/objects.py @@ -1,6 +1,7 @@ import html import os import re +import traceback from modules.helpers import strip_nonalphanum @@ -30,6 +31,10 @@ def __init__(self): self._ua_left = None self._resolved = None + # parsed authentication, tuple of username and password, if username is empty it will be None + self._description = "" + self._parsed_creds = [ ] + def set_paths(self, outdir, suffix=None): file_name = self.remote_system.replace('://', '.') for char in [':', '/', '?', '=', '%', '+']: @@ -179,6 +184,44 @@ def default_creds(self): @default_creds.setter def default_creds(self, default_creds): + if not default_creds: return + + # attempt to parse + # filter out those without '/' or start with '(' # save as comment only + if not '/' in default_creds or default_creds[0] == '(': + self._parsed_creds = [ (None, None, default_creds, None, '') ] # no user or pass, only comment + self._default_creds = default_creds + return + + try: + creds = default_creds.split(';') + # parse out those with comments if present + # else split / + for c in creds: + user = passwd = comment = None + if ' (' in c: + x = c.split(' (') + comment = x[1][:-1].strip() # assuming ending ) and removing it + y = x[0].split('/') + user = y[0].strip() + try: + passwd = y[1].strip() + except: + # Only 1 value found + passwd = None + else: + y = c.split('/') + user = y[0].strip() + try: + passwd = y[1].strip() + except: + # Only 1 value found + passwd = None + self._parsed_creds = self._parsed_creds + [ (user,passwd,comment,None, '') ] + except Exception as e: + print("[!] Failed to parse credentials: ", e) + print(" ", default_creds) + print(traceback.format_exc()) self._default_creds = default_creds @property @@ -203,12 +246,12 @@ def create_table_html(self): html = u"" if self._remote_login is not None: html += (""" -
+
{address}
""").format(address=self._remote_login) else: html += (""" -
+
{address}
""").format(address=self.remote_system) @@ -227,13 +270,51 @@ def create_table_html(self): self.remote_system) if self.default_creds is not None: - try: - html += "
Default credentials: {0}
".format( - self.sanitize(self.default_creds)) - except UnicodeEncodeError: - html += u"
Default credentials: {0}
".format( - self.sanitize(self.default_creds)) - + if type(self.default_creds) is list: + try: + html += "
Default credentials: {0} ({1})
".format( + self.sanitize(self._description), self.sanitize(", ".join(self.default_creds))) + except UnicodeEncodeError: + try: + html += u"
Default credentials: {0} ({1})
".format( + self.sanitize(self._description), self.sanitize(self.default_creds)) + except: + print('[!] Failed to format default credentials: ') + # print(json.dumps(self.default_creds)) + else: + try: + html += "
Default credentials: {0} ({1})
".format( + self.sanitize(self._description), self.sanitize(self.default_creds)) + except UnicodeEncodeError: + try: + html += u"
Default credentials: {0} ({1})
".format( + self.sanitize(self._description), self.sanitize(self.default_creds)) + except: + print('[!] Failed to format default credentials: ') + # print(json.dumps(self.default_creds)) + + if self._parsed_creds is not None and type(self._parsed_creds) is list and len(self._parsed_creds) > 0: + html += "
" + for cred in self._parsed_creds: + html += "" + if cred[0] and cred[1]: + html += "".format(cred[0], cred[1]) + elif cred[0]: + html += "".format(cred[0]) + elif cred[1]: + html += "".format(cred[0]) + if cred[3] == True: + if len(cred[4]) > 0: + a_scr_path = os.path.relpath(cred[4], self.root_path) + html += ''.format(a_scr_path) + else: + html += '' + else: + html += '' + html+= "" + html += "
UserPasswordStatus
{0}{1}{0}{0}SuccessSuccessFailed

" + + if self.error_state is None: try: html += "\n
Page Title: {0}\n".format( @@ -286,6 +367,13 @@ def create_table_html(self): target=\"_blank\"> 400 ? 400: true);\" src=\"{1}\">
""").format( src_path, scr_path) + if self._parsed_creds is not None and type(self._parsed_creds) is list and len(self._parsed_creds) > 0: + for cred in self._parsed_creds: + if len(cred[4]) > 0: + a_scr_path = os.path.relpath(cred[4], self.root_path) + html += "

".format(a_scr_path) + + html += ("""""") if len(self._uadata) > 0: divid = strip_nonalphanum(self.remote_system) @@ -389,7 +477,7 @@ def create_table_html(self, divid): src_path = os.path.relpath(self.source_path, self.root_path) html = u"" html += (""" -
+
{1}
""").format(divid, self.remote_system) @@ -407,12 +495,49 @@ def create_table_html(self, divid): self.remote_system) if self.default_creds is not None: - try: - html += "
Default credentials: {0}
".format( - self.sanitize(self.default_creds)) - except UnicodeEncodeError: - html += u"
Default credentials: {0}
".format( - self.sanitize(self.default_creds)) + if type(self.default_creds) is list: + try: + html += "
Default credentials: {0} ({1})
".format( + self.sanitize(self._description), self.sanitize(", ".join(self.default_creds))) + except UnicodeEncodeError: + try: + html += u"
Default credentials: {0} ({1})
".format( + self.sanitize(self._description), self.sanitize(self.default_creds)) + except: + print('[!] Failed to format default credentials: ') + # print(json.dumps(self.default_creds)) + else: + try: + html += "
Default credentials: {0} ({1})
".format( + self.sanitize(self._description), self.sanitize(self.default_creds)) + except UnicodeEncodeError: + try: + html += u"
Default credentials: {0} ({1})
".format( + self.sanitize(self._description), self.sanitize(self.default_creds)) + except: + print('[!] Failed to format default credentials: ') + # print(json.dumps(self.default_creds)) + + if self._parsed_creds is not None and type(self._parsed_creds) is list and len(self._parsed_creds) > 0: + html += "
" + for cred in self._parsed_creds: + html += "" + if cred[0] and cred[1]: + html += "".format(cred[0], cred[1]) + elif cred[0]: + html += "".format(cred[0]) + elif cred[1]: + html += "".format(cred[0]) + if cred[3] == True: + if len(cred[4]) > 0: + a_scr_path = os.path.relpath(cred[4], self.root_path) + html += ''.format(a_scr_path) + else: + html += '' + else: + html += '' + html+= "" + html += "
UserPasswordStatus
{0}{1}{0}{0}SuccessSuccessFailed

" try: html += "\n
Page Title: {0}\n".format( @@ -446,7 +571,12 @@ def create_table_html(self, divid): target=\"_blank\">Source Code
""").format( - src_path, scr_path) + src=\"{1}\">
""").format(src_path, scr_path) + if self._parsed_creds is not None and type(self._parsed_creds) is list and len(self._parsed_creds) > 0: + for cred in self._parsed_creds: + if len(cred[4]) > 0: + a_scr_path = os.path.relpath(cred[4], self.root_path) + html += "

".format(a_scr_path) + html += ("""""") return html diff --git a/Python/modules/reporting.py b/Python/modules/reporting.py index 09ba8a0a..eca6607e 100644 --- a/Python/modules/reporting.py +++ b/Python/modules/reporting.py @@ -1,6 +1,7 @@ import os import sys import urllib.parse +import traceback try: from fuzzywuzzy import fuzz @@ -158,9 +159,17 @@ def key_lambda(k): k.error_state = str(k.error_state) if k.page_title is None: k.page_title = str(k.page_title) - return (k.error_state, k.page_title) - errors = sorted([x for x in data if (x is not None) and (x.error_state is not None)], - key=key_lambda) + return (k.error_state, str(k.page_title)) + + errors = [ ] + try: + data_list = [x for x in data if (x is not None) and (x.error_state is not None)] + errors = sorted(data_list, key=key_lambda) + except Exception as e: + errors = [ ] + print("Error parsing out errors: %s" % e) + print(traceback.format_exc()) + data[:] = [x for x in data if x.error_state is None] data = sorted(data, key=lambda k: str(k.page_title)) html = u"" diff --git a/Python/modules/selenium_module.py b/Python/modules/selenium_module.py index c9f3f2f1..274d7548 100644 --- a/Python/modules/selenium_module.py +++ b/Python/modules/selenium_module.py @@ -2,9 +2,16 @@ import os import socket import sys +import time +import traceback import urllib.request import urllib.error +from urllib.parse import urlparse +import urllib3 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +import requests import ssl +import json try: from ssl import CertificateError as sslerr @@ -12,8 +19,11 @@ from ssl import SSLError as sslerr try: + # from seleniumwire import webdriver from selenium import webdriver from selenium.webdriver.firefox.options import Options + from selenium.webdriver.common.keys import Keys + from selenium.webdriver.common.by import By from selenium.common.exceptions import NoAlertPresentException from selenium.common.exceptions import TimeoutException from selenium.common.exceptions import UnexpectedAlertPresentException @@ -89,6 +99,552 @@ def create_driver(cli_parsed, user_agent=None): print(e) sys.exit() +def _has_failure_contents(contents): + + if '500 Internal Server Error' in contents: return True + elif '401 Unauthorized' in contents: return True + elif 'Authorization failed' in contents: return True + + return False + +def _auth_log(cred, cli_parsed, http_object, driver, method='form'): + """Writes a detected host with a valid default credential to disk + + Args: + cred (Tuple): Consists of username, password, comment, and status result once tested (bool) + cli_parsed (ArgumentParser): Command Line Object + http_object (HTTPTableObject): Object containing data relating to current URL + driver (FirefoxDriver): webdriver instance + ua (String, optional): Optional user agent string + + Returns: + Boolean: True for success, False for failure + """ + + print("\x1b[32m[!] AUTH LOG: Potential authentication success: {0} username: {1} password: {2} method: {3}\x1b[0m".format(http_object.remote_system, cred[0], cred[1], method)) + if not cli_parsed.validation_output: + return + + with open(cli_parsed.validation_output, "a+") as f: + print("AUTH LOG: Potential authentication success: {0} username: {1} password: {2} method: {3}".format(http_object.remote_system, cred[0], cred[1], method), file=f) + f.close() + + + +def _auth_host_uri(cred, cli_parsed, http_object, driver, ua=None): + """Performs the internal authentication with single given credential + + Args: + cred (Tuple): Consists of username, password, comment, and status result once tested (bool) + cli_parsed (ArgumentParser): Command Line Object + http_object (HTTPTableObject): Object containing data relating to current URL + driver (FirefoxDriver): webdriver instance + ua (String, optional): Optional user agent string + + Returns: + Boolean: String (filename of screenshot) for success, and False for failure + """ + + # first attempt for each cred, attempt cred call ie: https://username:password@hostname:port/ + # if result is unauthorized or a form is found with a password input (assuming failure) + + p = urlparse(http_object.remote_system) + if cred[0] and cred[1]: + if cred[1] == '': + auth_url = p.scheme + "://" + cred[0] + ":@" + p.netloc + p.path + else: + auth_url = p.scheme + "://" + cred[0] + ":" + cred[1] + "@" + p.netloc + p.path + elif cred[0]: + auth_url = p.scheme + "://" + cred[0] + ":@" + p.netloc + p.path + else: + print("[*] No credentials found, skipping...") + # print(cred) + # auth_url = p.scheme + "://" + p.netloc + p.path + return False + print("[*] Attempting authentication via url: ", auth_url) + + # Attempt to take the screenshot + try: + # If cookie is presented we need to avoid cookie-averse error. To do so, we need to get the page twice. + driver.get(auth_url) + + # if a text input and a password input are shown, print content, and assume login failed + screenshots = False + + # print(driver.page_source) debugging + if _has_failure_contents(driver.page_source): return False + + try: + elem = driver.find_element('xpath', "//input[@type='password']") + except WebDriverException as e: + print("[!] AUTH SUCCESS: No password element found, potential auth success: {0}".format(http_object.remote_system)) + _auth_log(cred, cli_parsed, http_object, driver, 'uri') + # Save our screenshot to the specified directory + try: + time.sleep(5) # wait for page to load + filename = http_object.screenshot_path[:-4] + ".auth.1.png" + i = 0 + while os.path.exists(filename): + filename = http_object.screenshot_path[:-4] + ".auth.1_%d.png" % i + i += 1 + + print("[!] Saving screenshot to: ", filename) + driver.save_screenshot(filename) + if not screenshots: screenshots = filename + else: screenshots += ';' + filename + except WebDriverException as e: + print('[*] Error saving web page screenshot' + ' for ' + http_object.remote_system) + + # get contents and inspect + if cli_parsed.cookies is not None: + for cookie in cli_parsed.cookies: + driver.add_cookie(cookie) + + driver.get(auth_url) + + # get contents and inspect again + try: + elem = driver.find_element('xpath', "//input[@type='password']") + except WebDriverException as e: + print("[!] AUTH SUCCESS: No password element found, potential auth success: {0}".format(http_object.remote_system)) + _auth_log(cred, cli_parsed, http_object, driver, 'uri') + # Save our screenshot to the specified directory + try: + time.sleep(5) # wait for page to load + filename = http_object.screenshot_path[:-4] + ".auth.2.png" + print("[!] Saving screenshot to: ", filename) + i = 0 + while os.path.exists(filename): + filename = http_object.screenshot_path[:-4] + ".auth.2_%d.png" % i + i += 1 + driver.save_screenshot(filename) + if not screenshots: screenshots = filename + else: screenshots += ';' + filename + except WebDriverException as e: + print('[*] Error saving web page screenshot' + ' for ' + http_object.remote_system) + return screenshots + + return False + + except KeyboardInterrupt: + print('[*] Skipping: {0}'.format(http_object.remote_system)) + http_object.error_state = 'Skipped' + http_object.page_title = 'Page Skipped by User' + except TimeoutException: + print('[*] Hit timeout limit when connecting to {0}, retrying'.format(http_object.remote_system)) + except http.client.BadStatusLine: + print('[*] Bad status line when connecting to {0}'.format(http_object.remote_system)) + except WebDriverException as e: + print('[*] WebDriverError when connecting to {0}'.format(http_object.remote_system)) + # print('[*] WebDriverError when connecting to {0} -> {1}'.format(http_object.remote_system, e)) + except Exception as e: + print("[*] URI login failure: ", e) + print(traceback.format_exc()) + + # Dismiss any alerts present on the page + # Will not work for basic auth dialogs! + try: + alert = driver.switch_to.alert + alert.dismiss() + except Exception as e: + pass + + return False + +def _auth_host_form(cred, cli_parsed, http_object, driver, ua=None): + """Performs the internal authentication with single given credential + + Args: + cred (Tuple): Consists of username, password, comment, and status result once tested (bool) + cli_parsed (ArgumentParser): Command Line Object + http_object (HTTPTableObject): Object containing data relating to current URL + driver (FirefoxDriver): webdriver instance + ua (String, optional): Optional user agent string + + Returns: + Boolean: String (filename of screenshot) for success, and False for failure + Driver: Needed since this functions closes connections and retries + """ + + # form is found, leverage selenium + + # selenium: for each form: + # find forms that contain password input type. + # form: provide each user/password and confirm non 400 return + + print("[!] Attempting form validation...") + driver2 = None + try: + success=False + + # If cookie is presented we need to avoid cookie-averse error. To do so, we need to get the page twice. ??? + + driver2 = create_driver(cli_parsed, ua) + driver2.get(http_object.remote_system) + if cli_parsed.cookies is not None: + for cookie in cli_parsed.cookies: + driver2.add_cookie(cookie) + driver2.get(http_object.remote_system) + + # get contents and inspect again + # for each form that contains an input + try: + forms = driver2.find_elements('xpath', "//form") + except WebDriverException as e: + print('[*] WebDriverError when connecting to {0} -> {1}'.format(http_object.remote_system, e)) + print('[*] No forms have been found! Exiting.') + driver2.quit() + return False + + # print("FORMS: ", forms) + print("[!] %d forms found..." % len(forms)) + screenshots = False + i = 0 + for form in forms: + # for each radio button, for each radio button option + + # get contents and inspect again + # for each form that contains an input + radios = [ ] + try: + radios = form.find_elements('xpath', "//input[@type='radio']") + except WebDriverException: + pass + + if len(radios) > 0: + # print("[*] Testing additional radio input found in form (radio #%d)" % radioOffset) + # radios[radioOffset].click() + # radioOffset += 1 + for radio in radios: + i = i + 1 + radio.click() + # submit + + i = i + 1 + try: + pass_elem = form.find_element('xpath', "//input[@type='password']") + if pass_elem: + if cred[1] == '': + pass_elem.send_keys("") + else: + pass_elem.send_keys(cred[1]) + except WebDriverException: + print("[*] No password input found in form, skipping form...") + continue + + try: + user_elem = form.find_element('xpath', "//input[@type='input']") + user_elem.send_keys(cred[0]) + except WebDriverException: + print('[*] No username element found, attempting to send password only.') + + try: + form.find_element('xpath', "//input[@type='submit']").click() + except WebDriverException: + print('[*] No submit input element found, attempting to give up.') + try: + form.submit() + except Exception as e: + print('[!] Unable to submit form: ', e) + + if not _has_failure_contents(driver2.page_source): + + try: + elem = driver2.find_element('xpath', "//input[@type='password']") + print('[*] Authentication failure.') + except WebDriverException: + print("[!] AUTH SUCCESS(2): No password element found, potential auth success!") + _auth_log(cred, cli_parsed, http_object, driver) + success=True + # Save our screenshot to the specified directory + try: + time.sleep(5) # wait for page to load + filename = http_object.screenshot_path[:-4] + ".auth.3_%d.png" % i + print("[!] Saving screenshot to: ", filename) + k = 0 + while os.path.exists(filename): + filename = http_object.screenshot_path[:-4] + ".auth.3_%d_%d.png" % (i, k) + k += 1 + driver2.save_screenshot(filename) + if not screenshots: screenshots = filename + else: screenshots += ';' + filename + except WebDriverException as e: + print('[*] Error saving web page screenshot' + ' for ' + http_object.remote_system) + + # Dismiss any alerts present on the page + # Will not work for basic auth dialogs! + try: + alert = driver2.switch_to.alert + alert.dismiss() + except Exception as e: + pass + + driver2.back() + + else: + + i = i + 1 + try: + pass_elem = form.find_element('xpath', "//input[@type='password']") + if pass_elem and cred[1]: + pass_elem.send_keys(cred[1]) + elif pass_elem: + pass_elem.send_keys("") + except WebDriverException: + print("[*] No password input found in form, skipping form...") + continue + except Exception as e: + print("[*] Failed to send password input, skipping form...") + continue + + try: + user_elem = form.find_element('xpath', "//input[@type='input']") + user_elem.send_keys(cred[0]) + except WebDriverException: + print('[*] No username element found, attempting to send password only.') + + try: + form.find_element('xpath', "//input[@type='submit']").click() + except WebDriverException: + print('[*] No submit input element found, attempting to give up.') + try: + form.submit() + except Exception as e: + print('[!] Unable to submit form: ', e) + + try: + elem = driver2.find_element('xpath', "//input[@type='password']") + print('[*] Authentication failure.') + except WebDriverException: + print("[!] AUTH SUCCESS(2): No password element found, potential auth success!") + _auth_log(cred, cli_parsed, http_object, driver) + success=True + # Save our screenshot to the specified directory + try: + time.sleep(5) # wait for page to load + filename = http_object.screenshot_path[:-4] + ".auth.3_%d.png" % i + print("[!] Saving screenshot to: ", filename) + k = 0 + while os.path.exists(filename): + filename = http_object.screenshot_path[:-4] + ".auth.3_%d_%d.png" % (i, k) + k += 1 + driver2.save_screenshot(filename) + if not screenshots: screenshots = filename + else: screenshots += ';' + filename + except WebDriverException as e: + print('[*] Error saving web page screenshot' + ' for ' + http_object.remote_system) + + # Dismiss any alerts present on the page + # Will not work for basic auth dialogs! + try: + alert = driver2.switch_to.alert + alert.dismiss() + except Exception as e: + pass + + driver2.back() + + + driver2.quit() + + return screenshots + except KeyboardInterrupt: + print('[*] Skipping: {0}'.format(http_object.remote_system)) + http_object.error_state = 'Skipped' + http_object.page_title = 'Page Skipped by User' + except TimeoutException: + print('[*] Hit timeout limit when connecting to {0}, retrying'.format(http_object.remote_system)) + except http.client.BadStatusLine: + print('[*] Bad status line when connecting to {0}'.format(http_object.remote_system)) + except WebDriverException: + print('[*] WebDriverError when connecting to {0}'.format(http_object.remote_system)) + # print('[*] WebDriverError when connecting to {0} -> {1}'.format(http_object.remote_system, e)) + except Exception as e: + print("[*] Form login failure: ", e) + print(traceback.format_exc()) + + if driver2: driver2.quit() + + return False + +def _auth_host(cred, cli_parsed, http_object, driver, is_protected, ua=None): + """Performs the internal authentication with single given credential + + Args: + cred (Tuple): Consists of username, password, comment, and status result once tested (bool) + cli_parsed (ArgumentParser): Command Line Object + http_object (HTTPTableObject): Object containing data relating to current URL + driver (FirefoxDriver): webdriver instance + ua (String, optional): Optional user agent string + + Returns: + Boolean: String (filename of screenshot) for success, and False for failure + """ + + # first attempt for each cred, attempt cred call ie: https://username:password@hostname:port/ + # if result is unauthorized or a form is found with a password input (assuming failure), try next request + # else if form is found, leverage selenium + + # selenium: for each form: + # find forms that contain password input type. + # form: provide each user/password and confirm non 400 return + + if is_protected: + return _auth_host_uri(cred, cli_parsed, http_object, driver, ua) + + return _auth_host_form(cred, cli_parsed, http_object, driver, ua) + + +def auth_host(cli_parsed, http_object, driver, is_protected, ua=None): + """Attempts to authenticate to a single host, given + the data available in http_object._parsed_creds + + Args: + cli_parsed (ArgumentParser): Command Line Object + http_object (HTTPTableObject): Object containing data relating to current URL + driver (FirefoxDriver): webdriver instance + ua (String, optional): Optional user agent string + + Returns: + HTTPTableObject: Complete http_object + """ + + + if len(http_object._parsed_creds) == 0: + # print("[!] Failed to test authentication, no credentials have been found: ", http_object.default_creds) + return http_object + + for idx in range(len(http_object._parsed_creds)): + c = http_object._parsed_creds[idx] + s = "" + if c[0] and c[1]: + s += "User: %s Password: %s" % (c[0], c[1]) + elif c[0]: + s += "User: %s Password: empty" % c[0] + if c[2]: + s += " Comment: %s" % c[2] + s += "\n" + + screenshots = _auth_host(c, cli_parsed, http_object, driver, is_protected, ua) + if screenshots != False: + # print("[*] Authentication Success! Credentials:\n%s" % s.strip("\n")) + c = list(c) + c[3] = True + # XXX + c[4] = screenshots + http_object._parsed_creds[idx] = tuple(c) + + return http_object + +def test_realm(cli_parsed, http_object, driver, ua=None): + """Capture HTTP HEAD request and look for Server and WWW-Authenticate headers + Args: + cli_parsed (ArgumentParser): Command Line Object + http_object (HTTPTableObject): Object containing data relating to current URL + driver (FirefoxDriver): webdriver instance + ua (String, optional): Optional user agent string + + Returns: + HTTPTableObject: Complete http_object + Boolean: if site is protected by a realm + """ + + status = None + is_protected = False + + try: + response = requests.head(http_object.remote_system, timeout=cli_parsed.timeout, verify=False) + # print("[*] Status Code: ", response.status_code, " Headers : ", json.dumps(dict(response.headers))) + if response.status_code >= 400 and response.status_code < 500: + # Realm detected? + auth_header = server_response = None + if 'Server' in response.headers: + server_response = response.headers['Server'] + if len(server_response.strip()) == 0: server_response = None + if 'WWW-Authenticate' in response.headers: + auth_header = response.headers['WWW-Authenticate'] + if len(auth_header.strip()) == 0: auth_header = None + else: + response.close() + return status, is_protected # no authentication prompt + + # parse + if auth_header: + is_protected = True + print("Header detected: ", auth_header) + if server_response: print("Server detected: ", server_response) + + # parse out our signature data and attempt to match. for each match: attempt defalut creds. if success, use creds and take screenshot + + http_object.default_creds = None + http_object.category = None + sigpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), + '..', 'signatures_realm.txt') + with open(sigpath) as sig_file: + signatures = sig_file.readlines() + + for sig in signatures: + # Find the signature(s), split them into their own list if needed + # Assign default creds to its own variable + # !! added support for description field and cred field, so that creds can be + # !! automatically checked + sig = sig.rstrip("\n") + sig_cred = sig.split('|') + if len(sig_cred) > 4: + # character '|' is contained in the page_sig, rejoin and work backwards + tmp_sig = sig_cred[0:len(sig_cred)-3] + sig = "|".join(tmp_sig) + sig_cred2 = [ sig, sig_cred[len(sig_cred) - 3], sig_cred[len(sig_cred) - 2] ] + sig_cred = sig_cred2 + + server_sig = sig_cred[0].strip() + realm_sig = sig_cred[1].strip() + desc = sig_cred[2].strip() + try: + cred_info = sig_cred[3] + except Exception as e: + # default to description, assume description is missing + cred_info = desc + + # if all([x.lower() in server_response.lower() for x in page_sig]) and : + if server_sig and realm_sig and server_response and auth_header and server_sig.lower() in server_response.lower() and auth_header.lower() in realm_sig.lower(): + print("[*] Matched Server Response and Authentication Header, attempting default credentials") + http_object._description = desc + if http_object.default_creds is None: + http_object.default_creds = cred_info + else: + http_object.default_creds += ';' + cred_info + status = http_object + elif server_sig and server_response and server_sig.lower() in server_response.lower(): + print("[*] Matched Server Response, attempting default credentials") + http_object._description = desc + if http_object.default_creds is None: + http_object.default_creds = cred_info + else: + http_object.default_creds += ';' + cred_info + status = http_object + elif realm_sig and auth_header and auth_header.lower() in realm_sig.lower(): + print("[*] Matched Authentication Header, attempting default credentials") + http_object._description = desc + if http_object.default_creds is None: + http_object.default_creds = cred_info + else: + http_object.default_creds += ';' + cred_info + status = http_object + + response.close() + + except Exception as e: + print('[*] Error ({0}), Skipping: {1}'.format(e, http_object.remote_system)) + http_object.error_state = 'Skipped' + + # take screenshot! + + return status, is_protected def capture_host(cli_parsed, http_object, driver, ua=None): """Screenshots a single host, saves information, and returns @@ -125,7 +681,8 @@ def capture_host(cli_parsed, http_object, driver, ua=None): print('[*] Bad status line when connecting to {0}'.format(http_object.remote_system)) http_object.error_state = 'BadStatus' return http_object, driver - except WebDriverException: + except WebDriverException as e: + # print('[*] WebDriverError when connecting to {0} -> {1}'.format(http_object.remote_system, e)) print('[*] WebDriverError when connecting to {0}'.format(http_object.remote_system)) http_object.error_state = 'BadStatus' return http_object, driver @@ -191,6 +748,7 @@ def capture_host(cli_parsed, http_object, driver, ua=None): # Save our screenshot to the specified directory try: + time.sleep(5) # wait for page to load driver.save_screenshot(http_object.screenshot_path) except WebDriverException as e: print('[*] Error saving web page screenshot' diff --git a/Python/signatures.txt b/Python/signatures.txt index 38245bb5..5d2bede6 100644 --- a/Python/signatures.txt +++ b/Python/signatures.txt @@ -1,59 +1,59 @@ -Integrated Dell Remote Access Controller|(Dell iDRAC) root/calvin -ACE 4710 Device Manager;Cisco|(ACE 4710 Device Manager) admin/admin -ATutor;form_login_action;/gad_search.php|(ATutor Web CMS) admin/admin or instructor/instructor -Apache Lenya;lenya.action=introspect|(Apache Lenya) admin/levi -Polycom RSS 2000|(Polycom RSS 2000) Administrator/polycom -Polycom;HDX 8000 HD|(Polycom HDX 8000) admin/ -Drupal.settings|(Drupal) admin/ -var daisy;daisy.site;daisy.site.name|(Daisy CMS) admin/admin or user/user -Hewlett-Packard Development Company;iLO.init|(HP iLo) Administrator/ -;;;|(Dell Generic Signature)No default username or pass -Dell Color Laser 5110cn|(Dell Color Laser 5110cn) No default username or pass -Dell Laser Printer 1700n|(Dell Laser Printer 1700n) No default username or pass -Dell Laser Printer 1720dn|(Dell Laser Printer 1720dn) No default username or pass -Dell B5465dnf;/cgi-bin/dynamic/dell_header.html|(Dell B5465dnf) No default username or pass -Dell B3465dnf Laser MFP;/cgi-bin/dynamic/dell_header.html|(Dell b3465dnf) No default username or pass -Dell Laser Printer 3000cn;frame src="status/status.htm" name="RightFrame"|(Dell 3000cn) No default username or pass -Xerox WorkCentre 7435;function restore_saved_params(form, key_html)|(Xerox WorkCentre 7435) admin/1111 -WorkCentre 7120;Fuji Xerox Co;var biItms|(WorkCentre 7120) admin/1111 -WorkCentre 7125;Fuji Xerox Co;var nvTree|(WorkCentre 7125) admin/1111 -Xerox WorkCentre 5225;inNN;var trItms|(Xerox WorkCentre 5225) 11111/x-admin -Xerox Corporation;XEROX WORKCENTRE;Header Frame;inUriToMatch|(Potential Generic Xerox) admin/1111 or x-admin/11111 -Xerox Workstation M20i;var HEADER_FRAME;var BRANDING_FRAME|(Xerox Workstation M20i) admin/1111 -ChangeDefWebLanguage();script/cookieCoke.js;script/Common_script.js;NOSCRIPT|(Potential Workcenter 3550) admin/1111 -WorkCentre 6605DN;frame src="menuhome.htm"|(WorkCentre 6605DN) No default pass -Xerox WorkCentre 7232;var nvTree;var rSec|(Xerox WorkCentre 7232) 11111/x-admin -Xerox WorkCentre 5325;var nvTree;var csItms|(Xerox WorkCentre 5325) admin/1111 -Xerox WorkCentre 7345;var nvTree;var csItms|(Xerox WorkCentre 7345) admin/1111 or 11111/x-admin -Dell Color Laser 3110cn;frameleft.htm;TopFrame|(Dell Laser 3110cn) No Default Username or Pass -HP LaserJet Pro MFP M521dn;setupUrls[index++];manageUrls|(HP MFP M521dn) No default username or pass -HP Color LaserJet CM2320nf MFP;applicationMastheadSmall|(HP Color LaserJet CM2320nf) No default username or pass -HP LaserJet M1522n MFP;mastheadIcon|(HP LaserJet M1522n) No default username or pass -HP LaserJet M1536dnf;buttonManager.js;mastheadIcon|(HP LaserJet M1536dnf) No default username or pass -HP LaserJet M2727nf MFP;mastheadIcon;mastheadTitle|(HP LaserJet M2727nf) No default username or pass -HP LaserJet 1536dnf MFP;mastheadIcon;mastheadTitle|(HP LaserJet 1536dnf) No default username or pass -HP Color LaserJet CM4540 MFP;class="device-name"|(HP Color LaserJet CM4540) No default username or pass (potential username admin, no pass) -Synology DiskStation - Monego;DiskMessageHandler;VideoPlayer|(Synology) admin/ -Brother MFC-8480DN;Brother Industries;OpenConfirmWindow|(Brother 8480DN) /access or admin/access -HP LaserJet 700 color MFP M775;/hp/device/ControlPanelCustomization/Index|(HP LaserJet 700) No default username or pass -MB471;okilogo;replacelogo;savecolor|(OKI MB471) /aaaaaa or 000000 or 0000 or root/(last 6 of mac address Uppercased) -MB461;okilogo;var pagename;var status_ad_flag|(OKI MB461) /aaaaaa or 000000 or 0000 or root/(last 6 of mac address Uppercased) -dlink;document.login_form.f_LOGIN_NAME.value!="admin";document.login_form.f_login_type.value=0;function checkID()|(DNS-323 or DNS-343) admin/ -var re=/root;anonymous;nobody;administrator;ftp;guest;squeezecenter;sshd;messagebus;netdev/i;var cUName = $.cookie('uname');var cPwd = $.cookie('password');/cgi-bin/login_mgr.cgi|(Potential DNS-320L NAS) admin/ or admin/ -APC;Log On;function isValidBrowser();APC Website;var nAgt|(Potential APC SmartUPS) apc/apc -;var RVL_str = "NULL";var RVL_decode_flg = "0";frameset rows="*";src="/configuration/cover_frame" scrolling="no"|(Potential IPmux-216) su/1234 or user/1234 or tech/1234 -Avaya Aura; System manager 6.1;div class="legalNoticeDiv";div class="loginouterdiv"|(Avaya Aura System Manager 6.1) admin/admin123 -hp LaserJet 1320 series;var ieVSupported = "MSIE 5.0";hp/device/banner.html|(HP LaserJet 1320 series) -hp LaserJet 4250;a.hpButtonNavigationLink;div.hpConsumableBlockData|(HP LaserJet 4250) -hp color LaserJet 4600;images/hp_invent_logo.gif;jsfiles/formatting.css|(HP LaserJet 4600) -HP Color LaserJet CP2025;td class="mastheadIcon";applicationMastheadSmall|(HP LaserJet CP2025) -Rainwise IP-100;Relative Humidity;Solar Rad;Leaf Wetness;var exdate=new Date()|(Rainwise IP-100) admin/admin, admin/paradox, or paradox/paradox -WebDVR;var ip;var pwd;login.sitecode.value;DX8100|(DX8100 Web DVR) admin/admin -DYMO LabelWriter Print Server;InternetExplorer3.0(or later)|(DYMO LabelWriter Print Server) admin/admin -HP System Management Homepage Login;Check if the user is already connected;pageColumns|(HP System Management) username and pass of OS or domain account -Polycom RMX 1000;ondragstart;cfg_ui_hide;proxy_log_ip|(Polycom RMX 1000) POLYCOM/POLYCOM -Sign In - Hyperic;body class="tundra";since dojo has trouble when;screencastLink|hqadmin/hqadmin -HP System Management Homepage;HP-UX removed;Unable to create a socket to communicate|(HP System Management) username and pass of OS or domain account +Integrated Dell Remote Access Controller|(Dell iDRAC)|root/calvin +ACE 4710 Device Manager;Cisco|(ACE 4710 Device Manager)|admin/admin +ATutor;form_login_action;/gad_search.php|(ATutor Web CMS)|admin/admin;instructor/instructor +Apache Lenya;lenya.action=introspect|(Apache Lenya)|admin/levi +Polycom RSS 2000|(Polycom RSS 2000)|Administrator/polycom +Polycom;HDX 8000 HD|(Polycom HDX 8000)|admin/ +Drupal.settings|(Drupal)|admin/ +var daisy;daisy.site;daisy.site.name|(Daisy CMS)|admin/admin;user/user +Hewlett-Packard Development Company;iLO.init|(HP iLo)|Administrator/ +;;;|(Dell Generic Signature)No default username/pass +Dell Color Laser 5110cn|(Dell Color Laser 5110cn)|No default username/pass +Dell Laser Printer 1700n|(Dell Laser Printer 1700n)|No default username/pass +Dell Laser Printer 1720dn|(Dell Laser Printer 1720dn)|No default username/pass +Dell B5465dnf;/cgi-bin/dynamic/dell_header.html|(Dell B5465dnf)|No default username/pass +Dell B3465dnf Laser MFP;/cgi-bin/dynamic/dell_header.html|(Dell b3465dnf)|No default username/pass +Dell Laser Printer 3000cn;frame src="status/status.htm" name="RightFrame"|(Dell 3000cn)|No default username/pass +Xerox WorkCentre 7435;function restore_saved_params(form, key_html)|(Xerox WorkCentre 7435)|admin/1111 +WorkCentre 7120;Fuji Xerox Co;var biItms|(WorkCentre 7120)|admin/1111 +WorkCentre 7125;Fuji Xerox Co;var nvTree|(WorkCentre 7125)|admin/1111 +Xerox WorkCentre 5225;inNN;var trItms|(Xerox WorkCentre 5225)|11111/x-admin +Xerox Corporation;XEROX WORKCENTRE;Header Frame;inUriToMatch|(Potential Generic Xerox)|admin/1111;x-admin/11111 +Xerox Workstation M20i;var HEADER_FRAME;var BRANDING_FRAME|(Xerox Workstation M20i)|admin/1111 +ChangeDefWebLanguage();script/cookieCoke.js;script/Common_script.js;NOSCRIPT|(Potential Workcenter 3550)|admin/1111 +WorkCentre 6605DN;frame src="menuhome.htm"|(WorkCentre 6605DN)|No default pass +Xerox WorkCentre 7232;var nvTree;var rSec|(Xerox WorkCentre 7232)|11111/x-admin +Xerox WorkCentre 5325;var nvTree;var csItms|(Xerox WorkCentre 5325)|admin/1111 +Xerox WorkCentre 7345;var nvTree;var csItms|(Xerox WorkCentre 7345)|admin/1111;11111/x-admin +Dell Color Laser 3110cn;frameleft.htm;TopFrame|(Dell Laser 3110cn)|No Default Username;Pass +HP LaserJet Pro MFP M521dn;setupUrls[index++];manageUrls|(HP MFP M521dn)|No default username/pass +HP Color LaserJet CM2320nf MFP;applicationMastheadSmall|(HP Color LaserJet CM2320nf)|No default username/pass +HP LaserJet M1522n MFP;mastheadIcon|(HP LaserJet M1522n)|No default username/pass +HP LaserJet M1536dnf;buttonManager.js;mastheadIcon|(HP LaserJet M1536dnf)|No default username/pass +HP LaserJet M2727nf MFP;mastheadIcon;mastheadTitle|(HP LaserJet M2727nf)|No default username/pass +HP LaserJet 1536dnf MFP;mastheadIcon;mastheadTitle|(HP LaserJet 1536dnf)|No default username/pass +HP Color LaserJet CM4540 MFP;class="device-name"|(HP Color LaserJet CM4540)|No default username/pass (potential username admin, no pass) +Synology DiskStation|Monego;DiskMessageHandler;VideoPlayer|(Synology)|admin/ +Brother MFC-8480DN;Brother Industries;OpenConfirmWindow|(Brother 8480DN)|/access;admin/access +HP LaserJet 700 color MFP M775;/hp/device/ControlPanelCustomization/Index|(HP LaserJet 700)|No default username/pass +MB471;okilogo;replacelogo;savecolor|(OKI MB471)|/aaaaaa;000000;0000;root/(last 6 of mac address Uppercased) +MB461;okilogo;var pagename;var status_ad_flag|(OKI MB461)|/aaaaaa;000000;0000;root/(last 6 of mac address Uppercased) +dlink;document.login_form.f_LOGIN_NAME.value!="admin";document.login_form.f_login_type.value=0;function checkID()|(DNS-323;DNS-343)|admin/ +var re=/root;anonymous;nobody;administrator;ftp;guest;squeezecenter;sshd;messagebus;netdev/i;var cUName = $.cookie('uname');var cPwd = $.cookie('password');/cgi-bin/login_mgr.cgi|(Potential DNS-320L NAS)|admin/;admin/ +APC;Log On;function isValidBrowser();APC Website;var nAgt|(Potential APC SmartUPS)|apc/apc +;var RVL_str = "NULL";var RVL_decode_flg = "0";frameset rows="*";src="/configuration/cover_frame" scrolling="no"|(Potential IPmux-216)|su/1234;user/1234;tech/1234 +Avaya Aura; System manager 6.1;div class="legalNoticeDiv";div class="loginouterdiv"|(Avaya Aura System Manager 6.1)|admin/admin123 +hp LaserJet 1320 series;var ieVSupported = "MSIE 5.0";hp/device/banner.html|(HP LaserJet 1320 series)|/ (no default username/password) +hp LaserJet 4250;a.hpButtonNavigationLink;div.hpConsumableBlockData|(HP LaserJet 4250)|/ (no default username/password) +hp color LaserJet 4600;images/hp_invent_logo.gif;jsfiles/formatting.css|(HP LaserJet 4600)|/ (no default username/password) +HP Color LaserJet CP2025;td class="mastheadIcon";applicationMastheadSmall|(HP LaserJet CP2025)|/ (no default username/password) +Rainwise IP-100;Relative Humidity;Solar Rad;Leaf Wetness;var exdate=new Date()|(Rainwise IP-100)|admin/admin,admin/paradox,;paradox/paradox +WebDVR;var ip;var pwd;login.sitecode.value;DX8100|(DX8100 Web DVR)|admin/admin +DYMO LabelWriter Print Server;InternetExplorer3.0(or later)|(DYMO LabelWriter Print Server)|admin/admin +HP System Management Homepage Login;Check if the user is already connected;pageColumns|(HP System Management)|username and pass of OS;domain account +Polycom RMX 1000;ondragstart;cfg_ui_hide;proxy_log_ip|(Polycom RMX 1000)|POLYCOM/POLYCOM +Sign In|Hyperic;body class="tundra";since dojo has trouble when;screencastLink|(Hyperic)|hqadmin/hqadmin +HP System Management Homepage;HP-UX removed;Unable to create a socket to communicate|(HP System Management)|username and pass of OS;domain account On Board Remote Management;This site requires the use of a frames capable;Web browser|guest/guest QUANTUM - Scalar i40 Login Screen;var bLoggingin = false;enterKey(event)|(Scalar i40 Login) admin/password Management Console - Login;AVANCE CSS;Stratus Technologies Bermuda|admin/admin @@ -92,112 +92,112 @@ var kerio = {lib:{};kerio.engine.acceptedLanguages;upper-message-container;actio meta name="SonicWALL Administrator" content;SonicWALL - Authentication;window.onunload=onPageUnload;"auth1.html" name="authFrm"|SonicWall Device admin/password D-Link VoIP Router;frameset framespacing="0" frameborder="0" rows="0,*"|DVG-5402SP admin/ mikrotik routeros > administration;form name="loginForm" action="/cfg" method="post" onsubmit="doLogin();mikrotik routeros;configuration page|Mikrotik Routeros admin/ -Grandstream Device Configuration;form action="/cgi-bin/dologin" method="post" name="loginForm";All Rights Reserved Grandstream Networks;input name="P2" type=password size=30 maxlength=30|Grandstream Networks: /admin or /123 -HP Color LaserJet CP3505;input name="btnContinue" type="image";img class="hpPageImage" src="images/question.gif" alt="On-line help|HP Color Jet CP3505 Printer -ATEN International Co Ltd;body onload='PageInit();form name="form1" action="/cgi/login.cgi" method="post";img src="../images/logo.gif" style="margin;alert(lang.LANG_LOGIN_INVALID_USERNAME);document.getElementById("login_word")|Possible SuperMicro IPMI ADMIN/ADMIN (Creds need confirming) -Welcome to the Web-Based Configurator;function str2blks_MD5(str);loginPassword.value = "ZyXEL ZyWALL Series";Prestige_Login" value="Login"|Zyxel P-660HW-T1 /1234 -AxisTV|AxisTV Login administrator/tech -
-