diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a404981 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +bs4 +requests +stringdist +tld +yattag diff --git a/setup.py b/setup.py index 43bfce3..f64ab0e 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -12,40 +12,36 @@ import io from setuptools import setup, find_packages from os import path + this_directory = path.abspath(path.dirname(__file__)) -with io.open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: +with io.open(path.join(this_directory, "README.md"), encoding="utf-8") as f: desc = f.read() setup( - name='xsrfprobe', - version=__import__('xsrfprobe').__version__, - description='The Prime Cross Site Request Forgery (CSRF) Audit & Exploitation Toolkit', + name="xsrfprobe", + version=__import__("xsrfprobe").__version__, + description="The Prime Cross Site Request Forgery (CSRF) Audit & Exploitation Toolkit", long_description=desc, - long_description_content_type='text/markdown', - author='Pinaki Mondal', - author_email='theinfecteddrake@gmail.com', - license='GPLv3', - url='https://github.com/0xInfection/XSRFProbe', - download_url='https://github.com/0xInfection/XSRFProbe/archive/v%s.zip' % __import__('xsrfprobe').__version__, + long_description_content_type="text/markdown", + author="Pinaki Mondal", + author_email="theinfecteddrake@gmail.com", + license="GPLv3", + url="https://github.com/0xInfection/XSRFProbe", + download_url="https://github.com/0xInfection/XSRFProbe/archive/v%s.zip" + % __import__("xsrfprobe").__version__, packages=find_packages(), - scripts=['xsrfprobe/bin/xsrfprobe'], - install_requires=[ - 'requests', - 'bs4', - 'stringdist', - 'tld', - 'yattag' - ], + scripts=["xsrfprobe/bin/xsrfprobe"], + install_requires=["requests", "bs4", "stringdist", "tld", "yattag"], classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: System Administrators', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Operating System :: OS Independent', - 'Topic :: Internet', - 'Topic :: Security', - 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', - 'Programming Language :: Python :: 3', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: System Administrators", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Operating System :: OS Independent", + "Topic :: Internet", + "Topic :: Security", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Programming Language :: Python :: 3", ], - keywords=['csrf', 'xsrf', 'appsec', 'vulnerability scanner', 'webapps', 'hacking'], + keywords=["csrf", "xsrf", "appsec", "vulnerability scanner", "webapps", "hacking"], ) diff --git a/xsrfprobe/__init__.py b/xsrfprobe/__init__.py index 2dc3802..18f4355 100644 --- a/xsrfprobe/__init__.py +++ b/xsrfprobe/__init__.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -__version__ = '2.3.1' -__license__ = 'GNU General Public License v3 (GPLv3)' +__version__ = "4.4.29" +__license__ = "GNU General Public License v3 (GPLv3)" diff --git a/xsrfprobe/bin/xsrfprobe b/xsrfprobe/bin/xsrfprobe old mode 100644 new mode 100755 index fdb603f..3fa41c4 --- a/xsrfprobe/bin/xsrfprobe +++ b/xsrfprobe/bin/xsrfprobe @@ -9,7 +9,15 @@ # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe +import os +import sys + +# Allow loading of the module from the path where we are when you +# didn't install it using pip +SCRIPT_DIR = os.path.realpath(os.path.dirname(".")) +sys.path.append(SCRIPT_DIR) + from xsrfprobe import xsrfprobe if __name__ == '__main__': - xsrfprobe.startEngine() \ No newline at end of file + xsrfprobe.startEngine() diff --git a/xsrfprobe/core/__init__.py b/xsrfprobe/core/__init__.py index 34048e2..0645695 100644 --- a/xsrfprobe/core/__init__.py +++ b/xsrfprobe/core/__init__.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# -#This module requires XSRFProbe -#https://github.com/0xInfection/XSRFProbe +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe diff --git a/xsrfprobe/core/banner.py b/xsrfprobe/core/banner.py index 202501e..911d6b6 100644 --- a/xsrfprobe/core/banner.py +++ b/xsrfprobe/core/banner.py @@ -1,48 +1,221 @@ #!/usr/bin/env python3 -#coding: utf-8 +# coding: utf-8 -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# -#Author: 0xInfection -#This module requires XSRF-Probe -#https://github.com/0xInfection/XSRF-Probe +# Author: 0xInfection +# This module requires XSRF-Probe +# https://github.com/0xInfection/XSRF-Probe # Just for some fancy benner to appear at beginning import time from xsrfprobe import __version__ -from xsrfprobe.core.colors import * +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +SLEEP_TIME = 0 + def banner(): + """Display the program banner""" + print("\n\n") + time.sleep(SLEEP_TIME) + print( + colors.ORANGE + + " _____ _____ _____ _____ _____ " + ) + time.sleep(SLEEP_TIME) + print( + colors.RED + + " __" + + colors.ORANGE + + "|" + + colors.RED + + "__ " + + colors.ORANGE + + " |_ " + + colors.RED + + "__" + + colors.ORANGE + + "|" + + colors.RED + + "___ " + + colors.ORANGE + + " |_ " + + colors.RED + + "__" + + colors.ORANGE + + "|" + + colors.RED + + "___ " + + colors.ORANGE + + "|_ " + + colors.RED + + "_" + + colors.ORANGE + + "|" + + colors.RED + + "____ " + + colors.ORANGE + + "|_" + + colors.RED + + " _" + + colors.ORANGE + + "|" + + colors.RED + + "____ " + + colors.ORANGE + + "|_ " + + colors.RED + + " _____ _____ ______ ______ " + ) + time.sleep(SLEEP_TIME) + print( + colors.RED + + " \ ` / " + + colors.ORANGE + + "|" + + colors.RED + + "| ___| " + + colors.ORANGE + + "|" + + colors.RED + + "| _ _| " + + colors.ORANGE + + "|" + + colors.RED + + "| ___| " + + colors.ORANGE + + "| " + + colors.RED + + "| _ | " + + colors.ORANGE + + "|" + + colors.RED + + "| _ ,' / \| _ )| ___| " + ) + time.sleep(SLEEP_TIME) + print( + colors.RED + + " > < " + + colors.ORANGE + + "|" + + colors.RED + + " `-.`-. " + + colors.ORANGE + + "|" + + colors.RED + + "| \ " + + colors.ORANGE + + "|" + + colors.RED + + "| ___| " + + colors.ORANGE + + "|" + + colors.RED + + " | __| " + + colors.ORANGE + + "|" + + colors.RED + + "| \ | - || |_ { | ___| " + ) + time.sleep(SLEEP_TIME) + print( + colors.RED + + " /__/__\ " + + colors.ORANGE + + "_|" + + colors.RED + + "|______| " + + colors.ORANGE + + "_|" + + colors.RED + + "|__|\__\ " + + colors.ORANGE + + " _|" + + colors.RED + + "|___| " + + colors.ORANGE + + " _|" + + colors.RED + + " |___| " + + colors.ORANGE + + " _|" + + colors.RED + + "|__|\__\\\_____/|______)|______| " + ) + time.sleep(SLEEP_TIME) + print( + colors.ORANGE + + " |_____| |_____| |_____| |_____| |_____| \n\n" + ) + time.sleep(SLEEP_TIME) + - print('\n\n') - time.sleep(0.05) - print(color.ORANGE+' _____ _____ _____ _____ _____ ') - time.sleep(0.05) - print(color.RED+' __'+color.ORANGE+'|'+color.RED+'__ '+color.ORANGE+' |_ '+color.RED+'__'+color.ORANGE+'|'+color.RED+'___ '+color.ORANGE+' |_ '+color.RED+'__'+color.ORANGE+'|'+color.RED+'___ '+color.ORANGE+'|_ '+color.RED+'_'+color.ORANGE+'|'+color.RED+'____ '+color.ORANGE+'|_'+color.RED+' _'+color.ORANGE+'|'+color.RED+'____ '+color.ORANGE+'|_ '+color.RED+' _____ _____ ______ ______ ') - time.sleep(0.05) - print(color.RED+" \ ` / "+color.ORANGE+'|'+color.RED+'| ___| '+color.ORANGE+'|'+color.RED+'| _ _| '+color.ORANGE+'|'+color.RED+'| ___| '+color.ORANGE+'| '+color.RED+'| _ | '+color.ORANGE+"|"+color.RED+"| _ ,' / \| _ )| ___| ") - time.sleep(0.05) - print(color.RED+' > < '+color.ORANGE+'|'+color.RED+' `-.`-. '+color.ORANGE+'|'+color.RED+'| \ '+color.ORANGE+'|'+color.RED+'| ___| '+color.ORANGE+'|'+color.RED+' | __| '+color.ORANGE+'|'+color.RED+'| \ | - || |_ { | ___| ') - time.sleep(0.05) - print(color.RED+' /__/__\ '+color.ORANGE+'_|'+color.RED+'|______| '+color.ORANGE+'_|'+color.RED+'|__|\__\ '+color.ORANGE+' _|'+color.RED+'|___| '+color.ORANGE+' _|'+color.RED+' |___| '+color.ORANGE+' _|'+color.RED+'|__|\__\\\_____/|______)|______| ') - time.sleep(0.05) - print(color.ORANGE+' |_____| |_____| |_____| |_____| |_____| \n\n') - time.sleep(0.05) - -def banabout(): # some fancy banner stuff :p - - print(color.BLUE+' [---] '+color.GREY+'XSRFProbe,'+color.RED+' A'+color.ORANGE+' Cross Site Request Forgery '+color.RED+'Audit Toolkit '+color.BLUE+'[---]') - time.sleep(0.05) - print(color.BLUE+' [---] [---]') - time.sleep(0.05) - print(color.BLUE+' [---] '+color.PURPLE+' '+color.GREEN+'~ Author : '+color.CYAN+'Pinaki Mondal ~ '+color.BLUE+' [---]') - time.sleep(0.05) - print(color.BLUE+' [---] '+color.CYAN+' ~ github.com / '+color.GREY+'0xInfection ~ '+color.BLUE+' [---]') - time.sleep(0.05) - print(color.BLUE+' [---] [---]') - time.sleep(0.05) - print(color.BLUE+' [---] '+color.ORANGE+' ~ Version '+color.RED+__version__+color.ORANGE+' ~ '+color.BLUE+' [---]\n') - time.sleep(0.05) +def banabout(): # some fancy banner stuff :p + print( + colors.BLUE + + " [---] " + + colors.GREY + + "XSRFProbe," + + colors.RED + + " A" + + colors.ORANGE + + " Cross Site Request Forgery " + + colors.RED + + "Audit Toolkit " + + colors.BLUE + + "[---]" + ) + time.sleep(SLEEP_TIME) + print( + colors.BLUE + + " [---] [---]" + ) + time.sleep(SLEEP_TIME) + print( + colors.BLUE + + " [---] " + + colors.PURPLE + + " " + + colors.GREEN + + "~ Author : " + + colors.CYAN + + "Pinaki Mondal ~ " + + colors.BLUE + + " [---]" + ) + time.sleep(SLEEP_TIME) + print( + colors.BLUE + + " [---] " + + colors.CYAN + + " ~ github.com / " + + colors.GREY + + "0xInfection ~ " + + colors.BLUE + + " [---]" + ) + time.sleep(SLEEP_TIME) + print( + colors.BLUE + + " [---] [---]" + ) + time.sleep(SLEEP_TIME) + print( + colors.BLUE + + " [---] " + + colors.ORANGE + + " ~ Version " + + colors.RED + + __version__ + + colors.ORANGE + + " ~ " + + colors.BLUE + + " [---]\n" + ) + time.sleep(SLEEP_TIME) diff --git a/xsrfprobe/core/colors.py b/xsrfprobe/core/colors.py index b82c3ea..0afb4ab 100644 --- a/xsrfprobe/core/colors.py +++ b/xsrfprobe/core/colors.py @@ -1,44 +1,53 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe import os -if os.name != 'nt' and os.name != 'mac': - class color: - END = '\033[0m' # normal - BOLD = '\033[1m' # bold - RED = '\033[1;91m' # red - GREEN = '\033[1;92m' # green - ORANGE = '\033[1;93m' # orange - BLUE = '\033[1;94m' # blue - PURPLE = '\033[1;95m' # purple - UNDERLINE = '\033[4m' # underline - CYAN = '\033[1;96m' # cyan - GREY = '\033[1;97m' # gray - BR = '\033[1;97;41m' # background red - BG = '\033[1;97;42m' # background green - BY = '\033[1;97;43m' # background yellow - - O = '\033[1m \033[93m[!]\033[0m ' # information - R = '\033[1m \033[91m[-]\033[0m ' # something's not right - GR = '\033[1m \033[97m[*]\033[0m ' # processing - G = '\033[1m \033[92m[+]\033[0m ' # yay! - C = '\033[1m \033[96m[+]\033[0m ' # crawling... - -else: - class color: - # no escape sequences - END=BOLD=RED=GREEN=ORANGE=BLUE=PURPLE=UNDERLINE=CYAN=GREY=BR=BG=BY='' + + +class color: + END = ( + BOLD + ) = ( + RED + ) = GREEN = ORANGE = BLUE = PURPLE = UNDERLINE = CYAN = GREY = BR = BG = BY = "" + # no color values - O = ' [!] ' - R = ' [-] ' - GR = ' [*] ' - G = ' [+] ' - C = ' [+] ' + O = " [!] " + R = " [-] " + GR = " [*] " + G = " [+] " + C = " [+] " + + def __init__(self) -> None: + # Put it here so that it is called after options is called + from xsrfprobe.files.config import NO_COLORS + + if os.name == "nt" or os.name == "mac" or NO_COLORS: + return + + self.END = "\033[0m" # normal + self.BOLD = "\033[1m" # bold + self.RED = "\033[1;91m" # red + self.GREEN = "\033[1;92m" # green + self.ORANGE = "\033[1;93m" # orange + self.BLUE = "\033[1;94m" # blue + self.PURPLE = "\033[1;95m" # purple + self.UNDERLINE = "\033[4m" # underline + self.CYAN = "\033[1;96m" # cyan + self.GREY = "\033[1;97m" # gray + self.BR = "\033[1;97;41m" # background red + self.BG = "\033[1;97;42m" # background green + self.BY = "\033[1;97;43m" # background yellow + self.O = "\033[1m \033[93m[!]\033[0m " # information + self.R = "\033[1m \033[91m[-]\033[0m " # something's not right + self.GR = "\033[1m \033[97m[*]\033[0m " # processing + self.G = "\033[1m \033[92m[+]\033[0m " # yay! + self.C = "\033[1m \033[96m[+]\033[0m " # crawling... diff --git a/xsrfprobe/core/forms.py b/xsrfprobe/core/forms.py index 57ed6b4..ac1d553 100644 --- a/xsrfprobe/core/forms.py +++ b/xsrfprobe/core/forms.py @@ -1,60 +1,64 @@ #!/usr/bin/env python3 # coding: utf-8 -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -def testFormx1(): # an example xsrfprobe-test-form to make sure the stuff works properly ;) +def testFormx1(): # an example xsrfprobe-test-form to make sure the stuff works properly ;) test_form_0x01 = """ -
-

- -

-

- -

-

-

- - - -

-
""" +
+

+ +

+

+ +

+

+ +

+

+ + + +

+
+""" return test_form_0x01 -def testFormx2(): # an example of a xsrfprobe-test-form (used drupal) +def testFormx2(): # an example of a xsrfprobe-test-form (used drupal) test_form_0x02 = """ -
-

Colorlib Contact Form

-

Contact us for custom quote

-
- -
-
- -
-
- -
-
- -
-
- -
-
+ +

Colorlib Contact Form

+

Contact us for custom quote

+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
-
- """ +
+ +""" return test_form_0x02 diff --git a/xsrfprobe/core/inputin.py b/xsrfprobe/core/inputin.py index 88b46fd..c57e33f 100644 --- a/xsrfprobe/core/inputin.py +++ b/xsrfprobe/core/inputin.py @@ -1,63 +1,84 @@ #!/usr/bin/env python3 -#coding: utf-8 +# coding: utf-8 -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# -#Author: 0xInfection (@_tID) -#This module requires XSRFProbe -#https://github.com/0xInfection/XSRFProbe +# Author: 0xInfection (@_tID) +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe -import requests, re from urllib.parse import urlparse -from xsrfprobe.core.colors import * +import re + +import requests + +from xsrfprobe.files.config import TIMEOUT_VALUE +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + from xsrfprobe.core.verbout import verbout from xsrfprobe.files.dcodelist import IP from xsrfprobe.core.logger import ErrorLogger -from xsrfprobe.files.config import ( - SITE_URL, - CRAWL_SITE, - VERIFY_CERT -) +from xsrfprobe.files.config import SITE_URL, CRAWL_SITE, VERIFY_CERT + def inputin(): - ''' + """ This module actually parses the url passed by the user. - ''' + """ + web = "" if SITE_URL: web = SITE_URL # If already assigned - if not web.endswith('/'): - web = web + '/' - if 'http' not in web: # add protocol to site - web = 'http://' + web + + if not web.endswith("/"): + web = web + "/" + + if "http" not in web: # add protocol to site + web = "http://" + web + try: web0 = urlparse(web).netloc except Exception: web0 = re.search(IP, web).group(0) + try: - print(O+'Testing site '+color.CYAN+web0+color.END+' status...') - requests.get(web) # test whether site is up or not - print(color.GREEN+' [+] Site seems to be up!'+color.END) + print( + colors.O + "Testing site " + colors.CYAN + web0 + colors.END + " status..." + ) + requests.get(web, timeout=TIMEOUT_VALUE) # test whether site is up or not + print(colors.GREEN + " [+] Site seems to be up!" + colors.END) except requests.exceptions.RequestException: # if site is down - print(R+'Site seems to be down...') + print(colors.R + "Site seems to be down...") quit() + # We'll test for endpoint only when the --crawl isn't supplied. if not CRAWL_SITE: try: - print(O+'Testing '+color.CYAN+web.split('//')[1].split('/', 1)[1]+color.END+' endpoint status...') - requests.get(web, verify=VERIFY_CERT) - print(color.GREEN+' [+] Endpoint seems to be up!'+color.END) + end_point = web.split("//")[1].split("/", 1)[1] + if end_point == "": + end_point = "/" + + print( + f"{colors.O}Testing {colors.CYAN}{end_point}{colors.END} endpoint status..." + ) + requests.get(web, timeout=TIMEOUT_VALUE, verify=VERIFY_CERT) + print(f" {colors.GREEN}[+] Endpoint seems to be up!{colors.END}") except requests.exceptions.RequestException as e: - verbout(R, 'Endpoint error: '+web.split('//')[1].split('/', 1)[1]) + verbout(colors.R, "Endpoint error: " + end_point) ErrorLogger(web0, e.__str__()) quit() except Exception as e: - verbout(R, "Exception Caught: "+e.__str__()) + verbout(colors.R, "Exception Caught: " + e.__str__()) ErrorLogger(web0, e.__str__()) quit() - if not web0.endswith('/'): - web0 = web0 + '/' - if web.split('//')[1] == web0: - return web, '' + + if not web0.endswith("/"): + web0 = web0 + "/" + + if web.split("//")[1] == web0: + return web, "" + return (web, web0) diff --git a/xsrfprobe/core/logger.py b/xsrfprobe/core/logger.py index ad33bda..a480054 100644 --- a/xsrfprobe/core/logger.py +++ b/xsrfprobe/core/logger.py @@ -1,81 +1,130 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -import os -from xsrfprobe.core.colors import * -from xsrfprobe.files.config import * +import json + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +from xsrfprobe.files.config import OUTPUT_DIR, JSON_OUTPUT from xsrfprobe.core.verbout import verbout -from xsrfprobe.files.discovered import INTERNAL_URLS, FILES_EXEC, SCAN_ERRORS -from xsrfprobe.files.discovered import VULN_LIST, FORMS_TESTED, REQUEST_TOKENS, STRENGTH_LIST +from xsrfprobe.files.discovered import ( + INTERNAL_URLS, + FILES_EXEC, + SCAN_ERRORS, + VULN_LIST, + FORMS_TESTED, + REQUEST_TOKENS, + STRENGTH_LIST, +) + def logger(filename, content): - ''' + """ This module is for logging all the stuff we found while crawling and scanning. - ''' - output_file = OUTPUT_DIR + filename + '.log' - with open(output_file, 'w+', encoding='utf8') as f: - if type(content) is tuple or type(content) is list: + """ + output_file = f"{OUTPUT_DIR}{filename}.log" + with open(output_file, "w+", encoding="utf8") as f: + if isinstance(content, tuple) or isinstance(content, list): for m in content: # if it is list or tuple, it is iterable - f.write(m+'\n') + f.write(m + "\n") else: f.write(content) # else we write out as it is... ;) - f.write('\n') + f.write("\n") + def preqheaders(tup): - ''' + """ This module prints out the headers as received in the requests normally. - ''' - verbout(GR, 'Receiving headers...\n') - verbout(color.GREY,' '+color.UNDERLINE+'REQUEST HEADERS'+color.END+color.GREY+':'+'\n') + """ + verbout(colors.GR, "Receiving headers...\n") + verbout( + colors.GREY, + f" {colors.UNDERLINE}REQUEST HEADERS{colors.END}{colors.GREY}:\n", + ) for key, val in tup.items(): - verbout(' ',color.CYAN+key+': '+color.ORANGE+val) - verbout('','') + verbout(" ", f"{colors.CYAN}{key}: {colors.ORANGE}{val}") + + verbout("", "") + def presheaders(tup): - ''' + """ This module prints out the headers as received in the requests normally. - ''' - verbout(GR, 'Receiving headers...\n') - verbout(color.GREY,' '+color.UNDERLINE+'RESPONSE HEADERS'+color.END+color.GREY+':'+'\n') + """ + verbout(colors.GR, "Receiving headers...\n") + verbout( + colors.GREY, + f" {colors.UNDERLINE}RESPONSE HEADERS{colors.END}{colors.GREY}:\n", + ) for key, val in tup.items(): - verbout(' ',color.CYAN+key+': '+color.ORANGE+val) - verbout('','') + verbout(" ", f"{colors.CYAN}{key}: {colors.ORANGE}{val}") + + verbout("", "") + def GetLogger(): + """Write out the results""" if INTERNAL_URLS: - logger('internal-links', INTERNAL_URLS) + logger("internal-links", INTERNAL_URLS) + if SCAN_ERRORS: - logger('errored', SCAN_ERRORS) + logger("errored", SCAN_ERRORS) + if FILES_EXEC: - logger('files-found', FILES_EXEC) + logger("files-found", FILES_EXEC) + if REQUEST_TOKENS: - logger('anti-csrf-tokens', REQUEST_TOKENS) + logger("anti-csrf-tokens", REQUEST_TOKENS) + if FORMS_TESTED: - logger('forms-tested', FORMS_TESTED) + logger("forms-tested", FORMS_TESTED) + if VULN_LIST: - logger('vulnerabilities', VULN_LIST) + logger("vulnerabilities", VULN_LIST) + if STRENGTH_LIST: - logger('strengths', STRENGTH_LIST) + logger("strengths", STRENGTH_LIST) + + if JSON_OUTPUT: + results = { + "internal-links": INTERNAL_URLS, + "errors": SCAN_ERRORS, + "files-found": FILES_EXEC, + "anti-csrf-tokens": REQUEST_TOKENS, + "forms-tested": FORMS_TESTED, + "vulnerabilities": VULN_LIST, + "strengths": STRENGTH_LIST, + } + + with open( + f"{OUTPUT_DIR}results.json", mode="w", encoding="latin1" + ) as file_handle: + json.dump(results, fp=file_handle) + def ErrorLogger(url, error): - con = '(i) '+url+' -> '+error.__str__() + con = f"(i) {url} -> {error}" SCAN_ERRORS.append(con) -def VulnLogger(url, vuln, content=''): - tent = '[!] '+url+' -> '+vuln+'\n\n'+str(content)+'\n\n' + +def VulnLogger(url, vuln, content=""): + tent = f"[!] {url} -> {vuln}\n\n{content}\n\n" VULN_LIST.append(tent) + def NovulLogger(url, strength): - tent = '[+] '+url+' -> '+strength + tent = f"[+] {url} -> {strength}" STRENGTH_LIST.append(tent) diff --git a/xsrfprobe/core/main.py b/xsrfprobe/core/main.py index 259e257..482f48f 100644 --- a/xsrfprobe/core/main.py +++ b/xsrfprobe/core/main.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -11,28 +11,44 @@ # Standard Package imports import os -import re +import sys import ssl import time import warnings -import difflib import http.cookiejar from bs4 import BeautifulSoup + +# This needs to be first, so that the options are loaded, as well as things like +# colors are disabled +import xsrfprobe.core.options + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + + try: from urllib.parse import urlencode from urllib.error import HTTPError, URLError from urllib.request import build_opener, HTTPCookieProcessor except ImportError: # Throws exception in Case of Python2 - print("\033[1;91m [-] \033[1;93mXSRFProbe\033[0m isn't compatible with Python 2.x versions.\n\033[1;91m [-] \033[0mUse Python 3.x to run \033[1;93mXSRFProbe.") + print( + f"{colors.RED} [-] {colors.ORANGE}XSRFProbe{colors.END} isn't compatible with Python 2.x versions.\n" + f"{colors.RED} [-] {colors.END}Use Python 3.x to run {colors.ORANGE}XSRFProbe." + ) quit() + try: import requests, stringdist, bs4 except ImportError: - print(' [-] Required dependencies are not installed.\n [-] Run \033[1;93mpip3 install -r requirements.txt\033[0m to fix it.') + print( + " [-] Required dependencies are not installed.\n" + " [-] Run {colors.ORANGE}pip3 install -r requirements.txt{colors.END} to fix it." + ) # Imports from core -from xsrfprobe.core.options import * -from xsrfprobe.core.colors import * +from xsrfprobe.files.discovered import FORMS_TESTED + from xsrfprobe.core.inputin import inputin from xsrfprobe.core.request import Get, Post from xsrfprobe.core.verbout import verbout @@ -43,8 +59,16 @@ from xsrfprobe.core.logger import VulnLogger, NovulLogger # Imports from files -from xsrfprobe.files.config import * -from xsrfprobe.files.discovered import FORMS_TESTED +from xsrfprobe.files.config import ( + VERIFY_CERT, + COOKIE_VALUE, + HEADER_VALUES, + CRAWL_SITE, + REFERER_ORIGIN_CHECKS, + FORM_SUBMISSION, + COOKIE_BASED, + POST_BASED, +) # Imports from modules from xsrfprobe.modules import Debugger @@ -58,14 +82,15 @@ from xsrfprobe.modules.Encoding import Encoding from xsrfprobe.modules.Analysis import Analysis from xsrfprobe.modules.Checkpost import PostBased + # Import Ends # First rule, remove the warnings! -warnings.filterwarnings('ignore') +warnings.filterwarnings("ignore") -def Engine(): # lets begin it! - os.system('clear') # Clear shit from terminal :p +def Engine(): # lets begin it! + os.system("clear") # Clear terminal :p banner() # Print the banner banabout() # The second banner web, fld = inputin() # Take the input @@ -75,22 +100,24 @@ def Engine(): # lets begin it! Cookie0 = http.cookiejar.CookieJar() # First as User1 Cookie1 = http.cookiejar.CookieJar() # Then as User2 if not VERIFY_CERT: - context=ssl._create_unverified_context() + context = ssl._create_unverified_context() sslHandler = urllib.request.HTTPSHandler(context=context) resp1 = build_opener(HTTPCookieProcessor(Cookie0), sslHandler) resp2 = build_opener(HTTPCookieProcessor(Cookie1), sslHandler) else: resp1 = build_opener(HTTPCookieProcessor(Cookie0)) - resp2 = build_opener(HTTPCookieProcessor(Cookie1)) + resp2 = build_opener(HTTPCookieProcessor(Cookie1)) actionDone = [] # init to the done stuff - csrf = '' # no token initialise / invalid token + csrf = "" # no token initialise / invalid token ref_detect = 0x00 # Null Char Flag ori_detect = 0x00 # Null Char Flags form = Debugger.Form_Debugger() # init to the form parser+token generator - bs1 = BeautifulSoup(form1).findAll('form', action=True)[0] # make sure the stuff works properly - bs2 = BeautifulSoup(form2).findAll('form', action=True)[0] # same as above + bs1 = BeautifulSoup(form1).findAll("form", action=True)[ + 0 + ] # make sure the stuff works properly + bs2 = BeautifulSoup(form2).findAll("form", action=True)[0] # same as above init1 = web # First init - hdrs = [('Cookie', ','.join(cookie for cookie in COOKIE_VALUE))] + hdrs = [("Cookie", ",".join(cookie for cookie in COOKIE_VALUE))] [hdrs.append((k, v)) for k, v in HEADER_VALUES.items()] resp1.addheaders = resp2.addheaders = hdrs resp1.open(init1) # Makes request as User2 @@ -105,36 +132,59 @@ def Engine(): # lets begin it! url = web try: response = Get(url).text - verbout(O, 'Trying to parse response...') + verbout(colors.O, "Trying to parse response...") soup = BeautifulSoup(response) # Parser init except AttributeError: - verbout(R, 'No response received, site probably down: '+url) - i = 0 # Init user number + verbout(colors.R, "No response received, site probably down: " + url) + i = 0 # Init user number if REFERER_ORIGIN_CHECKS: # Referer Based Checks if True... - verbout(O, 'Checking endpoint request validation via '+color.GREY+'Referer'+color.END+' Checks...') + verbout( + colors.O, + "Checking endpoint request validation via " + + colors.GREY + + "Referer" + + colors.END + + " Checks...", + ) + if Referer(url): ref_detect = 0x01 - verbout(O, 'Confirming the vulnerability...') + + verbout(colors.O, "Confirming the vulnerability...") # We have finished with Referer Based Checks, lets go for Origin Based Ones... - verbout(O, 'Confirming endpoint request validation via '+color.GREY+'Origin'+color.END+' Checks...') + verbout( + colors.O, + "Confirming endpoint request validation via " + f"{colors.GREY}Origin{colors.END} Checks...", + ) + if Origin(url): ori_detect = 0x01 + # Now lets get the forms... - verbout(O, 'Retrieving all forms on ' +color.GREY+url+color.END+'...') + verbout( + colors.O, + f"Retrieving all forms on {colors.GREY}{url}{colors.END}...", + ) + for m in Debugger.getAllForms(soup): # iterating over all forms extracted - verbout(O,'Testing form:\n'+color.CYAN) + verbout(colors.O, "Testing form:\n" + colors.CYAN) formPrettify(m.prettify()) - verbout('', '') - FORMS_TESTED.append('(i) '+url+':\n\n'+m.prettify()+'\n') + verbout("", "") + FORMS_TESTED.append("(i) " + url + ":\n\n" + m.prettify() + "\n") try: - if m['action']: + if m["action"]: pass except KeyError: - m['action'] = '/' + url.rsplit('/', 1)[1] + m["action"] = "/" + url.rsplit("/", 1)[1] ErrorLogger(url, 'No standard form "action".') - action = Parser.buildAction(url, m['action']) # get all forms which have 'action' attribute - if not action in actionDone and action!='': # if url returned is not a null value nor duplicate... + action = Parser.buildAction( + url, m["action"] + ) # get all forms which have 'action' attribute + if ( + not action in actionDone and action != "" + ): # if url returned is not a null value nor duplicate... # If form submission is kept to True if FORM_SUBMISSION: try: @@ -144,166 +194,357 @@ def Engine(): # lets begin it! # identities. Refer to XSRFProbe wiki for more info. # # NOTE: Slow connections may cause read timeouts which may result in AttributeError - result, genpoc = form.prepareFormInputs(m) # prepare inputs as user 1 - r1 = Post(url, action, result) # make request with token values generated as user1 - result, genpoc = form.prepareFormInputs(m) # prepare inputs as user 2 - r2 = Post(url, action, result) # again make request with token values generated as user2 + result, genpoc = form.prepareFormInputs( + m + ) # prepare inputs as user 1 + + r1 = Post( + url, action, result + ) # make request with token values generated as user1 + + result, genpoc = form.prepareFormInputs( + m + ) # prepare inputs as user 2 + + r2 = Post( + url, action, result + ) # again make request with token values generated as user2 + # Go for cookie based checks if COOKIE_BASED: Cookie(url, r1) + # Go for token based entropy checks... try: - if m['name']: - query, token = Entropy(result, url, r1.headers, m.prettify(), m['action'], m['name']) + if m["name"]: + query, token = Entropy( + result, + url, + r1.headers, + m.prettify(), + m["action"], + m["name"], + ) except KeyError: - query, token = Entropy(result, url, r1.headers, m.prettify(), m['action']) + query, token = Entropy( + result, url, r1.headers, m.prettify(), m["action"] + ) + # Now its time to detect the encoding type (if any) of the Anti-CSRF token. fnd, detct = Encoding(token) + if fnd == 0x01 and detct: - VulnLogger(url, 'Token is a string encoded value which can be probably decrypted.', '[i] Encoding: '+detct) + VulnLogger( + url, + "Token is a string encoded value which can be probably decrypted.", + "[i] Encoding: " + detct, + ) else: - NovulLogger(url, 'Anti-CSRF token is not a string encoded value.') + NovulLogger( + url, + "Anti-CSRF token is not a string encoded value.", + ) + # Go for token parameter tamper checks. - if (query and token): - txor = Tamper(url, action, result, r2.text, query, token) + if query and token: + txor = Tamper( + url, action, result, r2.text, query, token + ) + o2 = Get(url).text # make request as user2 try: - form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form + form2 = Debugger.getAllForms(BeautifulSoup(o2))[ + i + ] # user2 gets his form except IndexError: - verbout(R, 'Form Index Error') - ErrorLogger(url, 'Form Index Error.') + verbout(colors.R, "Form Index Error") + ErrorLogger(url, "Form Index Error.") continue # Making sure program won't end here (dirty fix :( ) - verbout(GR, 'Preparing form inputs...') - contents2, genpoc = form.prepareFormInputs(form2) # prepare for form 3 as user3 - r3 = Post(url, action, contents2) # make request as user3 with user3's form + + verbout(colors.GR, "Preparing form inputs...") + contents2, genpoc = form.prepareFormInputs( + form2 + ) # prepare for form 3 as user3 + r3 = Post( + url, action, contents2 + ) # make request as user3 with user3's form if (POST_BASED) and ((not query) or (txor)): try: - if m['name']: - PostBased(url, r1.text, r2.text, r3.text, action, result, genpoc, m.prettify(), m['name']) + if m["name"]: + PostBased( + url, + r1.text, + r2.text, + r3.text, + action, + result, + genpoc, + m.prettify(), + m["name"], + ) except KeyError: - PostBased(url, r1.text, r2.text, r3.text, action, result, genpoc, m.prettify()) + PostBased( + url, + r1.text, + r2.text, + r3.text, + action, + result, + genpoc, + m.prettify(), + ) else: - print(color.GREEN+' [+] The form was requested with a Anti-CSRF token.') - print(color.GREEN+' [+] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to POST-Based CSRF Attacks!') - NovulLogger(url, 'Not vulnerable to POST-Based CSRF Attacks.') + print( + f"{colors.GREEN} [+] The form was requested " + "with a Anti-CSRF token." + ) + print( + f"{colors.GREEN} [+] Endpoint {colors.BG} " + f"NOT VULNERABLE {colors.END}{colors.GREEN} to " + "POST-Based CSRF Attacks!" + ) + + NovulLogger( + url, "Not vulnerable to POST-Based CSRF Attacks." + ) except HTTPError as msg: # if runtime exception... - verbout(R, 'Exception : '+msg.__str__()) # again exception :( + verbout( + colors.R, "Exception : " + msg.__str__() + ) # again exception :( ErrorLogger(url, msg) actionDone.append(action) # add the stuff done - i+=1 # Increase user iteration + i += 1 # Increase user iteration else: # Implementing the 2nd mode [CRAWLING AND SCANNING]. - verbout(GR, "Initializing crawling and scanning...") + verbout(colors.GR, "Initializing crawling and scanning...") crawler = Crawler.Handler(init1, resp1) # Init to the Crawler handler while crawler.noinit(): # Until 0 urls left url = next(crawler) # Go for next! - print(C+'Testing :> '+color.CYAN+url) # Display what url its crawling + print( + f"{colors.C}Testing :> {colors.CYAN}{url}" + ) # Display what url its crawling + try: soup = crawler.process(fld) # Start the parser if not soup: - continue # Making sure not to end the program yet... + continue # Making sure not to end the program yet.. + i = 0 # Set count = 0 (user number 0, which will be subsequently incremented) if REFERER_ORIGIN_CHECKS: # Referer Based Checks if True... - verbout(O, 'Checking endpoint request validation via '+color.GREY+'Referer'+color.END+' Checks...') + verbout( + colors.O, + "Checking endpoint request validation via " + + colors.GREY + + "Referer" + + colors.END + + " Checks...", + ) + if Referer(url): ref_detect = 0x01 - verbout(O, 'Confirming the vulnerability...') + + verbout(colors.O, "Confirming the vulnerability...") # We have finished with Referer Based Checks, lets go for Origin Based Ones... - verbout(O, 'Confirming endpoint request validation via '+color.GREY+'Origin'+color.END+' Checks...') + verbout( + colors.O, + "Confirming endpoint request validation via " + f"{colors.GREY}Origin{colors.END} Checks...", + ) if Origin(url): ori_detect = 0x01 # Now lets get the forms... - verbout(O, 'Retrieving all forms on ' +color.GREY+url+color.END+'...') - for m in Debugger.getAllForms(soup): # iterating over all forms extracted - FORMS_TESTED.append('(i) '+url+':\n\n'+m.prettify()+'\n') + verbout( + colors.O, + f"Retrieving all forms on {colors.GREY}{url}{colors.END}...", + ) + + for m in Debugger.getAllForms( + soup + ): # iterating over all forms extracted + FORMS_TESTED.append(f"(i) {url}:\n\n{m.prettify()}\n") + try: - if m['action']: + if m["action"]: pass except KeyError: - m['action'] = '/' + url.rsplit('/', 1)[1] + m["action"] = "/" + url.rsplit("/", 1)[1] ErrorLogger(url, 'No standard "action" attribute.') - action = Parser.buildAction(url, m['action']) # get all forms which have 'action' attribute - if not action in actionDone and action != '': # if url returned is not a null value nor duplicate... + + action = Parser.buildAction( + url, m["action"] + ) # get all forms which have 'action' attribute + if ( + action not in actionDone and action != "" + ): # if url returned is not a null value nor duplicate... # If form submission is kept to True if FORM_SUBMISSION: try: - result, genpoc = form.prepareFormInputs(m) # prepare inputs as user 1 - r1 = Post(url, action, result) # make request with token values generated as user1 - result, genpoc = form.prepareFormInputs(m) # prepare inputs as user 2 - r2 = Post(url, action, result) # again make request with token values generated as user2 + result, genpoc = form.prepareFormInputs( + m + ) # prepare inputs as user 1 + + r1 = Post( + url, action, result + ) # make request with token values generated as user1 + + result, genpoc = form.prepareFormInputs( + m + ) # prepare inputs as user 2 + + r2 = Post( + url, action, result + ) # again make request with token values generated as user2 + if COOKIE_BASED: Cookie(url, r1) + # Go for token based entropy checks... try: - if m['name']: - query, token = Entropy(result, url, r1.headers, m.prettify(), m['action'], m['name']) + if m["name"]: + query, token = Entropy( + result, + url, + r1.headers, + m.prettify(), + m["action"], + m["name"], + ) except KeyError: - query, token = Entropy(result, url, r1.headers, m.prettify(), m['action']) + query, token = Entropy( + result, + url, + r1.headers, + m.prettify(), + m["action"], + ) ErrorLogger(url, 'No standard form "name".') + # Now its time to detect the encoding type (if any) of the Anti-CSRF token. fnd, detct = Encoding(token) + if fnd == 0x01 and detct: - VulnLogger(url, 'String encoded token value. Token might be decrypted.', '[i] Encoding: '+detct) + VulnLogger( + url, + "String encoded token value. Token might be decrypted.", + "[i] Encoding: " + detct, + ) else: - NovulLogger(url, 'Anti-CSRF token is not a string encoded value.') + NovulLogger( + url, + "Anti-CSRF token is not a string encoded value.", + ) + # Go for token parameter tamper checks. - if (query and token): - txor = Tamper(url, action, result, r2.text, query, token) + if query and token: + txor = Tamper( + url, action, result, r2.text, query, token + ) + o2 = Get(url).text # make request as user2 try: - form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form + form2 = Debugger.getAllForms(BeautifulSoup(o2))[ + i + ] # user2 gets his form except IndexError: - verbout(R, 'Form Index Error') - ErrorLogger(url, 'Form Index Error.') + verbout(colors.R, "Form Index Error") + ErrorLogger(url, "Form Index Error.") continue # making sure program won't end here (dirty fix :( ) - verbout(GR, 'Preparing form inputs...') - contents2, genpoc = form.prepareFormInputs(form2) # prepare for form 3 as user3 - r3 = Post(url, action, contents2) # make request as user3 with user3's form - if (POST_BASED) and ((query == '') or (txor == True)): + + verbout(colors.GR, "Preparing form inputs...") + + contents2, genpoc = form.prepareFormInputs( + form2 + ) # prepare for form 3 as user3 + + r3 = Post( + url, action, contents2 + ) # make request as user3 with user3's form + + if (POST_BASED) and ((query == "") or txor): try: - if m['name']: - PostBased(url, r1.text, r2.text, r3.text, m['action'], result, genpoc, m.prettify(), m['name']) + if m["name"]: + PostBased( + url, + r1.text, + r2.text, + r3.text, + m["action"], + result, + genpoc, + m.prettify(), + m["name"], + ) except KeyError: - PostBased(url, r1.text, r2.text, r3.text, m['action'], result, genpoc, m.prettify()) + PostBased( + url, + r1.text, + r2.text, + r3.text, + m["action"], + result, + genpoc, + m.prettify(), + ) else: - print(color.GREEN+' [+] The form was requested with a Anti-CSRF token.') - print(color.GREEN+' [+] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to P0ST-Based CSRF Attacks!') - NovulLogger(url, 'Not vulnerable to POST-Based CSRF Attacks.') + print( + f"{colors.GREEN} [+] The form was requested with a Anti-CSRF token." + ) + print( + colors.GREEN + + " [+] Endpoint " + + colors.BG + + " NOT VULNERABLE " + + colors.END + + colors.GREEN + + " to P0ST-Based CSRF Attacks!" + ) + NovulLogger( + url, + "Not vulnerable to POST-Based CSRF Attacks.", + ) except HTTPError as msg: # if runtime exception... - verbout(color.RED, ' [-] Exception : '+color.END+msg.__str__()) # again exception :( + verbout( + colors.RED, + " [-] Exception : " + + colors.END + + msg.__str__(), + ) # again exception :( ErrorLogger(url, msg) + actionDone.append(action) # add the stuff done - i+=1 # Increase user iteration + i += 1 # Increase user iteration + # This error usually happens when some sites are protected by some load balancer # example Cloudflare. These domains return a 403 forbidden response in various # contexts. For example when making reverse DNS queries. except HTTPError as e: - if str(e.code) == '403': - verbout(R, 'HTTP Authentication Error!') - verbout(R, 'Error Code : ' +O+ str(e.code)) + if str(e.code) == "403": + verbout(colors.R, "HTTP Authentication Error!") + verbout(colors.R, "Error Code : " + colors.O + str(e.code)) ErrorLogger(url, e) quit() except URLError as e: # if again... - verbout(R, 'Exception at : '+url) # again exception -_- + verbout(colors.R, "Exception at : " + url) # again exception -_- time.sleep(0.4) - verbout(O, 'Moving on...') + verbout(colors.O, "Moving on...") ErrorLogger(url, e) continue # make sure it doesn't stop at exceptions + GetLogger() # The scanning has finished, so now we can log out all the links ;) - print('\n'+G+"Scan completed!"+'\n') + print(f"\n{colors.G}Scan done\n") Analysis() # For Post Scan Analysis except KeyboardInterrupt as e: # Incase user wants to exit :') (while crawling) - verbout(R, 'User Interrupt!') + verbout(colors.R, "User Interrupt!") time.sleep(1.5) Analysis() # For Post scan Analysis - print(R+'Aborted!') # say goodbye - ErrorLogger('KeyBoard Interrupt', 'Aborted') + print(colors.R + "Aborted!") # say goodbye + ErrorLogger("KeyBoard Interrupt", "Aborted") GetLogger() # The scanning has interrupted, so now we can log out all the links ;) sys.exit(1) except Exception as e: - print('\n'+R+'Encountered an error. \n') - print(R+'Please view the error log files to view what went wrong.') - verbout(R, e.__str__()) + print("\n" + colors.R + "Encountered an error. \n") + print(colors.R + "Please view the error log files to view what went wrong.") + verbout(colors.R, e.__str__()) ErrorLogger(url, e) GetLogger() diff --git a/xsrfprobe/core/options.py b/xsrfprobe/core/options.py index a7accd4..8795e77 100644 --- a/xsrfprobe/core/options.py +++ b/xsrfprobe/core/options.py @@ -1,62 +1,193 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe # Importing stuff -import argparse, sys, tld -import urllib.parse, os, re +import argparse +import sys +import urllib.parse +import os +import re + +import tld + from xsrfprobe.files import config -from xsrfprobe.core.colors import R, G from xsrfprobe.core.updater import updater from xsrfprobe.files.dcodelist import IP from xsrfprobe import __version__, __license__ -print(''' - \033[1;91mXSRFProbe\033[0m, \033[1;97mA \033[1;93mCross Site Request Forgery \033[1;97mAudit Toolkit\033[0m -''') # Processing command line arguments parser = argparse.ArgumentParser(usage="xsrfprobe -u ") parser._action_groups.pop() -# A simple hack to have required argumentsa and optional arguments separately -required = parser.add_argument_group('Required Arguments') -optional = parser.add_argument_group('Optional Arguments') +# A simple hack to have required arguments and optional arguments separately +required = parser.add_argument_group("Required Arguments") +optional = parser.add_argument_group("Optional Arguments") # Required Options -required.add_argument('-u', '--url', help='Main URL to test', dest='url') +required.add_argument("-u", "--url", help="Main URL to test", dest="url") # Optional Arguments (main stuff and necessary) -optional.add_argument('-c', '--cookie', help='Cookie value to be requested with each successive request. If there are multiple cookies, separate them with commas. For example: `-c PHPSESSID=i837c5n83u4, _gid=jdhfbuysf`.', dest='cookie') -optional.add_argument('-o', '--output', help='Output directory where files to be stored. Default is the output/ folder where all files generated will be stored.', dest='output') -optional.add_argument('-d', '--delay', help='Time delay between requests in seconds. Default is zero.', dest='delay', type=float) -optional.add_argument('-q', '--quiet', help='Set the DEBUG mode to quiet. Report only when vulnerabilities are found. Minimal output will be printed on screen. ', dest='quiet', action='store_true') -optional.add_argument('-H', '--headers', help='Comma separated list of custom headers you\'d want to use. For example: ``--headers "Accept=text/php, X-Requested-With=Dumb"``.', dest='headers', type=str) -optional.add_argument('-v', '--verbose', help='Increase the verbosity of the output (e.g., -vv is more than -v). ', dest='verbose', action='store_true') -optional.add_argument('-t', '--timeout', help='HTTP request timeout value in seconds. The entered value may be either in floating point decimal or an integer. Example: ``--timeout 10.0``', dest='timeout', type=(float or int)) -optional.add_argument('-E', '--exclude', help='Comma separated list of paths or directories to be excluded which are not in scope. These paths/dirs won\'t be scanned. For example: `--exclude somepage/, sensitive-dir/, pleasedontscan/`', dest='exclude', type=str) +optional.add_argument( + "-c", + "--cookie", + help="Cookie value to be requested with each successive request. If there are multiple cookies, separate them with commas. For example: `-c PHPSESSID=i837c5n83u4, _gid=jdhfbuysf`.", + dest="cookie", +) +optional.add_argument( + "-o", + "--output", + help="Output directory where files to be stored. Default is the output/ folder where all files generated will be stored.", + dest="output", +) +optional.add_argument( + "-d", + "--delay", + help="Time delay between requests in seconds. Default is zero.", + dest="delay", + type=float, +) +optional.add_argument( + "-q", + "--quiet", + help="Set the DEBUG mode to quiet. Report only when vulnerabilities are found. Minimal output will be printed on screen. ", + dest="quiet", + action="store_true", +) +optional.add_argument( + "-H", + "--headers", + help='Comma separated list of custom headers you\'d want to use. For example: ``--headers "Accept=text/php, X-Requested-With=Dumb"``.', + dest="headers", + type=str, +) +optional.add_argument( + "-v", + "--verbose", + help="Increase the verbosity of the output (e.g., -vv is more than -v). ", + dest="verbose", + action="store_true", +) +optional.add_argument( + "-t", + "--timeout", + help="HTTP request timeout value in seconds. The entered value may be either in floating point decimal or an integer. Example: ``--timeout 10.0``", + dest="timeout", + type=(float or int), +) +optional.add_argument( + "-E", + "--exclude", + help="Comma separated list of paths or directories to be excluded which are not in scope. These paths/dirs won't be scanned. For example: `--exclude somepage/, sensitive-dir/, pleasedontscan/`", + dest="exclude", + type=str, +) # Other Options # optional.add_argument('-h', '--help', help='Show this help message and exit', dest='disp', default=argparse.SUPPRESS, action='store_true') -optional.add_argument('--user-agent', help='Custom user-agent to be used. Only one user-agent can be specified.', dest='user_agent', type=str) -optional.add_argument('--max-chars', help='Maximum allowed character length for the custom token value to be generated. For example: `--max-chars 5`. Default value is 6.', dest='maxchars', type=int) -optional.add_argument('--crawl', help="Crawl the whole site and simultaneously test all discovered endpoints for CSRF.", dest='crawl', action='store_true') -optional.add_argument('--no-analysis', help='Skip the Post-Scan Analysis of Tokens which were gathered during requests', dest='skipal', action='store_true') -optional.add_argument('--malicious', help='Generate a malicious CSRF Form which can be used in real-world exploits.', dest='malicious', action='store_true') -optional.add_argument('--skip-poc', help='Skip the PoC Form Generation of POST-Based Cross Site Request Forgeries.', dest='skippoc', action='store_true') -optional.add_argument('--no-verify', help='Do not verify SSL certificates with requests.', dest='no_verify', action='store_true') -optional.add_argument('--display', help='Print out response headers of requests while making requests.', dest='disphead', action='store_true') -optional.add_argument('--update', help='Update XSRFProbe to latest version on GitHub via git.', dest='update', action='store_true') -optional.add_argument('--random-agent', help='Use random user-agents for making requests.', dest='randagent', action='store_true') -optional.add_argument('--version', help='Display the version of XSRFProbe and exit.', dest='version', action='store_true') +optional.add_argument( + "--user-agent", + help="Custom user-agent to be used. Only one user-agent can be specified.", + dest="user_agent", + type=str, +) +optional.add_argument( + "--max-chars", + help="Maximum allowed character length for the custom token value to be generated. For example: `--max-chars 5`. Default value is 6.", + dest="maxchars", + type=int, +) +optional.add_argument( + "--crawl", + help="Crawl the whole site and simultaneously test all discovered endpoints for CSRF.", + dest="crawl", + action="store_true", +) +optional.add_argument( + "--no-analysis", + help="Skip the Post-Scan Analysis of Tokens which were gathered during requests", + dest="skipal", + action="store_true", +) +optional.add_argument( + "--malicious", + help="Generate a malicious CSRF Form which can be used in real-world exploits.", + dest="malicious", + action="store_true", +) +optional.add_argument( + "--skip-poc", + help="Skip the PoC Form Generation of POST-Based Cross Site Request Forgeries.", + dest="skippoc", + action="store_true", +) +optional.add_argument( + "--no-verify", + help="Do not verify SSL certificates with requests.", + dest="no_verify", + action="store_true", +) +optional.add_argument( + "--display", + help="Print out response headers of requests while making requests.", + dest="disphead", + action="store_true", +) +optional.add_argument( + "--no-colors", + help="Disable colors.", + dest="nocolors", + action="store_true", +) +optional.add_argument( + "--update", + help="Update XSRFProbe to latest version on GitHub via git.", + dest="update", + action="store_true", +) +optional.add_argument( + "--random-agent", + help="Use random user-agents for making requests.", + dest="randagent", + action="store_true", +) +optional.add_argument( + "--version", + help="Display the version of XSRFProbe and exit.", + dest="version", + action="store_true", +) +optional.add_argument( + "--json", + help="Output the results into a JSON file.", + dest="json", + action="store_true", +) args = parser.parse_args() +# Hide colors if user doesn't want it +if args.nocolors: + config.NO_COLORS = True + +# Support hiding the colors "early" +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +print( + f""" + {colors.RED}XSRFProbe{colors.END}, {colors.GREY}A {colors.ORANGE}Cross Site Request Forgery """ + f"""{colors.GREY}Audit Toolkit{colors.END} +""" +) + if not len(sys.argv) > 1: parser.print_help() quit() @@ -68,8 +199,14 @@ # Print out XSRFProbe version if args.version: - print('\033[1;96m [+] \033[1;91mXSRFProbe Version\033[0m : v'+__version__) - print('\033[1;96m [+] \033[1;91mXSRFProbe License\033[0m : '+__license__+'\n') + print( + f"{colors.CYAN} [+] {colors.RED}XSRFProbe Version{colors.END} : v" + __version__ + ) + print( + f"{colors.CYAN} [+] {colors.RED}XSRFProbe License{colors.END} : " + + __license__ + + "\n" + ) quit() # Now lets update some global config variables @@ -94,13 +231,13 @@ # Updating main root url if not args.version and not args.update: - if args.url: # and not args.help: - if 'http' in args.url: + if args.url: # and not args.help: + if "http" in args.url: config.SITE_URL = args.url else: - config.SITE_URL = 'http://'+args.url + config.SITE_URL = "http://" + args.url else: - print(R+'You must supply a url/endpoint.') + print(colors.R + "You must supply a url/endpoint.") # Crawl the site if --crawl supplied. if args.crawl: @@ -110,7 +247,7 @@ if args.cookie: # Assigning Cookie - for cook in args.cookie.split(','): + for cook in args.cookie.split(","): config.COOKIE_VALUE.append(cook) # This is necessary when a cookie value is supplied # Since if the user-agent used to make the request changes @@ -135,17 +272,19 @@ # NOTE: As a default idea, when the user supplies custom headers, we # simply add the custom headers to a list of existing headers in # files/config.py. - # Uncomment the following lines to just reinitialise the headers everytime + # Uncomment the following lines to just reinitialise the headers every time # they make a request. # - #config.HEADER_VALUES = {} - for m in args.headers.split(','): - config.HEADER_VALUES[m.split('=')[0].strip()] = m.split('=')[1].strip() # nice hack ;) + # config.HEADER_VALUES = {} + for m in args.headers.split(","): + config.HEADER_VALUES[m.split("=")[0].strip()] = m.split("=")[ + 1 + ].strip() # nice hack ;) if args.exclude: exc = args.exclude - #config.EXCLUDE_URLS = [s for s in exc.split(',').strip()] - m = exc.split(',').strip() + # config.EXCLUDE_URLS = [s for s in exc.split(',').strip()] + m = exc.split(",").strip() for s in m: config.EXCLUDE_DIRS.append(urllib.parse.urljoin(config.SITE_URL, s)) @@ -153,41 +292,49 @@ # If random-agent argument supplied... config.USER_AGENT_RANDOM = True # Turn off a single User-Agent mechanism... - config.USER_AGENT = '' + config.USER_AGENT = "" if config.SITE_URL: try: if args.output: # If output directory is mentioned... try: - if not os.path.exists(args.output+tld.get_fld(config.SITE_URL)): - os.makedirs(args.output+tld.get_fld(config.SITE_URL)) + if not os.path.exists(f"{args.output}{tld.get_fld(config.SITE_URL)}"): + os.makedirs(f"{args.output}{tld.get_fld(config.SITE_URL)}") except FileExistsError: pass - config.OUTPUT_DIR = args.output+tld.get_fld(config.SITE_URL) + '/' + + config.OUTPUT_DIR = f"{args.output}{tld.get_fld(config.SITE_URL)}/" else: try: - os.makedirs('xsrfprobe-output/'+tld.get_fld(config.SITE_URL)) + os.makedirs(f"xsrfprobe-output/{tld.get_fld(config.SITE_URL)}") except FileExistsError: pass - config.OUTPUT_DIR = 'xsrfprobe-output/'+tld.get_fld(config.SITE_URL) + '/' + + config.OUTPUT_DIR = f"xsrfprobe-output/{tld.get_fld(config.SITE_URL)}/" + # When this exception turns out, we know the user has supplied a IP not domain except tld.exceptions.TldDomainNotFound: direc = re.search(IP, config.SITE_URL).group(0) if args.output: # If output directory is mentioned... try: - if not os.path.exists(args.output+direc): - os.makedirs(args.output+direc) + if not os.path.exists(f"{args.output}{direc}"): + os.makedirs(f"{args.output}{direc}") except FileExistsError: pass - config.OUTPUT_DIR = args.output+direc + '/' + + config.OUTPUT_DIR = f"{args.output}{direc}/" else: try: - os.makedirs('xsrfprobe-output/'+direc) + os.makedirs(f"xsrfprobe-output/{direc}") except FileExistsError: pass - config.OUTPUT_DIR = 'xsrfprobe-output/'+direc + '/' + + config.OUTPUT_DIR = f"xsrfprobe-output/{direc}/" if args.quiet: config.DEBUG = False + +if args.json: + config.JSON_OUTPUT = True diff --git a/xsrfprobe/core/prettify.py b/xsrfprobe/core/prettify.py index 12b6843..0ff509c 100644 --- a/xsrfprobe/core/prettify.py +++ b/xsrfprobe/core/prettify.py @@ -1,22 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe import re -from xsrfprobe.core.colors import * + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + def formPrettify(response): - ''' + """ The main aim for this is to beautify the forms that will be displayed on the terminal. - ''' + """ highlighted = [] response = response.splitlines() for newLine in response: @@ -24,51 +28,53 @@ def formPrettify(response): # Find starting tags pattern = re.findall(r"""(<+\w+>)""", line) for grp in pattern: - starttag = ''.join(grp) + starttag = "".join(grp) if starttag: - line = line.replace(starttag, color.BLUE + starttag + color.END) + line = line.replace(starttag, colors.BLUE + starttag + colors.END) # Find attributes - pattern = re.findall(r'''(\s\w+=)''', line) + pattern = re.findall(r"""(\s\w+=)""", line) for grp in pattern: - stu = ''.join(grp) + stu = "".join(grp) if stu: - line = line.replace(stu, color.CYAN + stu + color.END) + line = line.replace(stu, colors.CYAN + stu + colors.END) # Find ending tags - pattern = re.findall(r'''()''', line) + pattern = re.findall(r"""()""", line) for grp in pattern: - endtag = ''.join(grp) + endtag = "".join(grp) if endtag: - line = line.replace(endtag, color.CYAN + endtag + color.END) + line = line.replace(endtag, colors.CYAN + endtag + colors.END) if line != newLine: highlighted.append(line) else: - highlighted.append(color.GREY+newLine) + highlighted.append(colors.GREY + newLine) for h in highlighted: - print(' '+h) + print(" " + h) + def indentPrettify(soup, indent=2): # where desired_indent is number of spaces as an int() - pretty_soup = str() - previous_indent = 0 - # iterate over each line of a prettified soup - for line in soup.prettify().split("\n"): - # returns the index for the opening html tag '<' - current_indent = str(line).find("<") - # which is also represents the number of spaces in the lines indentation - if current_indent == -1 or current_indent > previous_indent + 2: - current_indent = previous_indent + 1 - # str.find() will equal -1 when no '<' is found. This means the line is some kind - # of text or script instead of an HTML element and should be treated as a child - # of the previous line. also, current_indent should never be more than previous + 1. - previous_indent = current_indent - pretty_soup += writeOut(line, current_indent, indent) - return pretty_soup + pretty_soup = str() + previous_indent = 0 + # iterate over each line of a prettified soup + for line in soup.prettify().split("\n"): + # returns the index for the opening html tag '<' + current_indent = str(line).find("<") + # which is also represents the number of spaces in the lines indentation + if current_indent == -1 or current_indent > previous_indent + 2: + current_indent = previous_indent + 1 + # str.find() will equal -1 when no '<' is found. This means the line is some kind + # of text or script instead of an HTML element and should be treated as a child + # of the previous line. also, current_indent should never be more than previous + 1. + previous_indent = current_indent + pretty_soup += writeOut(line, current_indent, indent) + return pretty_soup + def writeOut(line, current_indent, desired_indent): - new_line = "" - spaces_to_add = (current_indent * desired_indent) - current_indent - if spaces_to_add > 0: - for i in range(spaces_to_add): - new_line += " " - new_line += str(line) + "\n" - return new_line + new_line = "" + spaces_to_add = (current_indent * desired_indent) - current_indent + if spaces_to_add > 0: + for i in range(spaces_to_add): + new_line += " " + new_line += str(line) + "\n" + return new_line diff --git a/xsrfprobe/core/randua.py b/xsrfprobe/core/randua.py index 581c881..b7c1c81 100644 --- a/xsrfprobe/core/randua.py +++ b/xsrfprobe/core/randua.py @@ -1,58 +1,58 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe import random -import datetime user_agent_list = [ # Chrome - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36', - 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', - 'Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', - 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', - 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36', - 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36', - 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36', - 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36', + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", # Firefox - 'Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)', - 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko', - 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)', - 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko', - 'Mozilla/5.0 (Windows NT 6.2; WOW64; Trident/7.0; rv:11.0) like Gecko', - 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko', - 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)', - 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko', - 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)', - 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; Trident/7.0; rv:11.0) like Gecko', - 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)', - 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)', - 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)', + "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)", + "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)", + "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko", + "Mozilla/5.0 (Windows NT 6.2; WOW64; Trident/7.0; rv:11.0) like Gecko", + "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)", + "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; Trident/7.0; rv:11.0) like Gecko", + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)", # Opera - 'Opera/9.80 (X11; Linux x86_64; U; bg) Presto/2.8.131 Version/11.10', - 'Opera/9.80 (Windows NT 6.0; U; en) Presto/2.8.99 Version/11.10', - 'Opera/9.80 (Windows NT 5.1; U; zh-tw) Presto/2.8.131 Version/11.10', - 'Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1', - 'Opera/9.80 (X11; Linux x86_64; U; Ubuntu/10.10 (maverick); pl) Presto/2.7.62 Version/11.01', - 'Opera/9.80 (X11; Linux i686; U; ja) Presto/2.7.62 Version/11.01', - 'Opera/9.80 (X11; Linux i686; U; fr) Presto/2.7.62 Version/11.01', - 'Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01', - 'Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.7.62 Version/11.01', - 'Opera/9.80 (Windows NT 6.1; U; sv) Presto/2.7.62 Version/11.01', - 'Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.01', - 'Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.7.62 Version/11.01', - 'Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.7.62 Version/11.01' + "Opera/9.80 (X11; Linux x86_64; U; bg) Presto/2.8.131 Version/11.10", + "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.8.99 Version/11.10", + "Opera/9.80 (Windows NT 5.1; U; zh-tw) Presto/2.8.131 Version/11.10", + "Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1", + "Opera/9.80 (X11; Linux x86_64; U; Ubuntu/10.10 (maverick); pl) Presto/2.7.62 Version/11.01", + "Opera/9.80 (X11; Linux i686; U; ja) Presto/2.7.62 Version/11.01", + "Opera/9.80 (X11; Linux i686; U; fr) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; sv) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.7.62 Version/11.01", ] + def RandomAgent(): return random.choice(user_agent_list) diff --git a/xsrfprobe/core/request.py b/xsrfprobe/core/request.py index afd8749..62283c4 100644 --- a/xsrfprobe/core/request.py +++ b/xsrfprobe/core/request.py @@ -1,18 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -import requests, time +import time from urllib.parse import urljoin -from xsrfprobe.core.colors import * -from xsrfprobe.files.config import * + +import requests + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +from xsrfprobe.files.config import ( + HEADER_VALUES, + COOKIE_VALUE, + USER_AGENT_RANDOM, + USER_AGENT, + DELAY_VALUE, + DISPLAY_HEADERS, + TIMEOUT_VALUE, + VERIFY_CERT, + FILE_EXTENSIONS, + EXECUTABLES, +) from xsrfprobe.core.verbout import verbout from xsrfprobe.core.randua import RandomAgent from xsrfprobe.files.discovered import FILES_EXEC @@ -22,99 +39,131 @@ # Set Cookie if COOKIE_VALUE: - headers['Cookie'] = ','.join(cookie for cookie in COOKIE_VALUE) + headers["Cookie"] = ",".join(cookie for cookie in COOKIE_VALUE) # Set User-Agent if USER_AGENT_RANDOM: - headers['User-Agent'] = RandomAgent() + headers["User-Agent"] = RandomAgent() if USER_AGENT: - headers['User-Agent'] = USER_AGENT + headers["User-Agent"] = USER_AGENT + def Post(url, action, data): - ''' + """ The main use of this function is as a Form Requester [POST]. - ''' + """ global headers, TIMEOUT_VALUE, VERIFY_CERT time.sleep(DELAY_VALUE) # If delay param has been supplied - verbout(GR, 'Preparing the request...') + verbout(colors.GR, "Preparing the request...") + if DISPLAY_HEADERS: preqheaders(headers) - verbout(GR, 'Processing the '+color.GREY+'POST'+color.END+' Request...') + + verbout(colors.GR, f"Processing the {colors.GREY}POST{colors.END} Request...") main_url = urljoin(url, action) # join url and action try: # Make the POST Request. - response = requests.post(main_url, headers=headers, data=data, - timeout=TIMEOUT_VALUE, verify=VERIFY_CERT) + response = requests.post( + main_url, + headers=headers, + data=data, + timeout=TIMEOUT_VALUE, + verify=VERIFY_CERT, + ) if DISPLAY_HEADERS: presheaders(response.headers) return response # read data content except requests.exceptions.HTTPError as e: # if error - verbout(R, "HTTP Error : "+main_url) + verbout(colors.R, "HTTP Error : " + main_url) ErrorLogger(main_url, e.__str__()) return None except requests.exceptions.ConnectionError as e: - verbout(R, 'Connection Aborted : '+main_url) + verbout(colors.R, "Connection Aborted : " + main_url) ErrorLogger(main_url, e.__str__()) return None except requests.exceptions.ReadTimeout as e: - verbout(R, 'Exception at: '+color.GREY+url) - verbout(R, 'Error: Read Timeout. Consider increasing the timeout value via --timeout.') + verbout(colors.R, "Exception at: " + colors.GREY + url) + verbout( + colors.R, + "Error: Read Timeout. Consider increasing the timeout value via --timeout.", + ) ErrorLogger(url, e.__str__()) return None except ValueError as e: # again if valuerror - verbout(R, "Value Error : "+main_url) + verbout(colors.R, "Value Error : " + main_url) ErrorLogger(main_url, e.__str__()) return None except Exception as e: - verbout(R, "Exception Caught: "+e.__str__()) + verbout(colors.R, "Exception Caught: " + e.__str__()) ErrorLogger(main_url, e.__str__()) return None # if at all nothing happens :( + def Get(url, headers=headers): - ''' + """ The main use of this function is as a Url Requester [GET]. - ''' + """ global TIMEOUT_VALUE, VERIFY_CERT # We do not verify the request while GET requests time.sleep(DELAY_VALUE) # We make requests after the time delay # Making sure the url is not a file - if url.split('.')[-1].lower() in (FILE_EXTENSIONS or EXECUTABLES): + if url.split(".")[-1].lower() in (FILE_EXTENSIONS or EXECUTABLES): FILES_EXEC.append(url) - verbout(G, 'Found File: '+color.BLUE+url) + verbout(colors.G, "Found File: " + colors.BLUE + url) return None try: - verbout(GR, 'Preparing the request...') + verbout(colors.GR, "Preparing the request...") + if DISPLAY_HEADERS: preqheaders(headers) - verbout(GR, 'Processing the '+color.GREY+'GET'+color.END+' Request...') - req = requests.get(url, headers=headers, timeout=TIMEOUT_VALUE, - stream=False, verify=VERIFY_CERT) + + verbout( + colors.GR, + f"Processing the {colors.GREY}GET{colors.END} Request...", + ) + + req = requests.get( + url, + headers=headers, + timeout=TIMEOUT_VALUE, + stream=False, + verify=VERIFY_CERT, + ) + + if req is None: + verbout(colors.RED, f" [!] Failed to get a response from: {url}") + return None + # Displaying headers if DISPLAY_HEADERS is 'True' if DISPLAY_HEADERS: presheaders(req.headers) + # Return the object return req except requests.exceptions.MissingSchema as e: - verbout(R, 'Exception at: '+color.GREY+url) - verbout(R, 'Error: Invalid URL Format') + verbout(colors.R, "Exception at: " + colors.GREY + url) + verbout(colors.R, "Error: Invalid URL Format") ErrorLogger(url, e.__str__()) return None except requests.exceptions.ReadTimeout as e: - verbout(R, 'Exception at: '+color.GREY+url) - verbout(R, 'Error: Read Timeout. Consider increasing the timeout value via --timeout.') + verbout(colors.R, "Exception at: " + colors.GREY + url) + verbout( + colors.R, + "Error: Read Timeout. Consider increasing the timeout value via --timeout.", + ) ErrorLogger(url, e.__str__()) return None except requests.exceptions.HTTPError as e: # if error - verbout(R, "HTTP Error Encountered : "+url) + verbout(colors.R, "HTTP Error Encountered : " + url) ErrorLogger(url, e.__str__()) return None except requests.exceptions.ConnectionError as e: - verbout(R, 'Connection Aborted : '+url) + verbout(colors.R, "Connection Aborted : " + url) ErrorLogger(url, e.__str__()) return None except Exception as e: - verbout(R, "Exception Caught: "+e.__str__()) + verbout(colors.R, "Exception Caught: " + e.__str__()) ErrorLogger(url, e.__str__()) return None diff --git a/xsrfprobe/core/updater.py b/xsrfprobe/core/updater.py index c8679cd..0bf1ada 100644 --- a/xsrfprobe/core/updater.py +++ b/xsrfprobe/core/updater.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -12,27 +12,41 @@ import os from requests import get from xsrfprobe import __version__ -from xsrfprobe.core.colors import * + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + def updater(): - ''' + """ Function to update XSRFProbe seamlessly. - ''' - print(GR+'Checking for updates...') - vno = get('https://raw.githubusercontent.com/0xInfection/XSRFProbe/master/xsrfprobe/files/VersionNum').text - print(GR+'Version on GitHub: '+color.CYAN+vno.strip()) - print(GR+'Version You Have : '+color.CYAN+__version__) + """ + print(colors.GR + "Checking for updates...") + vno = get( + "https://raw.githubusercontent.com/0xInfection/XSRFProbe/master/xsrfprobe/files/VersionNum", + timeout=1, + ).text + + print(colors.GR + "Version on GitHub: " + colors.CYAN + vno.strip()) + print(colors.GR + "Version You Have : " + colors.CYAN + __version__) if vno != __version__: - print(G+'A new version of XSRFProbe is available!') - current_path = os.getcwd().split('/') # if you know it, you know it - folder = current_path[-1] # current directory name - path = '/'.join(current_path) # current directory path - choice = input(O+'Would you like to update? [Y/n] :> ').lower() - if choice != 'n': - print(GR+'Updating XSRFProbe...') - os.system('git clone --quiet https://github.com/0xInfection/XSRFProbe %s' % (folder)) - os.system('cp -r %s/%s/* %s && rm -r %s/%s/ 2>/dev/null' % (path, folder, path, path, folder)) - print(G+'Update successful!') + print(colors.G + "A new version of XSRFProbe is available!") + current_path = os.getcwd().split("/") # if you know it, you know it + folder = current_path[-1] # current directory name + path = "/".join(current_path) # current directory path + choice = input(colors.O + "Would you like to update? [Y/n] :> ").lower() + if choice != "n": + print(colors.GR + "Updating XSRFProbe...") + os.system( + "git clone --quiet https://github.com/0xInfection/XSRFProbe %s" + % (folder) + ) + os.system( + "cp -r %s/%s/* %s && rm -r %s/%s/ 2>/dev/null" + % (path, folder, path, path, folder) + ) + print(colors.G + "Update successful!") else: - print(G+'XSRFProbe is up to date!') + print(colors.G + "XSRFProbe is up to date!") quit() diff --git a/xsrfprobe/core/utils.py b/xsrfprobe/core/utils.py index 5f49e4c..2adf7e8 100644 --- a/xsrfprobe/core/utils.py +++ b/xsrfprobe/core/utils.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -11,11 +11,12 @@ from difflib import SequenceMatcher -def sameSequence(str1,str2): - ''' + +def sameSequence(str1, str2): + """ This function is intended to find same sequence between str1 and str2. - ''' + """ # Initialize SequenceMatcher object with # Input string seqMatch = SequenceMatcher(None, str1, str2) @@ -25,23 +26,25 @@ def sameSequence(str1,str2): match = seqMatch.find_longest_match(0, len(str1), 0, len(str2)) # Print longest substring - if (match.size!=0): - return (str1[match.a: match.a + match.size]) + if match.size != 0: + return str1[match.a : match.a + match.size] else: - return '' + return "" + -def replaceStrIndex(text, index=0, replacement=''): - ''' +def replaceStrIndex(text, index=0, replacement=""): + """ This method returns a tampered string by replacement - ''' - return '%s%s%s' % (text[:index], replacement, text[index+1:]) + """ + return "%s%s%s" % (text[:index], replacement, text[index + 1 :]) + def checkDuplicates(iterable): - ''' + """ This function works as a byte sequence checker for tuples passed onto this function. - ''' + """ seen = set() for x in iterable: if x in seen: @@ -49,7 +52,8 @@ def checkDuplicates(iterable): seen.add(x) return False -def byteString(s, encoding='utf8'): + +def byteString(s, encoding="utf8"): """ Return a byte-string version of 's', Encoded as utf-8. @@ -60,13 +64,14 @@ def byteString(s, encoding='utf8'): s = str(s) return s -def subSequence(str1,str2): - ''' + +def subSequence(str1, str2): + """ Returns whether 'str1' and 'str2' are subsequence of one another. - ''' - j = 0 # Index of str1 - i = 0 # Index of str2 + """ + j = 0 # Index of str1 + i = 0 # Index of str2 # Traverse both str1 and str2 # Compare current character of str2 with @@ -74,10 +79,10 @@ def subSequence(str1,str2): # If matched, then move ahead in str1 m = len(str1) n = len(str2) - while j (3 - args.verbose): -# print verb_args[1] + print(stat + content_info) diff --git a/xsrfprobe/files/VersionNum b/xsrfprobe/files/VersionNum index a625450..a9fa699 100644 --- a/xsrfprobe/files/VersionNum +++ b/xsrfprobe/files/VersionNum @@ -1 +1 @@ -2.3.1 \ No newline at end of file +4.4.29 \ No newline at end of file diff --git a/xsrfprobe/files/__init__.py b/xsrfprobe/files/__init__.py index d79d32c..7dc4748 100644 --- a/xsrfprobe/files/__init__.py +++ b/xsrfprobe/files/__init__.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection -#This module requires XSRFProbe -#https://github.com/0xInfection/XSRFProbe +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe diff --git a/xsrfprobe/files/config.py b/xsrfprobe/files/config.py index 89de426..00cfa37 100644 --- a/xsrfprobe/files/config.py +++ b/xsrfprobe/files/config.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -16,9 +16,10 @@ global HEADER_VALUES, TIMEOUT_VALUE, REFERER_ORIGIN_CHECKS, REFERER_URL, POST_BASED global DISPLAY_HEADERS, EXECUTABLES, FILE_EXTENSIONS, POC_GENERATION, OUTPUT_DIR, VERIFY_CERT global CRAWL_SITE, TOKEN_CHECKS, DELAY_VALUE, SCAN_ANALYSIS, EXCLUDE_DIRS, GEN_MALICIOUS +global NO_COLORS # Site Url to be scanned (Required) -SITE_URL = '' +SITE_URL = "" # Switch for whether to crawl the site or not CRAWL_SITE = False @@ -35,12 +36,12 @@ # User-Agent to be used (If COOKIE_VALUE supplied). # -# This is standard User-Agent emulating Chrome 68 on Windows 10 +# This is standard User-Agent emulating Chrome 68 on Windows 10 # # NOTE: This is a precaution in case the cookie value is supplied, # if the user-agent gets changed from time to time, the remote # application might trigger up some protection agents -USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36' +USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36" # Cookie value to be sent alongwith the requests. This option is particularly # needed for a wholesome check on CSRFs. Since for a basic successful CSRF attack @@ -60,20 +61,20 @@ # Header values to be used (Modify it as per your need) HEADER_VALUES = { - 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', - 'Accept-Language' : 'en-US,en;q=0.9', - 'Accept-Encoding' : 'gzip, deflate, br', - 'Sec-Fetch-Mode' : 'navigate', - 'DNT' : '1', # Do Not Track Request Header :D - 'Connection' : 'close' - } + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate, br", + "Sec-Fetch-Mode": "navigate", + "DNT": "1", # Do Not Track Request Header :D + "Connection": "close", +} # Email value to be supplied when parsing/filling forms # You can modify it as per your need :) -EMAIL_VALUE = 'csrf.testing@xsrfprobe.tld' +EMAIL_VALUE = "csrf.testing@xsrfprobe.tld" # Plaintext value to be supplied when parsing/filling forms -TEXT_VALUE = 'csrftesting' +TEXT_VALUE = "csrftesting" # Request Timeout (Keep the max. timeout value to 10s) TIMEOUT_VALUE = 7 @@ -117,11 +118,11 @@ # Referer Url (Change It Accordingly) # eg. Use one of your Subdomains (Same Origin Policy)) -REFERER_URL = 'http://not-a-valid-referer.xsrfprobe-csrftesting.0xinfection.xyz' +REFERER_URL = "http://not-a-valid-referer.xsrfprobe-csrftesting.0xinfection.xyz" # Origin Url (Change It Accordingly) # eg. Use one of your Subdomains (Same Origin Policy)) -ORIGIN_URL = 'http://not-a-valid-origin.xsrfprobe-csrftesting.0xinfection.xyz' +ORIGIN_URL = "http://not-a-valid-origin.xsrfprobe-csrftesting.0xinfection.xyz" # The length of the custom token to be generated for params # @@ -138,13 +139,19 @@ # Output directory where everything (including logs) are to # be stored -OUTPUT_DIR = '' +OUTPUT_DIR = "" + +# Allow JSON output +JSON_OUTPUT = False # This option is for displaying the headers received as response. # Turn this off if you don't want to see the headers on the # terminal, or if it feels irritating. DISPLAY_HEADERS = False +# No colors option +NO_COLORS = False + # Option for controlling post-scan analysis. Turning it off # results in not analysing the tokens gathered. SCAN_ANALYSIS = True @@ -159,6 +166,39 @@ # A list of file extensions that might be come across while scanning # and crawling -FILE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'pdf', 'js', 'css', 'ico', 'bmp', 'svg', 'json', 'xml', 'xls', 'csv', 'docx',] +FILE_EXTENSIONS = [ + "png", + "jpg", + "jpeg", + "pdf", + "js", + "css", + "ico", + "bmp", + "svg", + "json", + "xml", + "xls", + "csv", + "docx", +] # These are a list of executable files that are found on the web -EXECUTABLES = ['deb', 'bat', 'exe', 'msu', 'msi', 'apk', 'bin', 'csh', 'inf', 'ini', 'msc', 'osx' ,'out', 'vbe', 'ws', 'msp', 'jse'] +EXECUTABLES = [ + "deb", + "bat", + "exe", + "msu", + "msi", + "apk", + "bin", + "csh", + "inf", + "ini", + "msc", + "osx", + "out", + "vbe", + "ws", + "msp", + "jse", +] diff --git a/xsrfprobe/files/dcodelist.py b/xsrfprobe/files/dcodelist.py index 14efd1c..1d5ab27 100644 --- a/xsrfprobe/files/dcodelist.py +++ b/xsrfprobe/files/dcodelist.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -14,100 +14,100 @@ # Token hash encoding detection db, thanks to a book, Python for Penetation Testers, LOL! HASH_DB = ( - ("Blowfish (Eggdrop)", r"^\+[a-zA-Z0-9\/\.]{12}$"), - ("Blowfish (OpenBSD)", r"^\$2a\$[0-9]{0,2}?\$[a-zA-Z0-9\/\.]{53}$"), - ("Blowfish Crypt", r"^\$2[axy]{0,1}\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), - ("DES (Unix)", r"^.{0,2}[a-zA-Z0-9\/\.]{11}$"), - ("MD5 (Unix)", r"^\$1\$.{0,8}\$[a-zA-Z0-9\/\.]{22}$"), - ("MD5 (APR)", r"^\$apr1\$.{0,8}\$[a-zA-Z0-9\/\.]{22}$"), - ("MD5 (MyBB)", r"^[a-fA-F0-9]{32}:[a-z0-9]{8}$"), - ("MD5 (ZipMonster)", r"^[a-fA-F0-9]{32}$"), - ("MD5 crypt", r"^\$1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), - ("MD5 apache crypt", r"^\$apr1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), - ("MD5 (Joomla)", r"^[a-fA-F0-9]{32}:[a-zA-Z0-9]{16,32}$"), - ("MD5 (Wordpress)", r"^\$P\$[a-zA-Z0-9\/\.]{31}$"), - ("MD5 (phpBB3)", r"^\$H\$[a-zA-Z0-9\/\.]{31}$"), - ("MD5 (Cisco PIX)", r"^[a-zA-Z0-9\/\.]{16}$"), - ("MD5 (osCommerce)", r"^[a-fA-F0-9]{32}:[a-zA-Z0-9]{2}$"), - ("MD5 (Palshop)", r"^[a-fA-F0-9]{51}$"), - ("MD5 (IP.Board)", r"^[a-fA-F0-9]{32}:.{5}$"), - ("MD5 (Chap)", r"^[a-fA-F0-9]{32}:[0-9]{32}:[a-fA-F0-9]{2}$"), - ("Juniper Netscreen/SSG (ScreenOS)", r"^[a-zA-Z0-9]{30}:[a-zA-Z0-9]{4,}$"), - ("Fortigate (FortiOS)", r"^[a-fA-F0-9]{47}$"), - ("Minecraft (Authme)", r"^\$sha\$[a-zA-Z0-9]{0,16}\$[a-fA-F0-9]{64}$"), - ("Lotus Domino", r"^\(?[a-zA-Z0-9\+\/]{20}\)?$"), - ("Lineage II C4", r"^0x[a-fA-F0-9]{32}$"), - ("CRC-96 (ZIP)", r"^[a-fA-F0-9]{24}$"), - ("NT crypt", r"^\$3\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), - ("Skein-1024", r"^[a-fA-F0-9]{256}$"), - ("RIPEMD-320", r"^[A-Fa-f0-9]{80}$"), - ("EPi hash", r"^0x[A-F0-9]{60}$"), - ("EPiServer 6.x < v4", r"^\$episerver\$\*0\*[a-zA-Z0-9]{22}==\*[a-zA-Z0-9\+]{27}$"), - ("EPiServer 6.x >= v4", r"^\$episerver\$\*1\*[a-zA-Z0-9]{22}==\*[a-zA-Z0-9]{43}$"), - ("Cisco IOS SHA256", r"^[a-zA-Z0-9]{43}$"), - ("oRACLE 11g/12c", r"^(S:)?[a-f0-9]{40}(:)?[a-f0-9]{20}$"), - ("SHA-1 (Django)", r"^sha1\$.{0,32}\$[a-fA-F0-9]{40}$"), - ("SHA-1 crypt", r"^\$4\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), - ("SHA-1 (Hex)", r"^[a-fA-F0-9]{40}$"), - ("SHA-1 (LDAP) Base64", r"^\{SHA\}[a-zA-Z0-9+/]{27}=$"), - ("SHA-1 (LDAP) Base64 + salt", r"^\{SSHA\}[a-zA-Z0-9+/]{28,}[=]{0,3}$"), - ("SHA-512 (Drupal)", r"^\$S\$[a-zA-Z0-9\/\.]{52}$"), - ("SHA-512 crypt", r"^\$6\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), - ("SHA-256 (Django)", r"^sha256\$.{0,32}\$[a-fA-F0-9]{64}$"), - ("SHA-256 crypt", r"^\$5\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), - ("SHA-384 (Django)", r"^sha384\$.{0,32}\$[a-fA-F0-9]{96}$"), - ("SHA-256 (Unix)", r"^\$5\$.{0,22}\$[a-zA-Z0-9\/\.]{43,69}$"), - ("SHA-512 (Unix)", r"^\$6\$.{0,22}\$[a-zA-Z0-9\/\.]{86}$"), - ("SHA-384", r"^[a-fA-F0-9]{96}$"), - ("SHA-512", r"^[a-fA-F0-9]{128}$"), - ("SipHash", r"^[a-f0-9]{16}:2:4:[a-f0-9]{32}$"), - ("SSHA-1", r"^({SSHA})?[a-zA-Z0-9\+\/]{32,38}?(==)?$"), - ("SSHA-1 (Base64)", r"^\{SSHA\}[a-zA-Z0-9]{32,38}?(==)?$"), - ("SSHA-512 (Base64)", r"^\{SSHA512\}[a-zA-Z0-9+]{96}$"), - ("Oracle 11g", r"^S:[A-Z0-9]{60}$"), - ("SMF >= v1.1", r"^[a-fA-F0-9]{40}:[0-9]{8}&"), - ("MySQL 5.x", r"^\*[a-f0-9]{40}$"), - ("MySQL 3.x", r"^[a-fA-F0-9]{16}$"), - ("OSX v10.7", r"^[a-fA-F0-9]{136}$"), - ("OSX v10.8", r"^\$ml\$[a-fA-F0-9$]{199}$"), - ("SAM (LM_Hash:NT_Hash)", r"^[a-fA-F0-9]{32}:[a-fA-F0-9]{32}$"), - ("MSSQL (2000)", r"^0x0100[a-f0-9]{0,8}?[a-f0-9]{80}$"), - ("Cisco Type 7", r"^[a-f0-9]{4,}$"), - ("Snefru-256", r"^(\\$snefru\\$)?[a-f0-9]{64}$"), - ("MSSQL (2005)", r"^0x0100[a-f0-9]{0,8}?[a-f0-9]{40}$"), - ("MSSQL (2012)", r"^0x02[a-f0-9]{0,10}?[a-f0-9]{128}$"), - ("TIGER-160 (HMAC)", r"^[a-f0-9]{40}$"), - ("SHA-256", r"^[a-fA-F0-9]{64}$"), - ("SHA-1 (Oracle)", r"^[a-fA-F0-9]{48}$"), - ("SHA-224", r"^[a-fA-F0-9]{56}$"), - ("Adler32", r"^[a-f0-9]{8}$"), - ("CRC-16-CCITT", r"^[a-fA-F0-9]{4}$"), - ("NTLM", r"^[0-9A-Fa-f]{32}$"), - ) + ("Blowfish (Eggdrop)", r"^\+[a-zA-Z0-9\/\.]{12}$"), + ("Blowfish (OpenBSD)", r"^\$2a\$[0-9]{0,2}?\$[a-zA-Z0-9\/\.]{53}$"), + ("Blowfish Crypt", r"^\$2[axy]{0,1}\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), + ("DES (Unix)", r"^.{0,2}[a-zA-Z0-9\/\.]{11}$"), + ("MD5 (Unix)", r"^\$1\$.{0,8}\$[a-zA-Z0-9\/\.]{22}$"), + ("MD5 (APR)", r"^\$apr1\$.{0,8}\$[a-zA-Z0-9\/\.]{22}$"), + ("MD5 (MyBB)", r"^[a-fA-F0-9]{32}:[a-z0-9]{8}$"), + ("MD5 (ZipMonster)", r"^[a-fA-F0-9]{32}$"), + ("MD5 crypt", r"^\$1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), + ("MD5 apache crypt", r"^\$apr1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), + ("MD5 (Joomla)", r"^[a-fA-F0-9]{32}:[a-zA-Z0-9]{16,32}$"), + ("MD5 (Wordpress)", r"^\$P\$[a-zA-Z0-9\/\.]{31}$"), + ("MD5 (phpBB3)", r"^\$H\$[a-zA-Z0-9\/\.]{31}$"), + ("MD5 (Cisco PIX)", r"^[a-zA-Z0-9\/\.]{16}$"), + ("MD5 (osCommerce)", r"^[a-fA-F0-9]{32}:[a-zA-Z0-9]{2}$"), + ("MD5 (Palshop)", r"^[a-fA-F0-9]{51}$"), + ("MD5 (IP.Board)", r"^[a-fA-F0-9]{32}:.{5}$"), + ("MD5 (Chap)", r"^[a-fA-F0-9]{32}:[0-9]{32}:[a-fA-F0-9]{2}$"), + ("Juniper Netscreen/SSG (ScreenOS)", r"^[a-zA-Z0-9]{30}:[a-zA-Z0-9]{4,}$"), + ("Fortigate (FortiOS)", r"^[a-fA-F0-9]{47}$"), + ("Minecraft (Authme)", r"^\$sha\$[a-zA-Z0-9]{0,16}\$[a-fA-F0-9]{64}$"), + ("Lotus Domino", r"^\(?[a-zA-Z0-9\+\/]{20}\)?$"), + ("Lineage II C4", r"^0x[a-fA-F0-9]{32}$"), + ("CRC-96 (ZIP)", r"^[a-fA-F0-9]{24}$"), + ("NT crypt", r"^\$3\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), + ("Skein-1024", r"^[a-fA-F0-9]{256}$"), + ("RIPEMD-320", r"^[A-Fa-f0-9]{80}$"), + ("EPi hash", r"^0x[A-F0-9]{60}$"), + ("EPiServer 6.x < v4", r"^\$episerver\$\*0\*[a-zA-Z0-9]{22}==\*[a-zA-Z0-9\+]{27}$"), + ("EPiServer 6.x >= v4", r"^\$episerver\$\*1\*[a-zA-Z0-9]{22}==\*[a-zA-Z0-9]{43}$"), + ("Cisco IOS SHA256", r"^[a-zA-Z0-9]{43}$"), + ("oRACLE 11g/12c", r"^(S:)?[a-f0-9]{40}(:)?[a-f0-9]{20}$"), + ("SHA-1 (Django)", r"^sha1\$.{0,32}\$[a-fA-F0-9]{40}$"), + ("SHA-1 crypt", r"^\$4\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), + ("SHA-1 (Hex)", r"^[a-fA-F0-9]{40}$"), + ("SHA-1 (LDAP) Base64", r"^\{SHA\}[a-zA-Z0-9+/]{27}=$"), + ("SHA-1 (LDAP) Base64 + salt", r"^\{SSHA\}[a-zA-Z0-9+/]{28,}[=]{0,3}$"), + ("SHA-512 (Drupal)", r"^\$S\$[a-zA-Z0-9\/\.]{52}$"), + ("SHA-512 crypt", r"^\$6\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), + ("SHA-256 (Django)", r"^sha256\$.{0,32}\$[a-fA-F0-9]{64}$"), + ("SHA-256 crypt", r"^\$5\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), + ("SHA-384 (Django)", r"^sha384\$.{0,32}\$[a-fA-F0-9]{96}$"), + ("SHA-256 (Unix)", r"^\$5\$.{0,22}\$[a-zA-Z0-9\/\.]{43,69}$"), + ("SHA-512 (Unix)", r"^\$6\$.{0,22}\$[a-zA-Z0-9\/\.]{86}$"), + ("SHA-384", r"^[a-fA-F0-9]{96}$"), + ("SHA-512", r"^[a-fA-F0-9]{128}$"), + ("SipHash", r"^[a-f0-9]{16}:2:4:[a-f0-9]{32}$"), + ("SSHA-1", r"^({SSHA})?[a-zA-Z0-9\+\/]{32,38}?(==)?$"), + ("SSHA-1 (Base64)", r"^\{SSHA\}[a-zA-Z0-9]{32,38}?(==)?$"), + ("SSHA-512 (Base64)", r"^\{SSHA512\}[a-zA-Z0-9+]{96}$"), + ("Oracle 11g", r"^S:[A-Z0-9]{60}$"), + ("SMF >= v1.1", r"^[a-fA-F0-9]{40}:[0-9]{8}&"), + ("MySQL 5.x", r"^\*[a-f0-9]{40}$"), + ("MySQL 3.x", r"^[a-fA-F0-9]{16}$"), + ("OSX v10.7", r"^[a-fA-F0-9]{136}$"), + ("OSX v10.8", r"^\$ml\$[a-fA-F0-9$]{199}$"), + ("SAM (LM_Hash:NT_Hash)", r"^[a-fA-F0-9]{32}:[a-fA-F0-9]{32}$"), + ("MSSQL (2000)", r"^0x0100[a-f0-9]{0,8}?[a-f0-9]{80}$"), + ("Cisco Type 7", r"^[a-f0-9]{4,}$"), + ("Snefru-256", r"^(\\$snefru\\$)?[a-f0-9]{64}$"), + ("MSSQL (2005)", r"^0x0100[a-f0-9]{0,8}?[a-f0-9]{40}$"), + ("MSSQL (2012)", r"^0x02[a-f0-9]{0,10}?[a-f0-9]{128}$"), + ("TIGER-160 (HMAC)", r"^[a-f0-9]{40}$"), + ("SHA-256", r"^[a-fA-F0-9]{64}$"), + ("SHA-1 (Oracle)", r"^[a-fA-F0-9]{48}$"), + ("SHA-224", r"^[a-fA-F0-9]{56}$"), + ("Adler32", r"^[a-f0-9]{8}$"), + ("CRC-16-CCITT", r"^[a-fA-F0-9]{4}$"), + ("NTLM", r"^[0-9A-Fa-f]{32}$"), +) # IP Regex -IP = r'((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' +IP = r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" # Get rid of Double ../../ -RID_DOUBLE = r'/\.\./' +RID_DOUBLE = r"/\.\./" # Get rid of ./'s -RID_SINGLE = r'\./' +RID_SINGLE = r"\./" # Complier based regex -RID_COMPILE = r'/[^/]*/../' +RID_COMPILE = r"/[^/]*/../" # Number based. -NUM_SUB = r'=[0-9]+' +NUM_SUB = r"=[0-9]+" # Number based compile. -NUM_COM = r'(title=)[^&]*' +NUM_COM = r"(title=)[^&]*" # Binary strings. -BINARY = r'^[01]+$' +BINARY = r"^[01]+$" # Decimal Strings. -DEC = r'&#.*;+' +DEC = r"&#.*;+" # Protocol Types -PROTOCOLS = r'(.*\/)[^\/]*' \ No newline at end of file +PROTOCOLS = r"(.*\/)[^\/]*" diff --git a/xsrfprobe/files/discovered.py b/xsrfprobe/files/discovered.py index 63bf204..ff99e8c 100644 --- a/xsrfprobe/files/discovered.py +++ b/xsrfprobe/files/discovered.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # Author: 0xInfection # This module requires XSRFProbe diff --git a/xsrfprobe/files/paramlist.py b/xsrfprobe/files/paramlist.py index a8d3b76..e374e03 100644 --- a/xsrfprobe/files/paramlist.py +++ b/xsrfprobe/files/paramlist.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -15,85 +15,83 @@ # # Feel free to add more of your tokens if you have. ;) COMMON_CSRF_NAMES = ( - # These are a list of known common tokens parameters - 'CSRFName', # OWASP CSRF_Guard - 'CSRFToken', # OWASP CSRF_Guard - 'csrf_token', # PHP NoCSRF Class - 'anticsrf', # AntiCsrfParam.java - '__RequestVerificationToken', # ASP.NET TokenParam - 'VerificationToken', # AntiCSRFParam.java - 'form_build_id', # Drupal CMS AntiCSRF - 'nonce', # WordPress Nonce - 'authenticity_token', # Ruby on Rails - 'csrf_param', # Ruby on Rails - 'TransientKey', # VanillaForums Param - 'csrf', # PHP CSRFProtect - 'AntiCSURF', # Anti CSURF (PHP) - 'YII_CSRF_TOKEN', # http://www.yiiframework.com/ - 'yii_anticsrf', # http://www.yiiframework.com/ - '[_token]', # Symfony 2.x - '_csrf_token', # Symfony 1.4 - 'csrfmiddlewaretoken', # Django 1.5 - 'ccm_token', # Concrete 5 CMS - 'XOOPS_TOKEN_REQUEST', # Xoops CMS - '_csrf', # Express JS Default Anti-CSRF - - # These are some other various token names I have seen in - # various websites. - # - # TODO: Add more similar csrf token parameters - 'token', - 'auth', - 'hash', - 'secret', - 'verify', - ) + # These are a list of known common tokens parameters + "CSRFName", # OWASP CSRF_Guard + "CSRFToken", # OWASP CSRF_Guard + "csrf_token", # PHP NoCSRF Class + "anticsrf", # AntiCsrfParam.java + "__RequestVerificationToken", # ASP.NET TokenParam + "VerificationToken", # AntiCSRFParam.java + "form_build_id", # Drupal CMS AntiCSRF + "nonce", # WordPress Nonce + "authenticity_token", # Ruby on Rails + "csrf_param", # Ruby on Rails + "TransientKey", # VanillaForums Param + "csrf", # PHP CSRFProtect + "AntiCSURF", # Anti CSURF (PHP) + "YII_CSRF_TOKEN", # http://www.yiiframework.com/ + "yii_anticsrf", # http://www.yiiframework.com/ + "[_token]", # Symfony 2.x + "_csrf_token", # Symfony 1.4 + "csrfmiddlewaretoken", # Django 1.5 + "ccm_token", # Concrete 5 CMS + "XOOPS_TOKEN_REQUEST", # Xoops CMS + "_csrf", # Express JS Default Anti-CSRF + # These are some other various token names I have seen in + # various websites. + # + # TODO: Add more similar csrf token parameters + "token", + "auth", + "hash", + "secret", + "verify", +) COMMON_CSRF_HEADERS = ( - # These are a list of HTTP Headers often found in requests - # of web applications using various frameworks. - 'CSRF-Token', # Express JS CSURF Middleware - 'XSRF-Token', # Node JS/ Express JS - 'X-CSRF-Token', # Ruby on Rails - 'X-XSRF-Token', # Express JS CSURF Middleware - # Some other probabilties - 'X-CSRF-Header', - 'X-XSRF-Header', - 'X-CSRF-Protection' - ) + # These are a list of HTTP Headers often found in requests + # of web applications using various frameworks. + "CSRF-Token", # Express JS CSURF Middleware + "XSRF-Token", # Node JS/ Express JS + "X-CSRF-Token", # Ruby on Rails + "X-XSRF-Token", # Express JS CSURF Middleware + # Some other probabilties + "X-CSRF-Header", + "X-XSRF-Header", + "X-CSRF-Protection", +) # TODO: Add and replace with more valid and arguable exclusion lists EXCLUSIONS_LIST = ( - 'logout', - 'action=out', - 'action=logoff', - 'action=delete', - 'UserLogout', - 'osCsid', - 'action=logout', - ) + "logout", + "action=out", + "action=logoff", + "action=delete", + "UserLogout", + "osCsid", + "action=logout", +) # List of common errors shown when token is tampered. TOKEN_ERRORS = ( - 'the required form field', - 'token could not', - 'invalid token', - 'wrong', - 'error', - 'not valid', - 'please check your request', - 'your browser did something unexpected', - 'csrf' - 'clearing your cookies', - 'tampered token', - 'null', - 'unacceptable', - 'false', - 'void', - 'incorrect', - 'inoperative', - 'faulty', - 'absurd', - 'inconsistent', - 'not acceptable', - ) + "the required form field", + "token could not", + "invalid token", + "wrong", + "error", + "not valid", + "please check your request", + "your browser did something unexpected", + "csrf" "clearing your cookies", + "tampered token", + "null", + "unacceptable", + "false", + "void", + "incorrect", + "inoperative", + "faulty", + "absurd", + "inconsistent", + "not acceptable", +) diff --git a/xsrfprobe/modules/Analysis.py b/xsrfprobe/modules/Analysis.py index 5ad57cb..1e03a79 100644 --- a/xsrfprobe/modules/Analysis.py +++ b/xsrfprobe/modules/Analysis.py @@ -1,58 +1,97 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe import stringdist -import itertools, time -from xsrfprobe.core.colors import * +import itertools +import time + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + from xsrfprobe.modules.Entropy import calcEntropy from xsrfprobe.core.verbout import verbout from xsrfprobe.core.utils import sameSequence, byteString from xsrfprobe.files.discovered import REQUEST_TOKENS from xsrfprobe.core.logger import VulnLogger, NovulLogger + def Analysis(): - ''' + """ The main idea behind this is to observe and analyse the patterns in which the CSRF tokens are generated by server. - ''' + """ ctr = 0 # Counter variable set to 0 # Checking if the no of tokens is greater than 1 if len(REQUEST_TOKENS) > 1: - verbout(color.RED, '\n +--------------+') - verbout(color.RED, ' | Analysis |') - verbout(color.RED, ' +--------------+\n') - print(GR+'Proceeding for post-scan analysis of tokens gathered...') - verbout(G, 'A total of %s tokens was discovered during the scan' % (len(REQUEST_TOKENS))) + verbout(colors.RED, "\n +--------------+") + verbout(colors.RED, " | Analysis |") + verbout(colors.RED, " +--------------+\n") + print(colors.GR + "Proceeding for post-scan analysis of tokens gathered...") + verbout( + colors.G, + "A total of %s tokens was discovered during the scan" + % (len(REQUEST_TOKENS)), + ) # The idea behind this is to generate all possible combinations (not # considering permutations) from the given list of discovered tokens # and generate anti-CSRF token generation pattern. for tokenx1, tokenx2 in itertools.combinations(REQUEST_TOKENS, 2): try: - verbout(GR, 'Analysing 2 Anti-CSRF Tokens from gathered requests...') - verbout(color.CYAN, ' [+] First Token: '+color.BLUE+tokenx1) - verbout(color.ORANGE, ' [+] Shannon Entropy: '+color.GREEN+'%s' % (calcEntropy(tokenx1))) - verbout(color.CYAN, ' [+] Second Token: '+color.BLUE+tokenx2) - verbout(color.ORANGE, ' [+] Shannon Entropy: '+color.GREEN+'%s' % (calcEntropy(tokenx2))) + verbout( + colors.GR, "Analysing 2 Anti-CSRF Tokens from gathered requests..." + ) + verbout(colors.CYAN, " [+] First Token: " + colors.BLUE + tokenx1) + verbout( + colors.ORANGE, + " [+] Shannon Entropy: " + + colors.GREEN + + "%s" % (calcEntropy(tokenx1)), + ) + verbout(colors.CYAN, " [+] Second Token: " + colors.BLUE + tokenx2) + verbout( + colors.ORANGE, + " [+] Shannon Entropy: " + + colors.GREEN + + "%s" % (calcEntropy(tokenx2)), + ) # Calculating the edit distance via Damerau Levenshtein algorithm m = stringdist.rdlevenshtein(tokenx1, tokenx2) - verbout(color.CYAN, ' [+] Edit Distance Calculated: '+color.GREY+str(m)+'%') + verbout( + colors.CYAN, + " [+] Edit Distance Calculated: " + colors.GREY + str(m) + "%", + ) # Now its time to detect the alignment ratio n = stringdist.rdlevenshtein_norm(tokenx1, tokenx2) - verbout(color.CYAN, ' [+] Alignment Ratio Calculated: '+color.GREY+str(n)) + verbout( + colors.CYAN, + " [+] Alignment Ratio Calculated: " + colors.GREY + str(n), + ) # If both tokens are same, then if len(tokenx1) == len(tokenx2): - verbout(C, 'Token length calculated is same: '+color.ORANGE+'Each %s bytes' % len(byteString(tokenx1))) + verbout( + colors.C, + "Token length calculated is same: " + + colors.ORANGE + + "Each %s bytes" % len(byteString(tokenx1)), + ) else: - verbout(C, 'Token length calculated is different: '+color.ORANGE+'By %s bytes' % (len(byteString(tokenx1)) - len(byteString(tokenx2)))) + verbout( + colors.C, + "Token length calculated is different: " + + colors.ORANGE + + "By %s bytes" + % (len(byteString(tokenx1)) - len(byteString(tokenx2))), + ) time.sleep(0.5) # In my experience with web security assessments, often the Anti-CSRF token # is composed of two parts, one of them remains static while the other one dynamic. @@ -64,40 +103,190 @@ def Analysis(): # The main idea behind this is to detect the static and dynamic part via DL Algorithm # as discussed above by calculating edit distance. p = sameSequence(tokenx1, tokenx2) - tokenx01 = tokenx1.replace(p,'') - tokenx02 = tokenx2.replace(p,'') - if n == 0.5 or m == len(tokenx1)/2: - verbout(GR, 'The tokens are composed of 2 parts (one static and other dynamic)... ') - verbout(C, 'Static Part : '+color.GREY+p+color.END+' | Length: '+color.CYAN+str(len(p))) - verbout(O, 'Dynamic Part of Token 0x1: '+color.GREY+tokenx01+color.END+' | Length: '+color.CYAN+str(len(tokenx01))) - verbout(O, 'Dynamic Part of Token 0x2: '+color.GREY+tokenx02+color.END+' | Length: '+color.CYAN+str(len(tokenx02))) - if len(len(tokenx1)/2) <= 6: - verbout(color.RED,' [-] Post-Analysis reveals that token might be '+color.BR+' VULNERABLE '+color.END+'!') - print(color.RED+ ' [+] Possible CSRF Vulnerability Detected!') - print(color.ORANGE+' [!] Vulnerability Type: '+color.BR+' Weak Dynamic Part of Tokens '+color.END) - print(color.GREY+' [+] Tokens can easily be '+color.RED+'Forged by Bruteforcing/Guessing'+color.END+'!\n') - VulnLogger('Analysis', 'Tokens can easily be Forged by Bruteforcing/Guessing.', '[i] Token 1: '+tokenx1+'\n[i] Token 2: '+tokenx2) - elif n < 0.5 or m < len(tokenx1)/2: - verbout(R, 'Token distance calculated is '+color.RED+'less than 0.5!') - verbout(C, 'Static Part : '+color.GREY+p+color.END+' | Length: '+color.CYAN+str(len(p))) - verbout(O, 'Dynamic Part of Token 0x1: '+color.GREY+tokenx01+color.END+' | Length: '+color.CYAN+str(len(tokenx01))) - verbout(O, 'Dynamic Part of Token 0x2: '+color.GREY+tokenx02+color.END+' | Length: '+color.CYAN+str(len(tokenx02))) - verbout(color.RED,' [-] Post-Analysis reveals that token might be '+color.BR+' VULNERABLE '+color.END+'!') - print(color.GREEN+ ' [+] Possible CSRF Vulnerability Detected!') - print(color.ORANGE+' [!] Vulnerability Type: '+color.BR+' Weak Dynamic Part of Tokens '+color.END) - print(color.GREY+' [+] Tokens can easily be '+color.RED+'Forged by Bruteforcing/Guessing'+color.END+'!\n') - VulnLogger('Analysis', 'Tokens can easily be Forged by Bruteforcing/Guessing.', '[i] Token 1: '+tokenx1+'\n[i] Token 2: '+tokenx2) + tokenx01 = tokenx1.replace(p, "") + tokenx02 = tokenx2.replace(p, "") + if n == 0.5 or m == len(tokenx1) / 2: + verbout( + colors.GR, + "The tokens are composed of 2 parts (one static and other dynamic)... ", + ) + verbout( + colors.C, + "Static Part : " + + colors.GREY + + p + + colors.END + + " | Length: " + + colors.CYAN + + str(len(p)), + ) + verbout( + colors.O, + "Dynamic Part of Token 0x1: " + + colors.GREY + + tokenx01 + + colors.END + + " | Length: " + + colors.CYAN + + str(len(tokenx01)), + ) + verbout( + colors.O, + "Dynamic Part of Token 0x2: " + + colors.GREY + + tokenx02 + + colors.END + + " | Length: " + + colors.CYAN + + str(len(tokenx02)), + ) + if len(len(tokenx1) / 2) <= 6: + verbout( + colors.RED, + " [-] Post-Analysis reveals that token might be " + f"{colors.BR} VULNERABLE {colors.END}", + ) + print(f"{colors.RED} [+] Possible CSRF Vulnerability Detected!") + print( + f"{colors.ORANGE} [!] Vulnerability Type: " + f"{colors.BR} Weak Dynamic Part of Tokens {colors.END}" + ) + print( + f"{colors.GREY} [+] Tokens can easily be " + f"{colors.RED}Forged by Bruteforcing/Guessing{colors.END}!" + ) + VulnLogger( + "Analysis", + "Tokens can easily be Forged by Bruteforcing/Guessing.", + "[i] Token 1: " + tokenx1 + "\n[i] Token 2: " + tokenx2, + ) + elif n < 0.5 or m < len(tokenx1) / 2: + verbout( + colors.R, + "Token distance calculated is " + colors.RED + "less than 0.5!", + ) + verbout( + colors.C, + "Static Part : " + + colors.GREY + + p + + colors.END + + " | Length: " + + colors.CYAN + + str(len(p)), + ) + verbout( + colors.O, + "Dynamic Part of Token 0x1: " + + colors.GREY + + tokenx01 + + colors.END + + " | Length: " + + colors.CYAN + + str(len(tokenx01)), + ) + verbout( + colors.O, + "Dynamic Part of Token 0x2: " + + colors.GREY + + tokenx02 + + colors.END + + " | Length: " + + colors.CYAN + + str(len(tokenx02)), + ) + verbout( + colors.RED, + " [-] Post-Analysis reveals that token might be " + + colors.BR + + " VULNERABLE " + + colors.END, + ) + print(colors.GREEN + " [+] Possible CSRF Vulnerability Detected!") + print( + colors.ORANGE + + " [!] Vulnerability Type: " + + colors.BR + + " Weak Dynamic Part of Tokens " + + colors.END + ) + print( + colors.GREY + + " [+] Tokens can easily be " + + colors.RED + + "Forged by Bruteforcing/Guessing" + + colors.END + + "!\n" + ) + VulnLogger( + "Analysis", + "Tokens can easily be Forged by Bruteforcing/Guessing.", + "[i] Token 1: " + tokenx1 + "\n[i] Token 2: " + tokenx2, + ) else: - verbout(R, 'Token distance calculated is '+color.GREEN+'greater than 0.5!') - verbout(C, 'Static Part : '+color.GREY+p+color.END+' | Length: '+color.CYAN+str(len(p))) - verbout(O, 'Dynamic Part of Token 0x1: '+color.GREY+tokenx01+color.END+' | Length: '+color.CYAN+str(len(tokenx01))) - verbout(O, 'Dynamic Part of Token 0x2: '+color.GREY+tokenx02+color.END+' | Length: '+color.CYAN+str(len(tokenx02))) - verbout(color.GREEN,' [+] Post-Analysis reveals that tokens are '+color.BG+' NOT VULNERABLE '+color.END+'!') - print(color.ORANGE+' [!] Vulnerability Mitigation: '+color.BG+' Strong Dynamic Part of Tokens '+color.END) - print(color.GREY+' [+] Tokens '+color.GREEN+'Cannot be Forged by Bruteforcing/Guessing'+color.END+'!\n') - NovulLogger('Analysis', 'Tokens cannot be Forged by Bruteforcing/Guessing.') + verbout( + colors.R, + "Token distance calculated is " + + colors.GREEN + + "greater than 0.5!", + ) + verbout( + colors.C, + "Static Part : " + + colors.GREY + + p + + colors.END + + " | Length: " + + colors.CYAN + + str(len(p)), + ) + verbout( + colors.O, + "Dynamic Part of Token 0x1: " + + colors.GREY + + tokenx01 + + colors.END + + " | Length: " + + colors.CYAN + + str(len(tokenx01)), + ) + verbout( + colors.O, + "Dynamic Part of Token 0x2: " + + colors.GREY + + tokenx02 + + colors.END + + " | Length: " + + colors.CYAN + + str(len(tokenx02)), + ) + verbout( + colors.GREEN, + " [+] Post-Analysis reveals that tokens are " + + colors.BG + + " NOT VULNERABLE " + + colors.END, + ) + print( + colors.ORANGE + + " [!] Vulnerability Mitigation: " + + colors.BG + + " Strong Dynamic Part of Tokens " + + colors.END + ) + print( + colors.GREY + + " [+] Tokens " + + colors.GREEN + + "Cannot be Forged by Bruteforcing/Guessing" + + colors.END + + "!\n" + ) + NovulLogger( + "Analysis", "Tokens cannot be Forged by Bruteforcing/Guessing." + ) time.sleep(1) except KeyboardInterrupt: - ctr+=1 + ctr += 1 continue - print(C+'Post-Scan Analysis Completed!') + print(colors.C + "Post-Scan Analysis Completed!") diff --git a/xsrfprobe/modules/Checkpost.py b/xsrfprobe/modules/Checkpost.py index 1e4ac4f..49264d2 100644 --- a/xsrfprobe/modules/Checkpost.py +++ b/xsrfprobe/modules/Checkpost.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -13,32 +13,43 @@ import time import difflib from urllib.parse import urlencode -from xsrfprobe.core.colors import * + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + from xsrfprobe.core.verbout import verbout from xsrfprobe.core.logger import VulnLogger from xsrfprobe.files.config import POC_GENERATION, GEN_MALICIOUS from xsrfprobe.modules.Generator import GenNormalPoC, GenMalicious -def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): - ''' + +def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=""): + """ This method is for detecting POST-Based Request Forgeries on basis of fuzzy string matching and comparison based on Ratcliff-Obershelp Algorithm. - ''' - verbout(color.RED, '\n +------------------------------+') - verbout(color.RED, ' | POST-Based Forgery Check |') - verbout(color.RED, ' +------------------------------+\n') - verbout(O, 'Matching response query differences...') - checkdiffx1 = difflib.ndiff(r1.splitlines(1), r2.splitlines(1)) # check the diff noted - checkdiffx2 = difflib.ndiff(r1.splitlines(1), r3.splitlines(1)) # check the diff noted + """ + verbout(colors.RED, "\n +------------------------------+") + verbout(colors.RED, " | POST-Based Forgery Check |") + verbout(colors.RED, " +------------------------------+\n") + verbout(colors.O, "Matching response query differences...") + checkdiffx1 = difflib.ndiff( + r1.splitlines(1), r2.splitlines(1) + ) # check the diff noted + checkdiffx2 = difflib.ndiff( + r1.splitlines(1), r3.splitlines(1) + ) # check the diff noted result12 = [] # an init - verbout(O, 'Matching results...') + verbout(colors.O, "Matching results...") + for n in checkdiffx1: - if re.match('\+|-', n): # get regex matching stuff only +/- + if re.match(r"\+|-", n): # get regex matching stuff only +/- result12.append(n) # append to existing list + result13 = [] # an init for n in checkdiffx2: - if re.match('\+|-', n): # get regex matching stuff + if re.match(r"\+|-", n): # get regex matching stuff result13.append(n) # append to existing list # This logic is based purely on the assumption on the difference of various requests @@ -48,31 +59,56 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): # # NOTE: The algorithm has lots of scopes of improvement... if len(result12) <= len(result13): - print(color.GREEN+ ' [+] CSRF Vulnerability Detected : '+color.ORANGE+url+'!') - print(color.ORANGE+' [!] Vulnerability Type: '+color.BR+' POST-Based Request Forgery '+color.END) - VulnLogger(url, 'POST-Based Request Forgery on Forms.', '[i] Form: '+form.__str__()+'\n[i] POST Query: '+result.__str__()+'\n') + print(f"{colors.GREEN} [+] CSRF Vulnerability Detected : {colors.ORANGE}{url}!") + print( + f"{colors.ORANGE} [!] Vulnerability Type: {colors.BR} POST-Based Request Forgery {colors.END}" + ) + VulnLogger( + url, + "POST-Based Request Forgery on Forms.", + f"[i] Form: {form}\n" f"[i] POST Query: {result}\n", + ) time.sleep(0.3) - verbout(O, 'PoC of response and request...') + verbout(colors.O, "PoC of response and request...") + if m_name: - print(color.RED+'\n +-----------------+') - print(color.RED+' | Request PoC |') - print(color.RED+' +-----------------+\n') - print(color.BLUE+' [+] URL : ' +color.CYAN+url) # url part - print(color.CYAN+' [+] Name : ' +color.ORANGE+m_name) # name - if m_action.count('/') > 1: - print(color.GREEN+' [+] Action : ' +color.END+'/'+m_action.rsplit('/', 1)[1]) # action + print(f"{colors.RED}\n +-----------------+") + print(f"{colors.RED} | Request PoC |") + print(f"{colors.RED} +-----------------+\n") + print(f"{colors.BLUE} [+] URL : {colors.CYAN}{url}") # url part + print(f"{colors.CYAN} [+] Name : {colors.ORANGE}{m_name}") # name + + if m_action.count("/") > 1: + print( + colors.GREEN + + " [+] Action : " + + colors.END + + "/" + + m_action.rsplit("/", 1)[1] + ) # action else: - print(color.GREEN+' [+] Action : ' +color.END+m_action) # action + print(colors.GREEN + " [+] Action : " + colors.END + m_action) # action else: # if value m['name'] not there :( - print(color.RED+'\n +-----------------+') - print(color.RED+' | Request PoC |') - print(color.RED+' +-----------------+\n') - print(color.BLUE+' [+] URL : ' +color.CYAN+url) # the url - if m_action.count('/') > 1: - print(color.GREEN+' [+] Action : ' +color.END+'/'+m_action.rsplit('/', 1)[1]) # action + print(f"{colors.RED} \n +-----------------+") + print(f"{colors.RED} | Request PoC |") + print(f"{colors.RED} +-----------------+\n") + print(colors.BLUE + " [+] URL : " + colors.CYAN + url) # the url + if m_action.count("/") > 1: + print( + colors.GREEN + + " [+] Action : " + + colors.END + + "/" + + m_action.rsplit("/", 1)[1] + ) # action else: - print(color.GREEN+' [+] Action : ' +color.END+m_action) # action - print(color.ORANGE+' [+] POST Query : '+color.GREY+ urlencode(result).strip()) + print(colors.GREEN + " [+] Action : " + colors.END + m_action) # action + print( + colors.ORANGE + + " [+] POST Query : " + + colors.GREY + + urlencode(result).strip() + ) # If option --skip-poc hasn't been supplied... if POC_GENERATION: # If --malicious has been supplied diff --git a/xsrfprobe/modules/Cookie.py b/xsrfprobe/modules/Cookie.py index 0cd4cb1..853b5a7 100644 --- a/xsrfprobe/modules/Cookie.py +++ b/xsrfprobe/modules/Cookie.py @@ -1,106 +1,139 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -import time, os, sys +import sys from re import search, I -from xsrfprobe.core.colors import * -from xsrfprobe.files.config import * + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +from xsrfprobe.files.config import HEADER_VALUES, USER_AGENT, COOKIE_VALUE, REFERER_URL from xsrfprobe.core.verbout import verbout from xsrfprobe.core.request import Get from xsrfprobe.core.randua import RandomAgent from xsrfprobe.modules.Persistence import Persistence from xsrfprobe.core.logger import VulnLogger, NovulLogger -from urllib.parse import urlencode, unquote, urlsplit +from urllib.parse import urlsplit resps = [] + def SameSite(url): - ''' + """ This function parses and verifies the cookies with SameSite Flags. - ''' - verbout(color.RED, '\n +------------------------------------+') - verbout(color.RED, ' | Cross Origin Cookie Validation |') - verbout(color.RED, ' +------------------------------------+\n') + """ + verbout(colors.RED, "\n +------------------------------------+") + verbout(colors.RED, " | Cross Origin Cookie Validation |") + verbout(colors.RED, " +------------------------------------+\n") # Some Flags we'd need later... foundx1 = 0x00 foundx2 = 0x00 foundx3 = 0x00 # Step 1: First we check that if the server returns any # SameSite flag on Cookies with the same Referer as the netloc - verbout(color.GREY,' [+] Lets examine how server reacts to same referer...') + verbout(colors.GREY, " [+] Lets examine how server reacts to same referer...") gen_headers = HEADER_VALUES - gen_headers['User-Agent'] = USER_AGENT if USER_AGENT else RandomAgent() - verbout(GR,'Setting Referer header same as host...') + gen_headers["User-Agent"] = USER_AGENT if USER_AGENT else RandomAgent() + verbout(colors.GR, "Setting Referer header same as host...") # Setting the netloc as the referer for the first check. - gen_headers['Referer'] = urlsplit(url).netloc + gen_headers["Referer"] = urlsplit(url).netloc if COOKIE_VALUE: - gen_headers['Cookie'] = ','.join(cookie for cookie in COOKIE_VALUE) + gen_headers["Cookie"] = ",".join(cookie for cookie in COOKIE_VALUE) getreq = Get(url, headers=gen_headers) # Making the request - map(HEADER_VALUES.pop, ['Referer', 'Cookie']) + map(HEADER_VALUES.pop, ["Referer", "Cookie"]) head = getreq.headers for h in head: - #if search('cookie', h, I) or search('set-cookie', h, I): - if 'Cookie'.lower() in h.lower(): - verbout(G,'Found cookie header value...') + # if search('cookie', h, I) or search('set-cookie', h, I): + if "Cookie".lower() in h.lower(): + verbout(colors.G, "Found cookie header value...") cookieval = head[h] - verbout(color.ORANGE,' [+] Cookie Received: '+color.CYAN+str(cookieval)) - m = cookieval.split(';') - verbout(GR,'Examining Cookie...') + verbout( + colors.ORANGE, " [+] Cookie Received: " + colors.CYAN + str(cookieval) + ) + m = cookieval.split(";") + verbout(colors.GR, "Examining Cookie...") for q in m: - if search('SameSite', q, I): - verbout(G,'SameSite Flag '+color.ORANGE+' detected on cookie!') + if search("SameSite", q, I): + verbout( + colors.G, + "SameSite Flag " + colors.ORANGE + " detected on cookie!", + ) foundx1 = 0x01 - q = q.split('=')[1].strip() - verbout(C, 'Cookie: '+color.ORANGE+q) + q = q.split("=")[1].strip() + verbout(colors.C, "Cookie: " + colors.ORANGE + q) break else: foundx3 = 0x02 if foundx1 == 0x01: - verbout(R,' [+] Endpoint '+color.ORANGE+'SameSite Flag Cookie Validation'+color.END+' Present!') + verbout( + colors.R, + " [+] Endpoint " + + colors.ORANGE + + "SameSite Flag Cookie Validation" + + colors.END + + " Present!", + ) # Step 2: Now we check security mechanisms when the Referer is # different, i.e. request originates from a different url other # than the host. (This time without the Cookie assigned) - verbout(color.GREY,' [+] Lets examine how server reacts to a fake external referer...') + verbout( + colors.GREY, " [+] Lets examine how server reacts to a fake external referer..." + ) gen_headers = HEADER_VALUES - gen_headers['User-Agent'] = USER_AGENT if USER_AGENT else RandomAgent() # Setting user-agents + gen_headers["User-Agent"] = ( + USER_AGENT if USER_AGENT else RandomAgent() + ) # Setting user-agents # Assigning a fake referer for the second check, but no cookie. - gen_headers['Referer'] = REFERER_URL - gen_headers.pop('Cookie', None) + gen_headers["Referer"] = REFERER_URL + gen_headers.pop("Cookie", None) getreq = Get(url, headers=gen_headers) - HEADER_VALUES.pop('Referer', None) + HEADER_VALUES.pop("Referer", None) head = getreq.headers # Getting headers from requests for h in head: # If search('cookie', h, I) or search('set-cookie', h, I): - if 'Cookie'.lower() in h.lower(): - verbout(G,'Found cookie header value...') + if "Cookie".lower() in h.lower(): + verbout(colors.G, "Found cookie header value...") cookieval = head[h] - verbout(color.ORANGE,' [+] Cookie Received: '+color.CYAN+str(cookieval)) - m = cookieval.split(';') - verbout(GR,'Examining Cookie...') + verbout( + colors.ORANGE, " [+] Cookie Received: " + colors.CYAN + str(cookieval) + ) + m = cookieval.split(";") + verbout(colors.GR, "Examining Cookie...") for q in m: - if search('SameSite', q, I): - verbout(G,'SameSite Flag '+color.ORANGE+' detected on cookie!') + if search("SameSite", q, I): + verbout( + colors.G, + "SameSite Flag " + colors.ORANGE + " detected on cookie!", + ) foundx2 = 0x01 - q = q.split('=')[1].strip() - verbout(C, 'Cookie: '+color.ORANGE+q) + q = q.split("=")[1].strip() + verbout(colors.C, "Cookie: " + colors.ORANGE + q) break else: foundx3 = 0x02 if foundx1 == 0x01: - verbout(R,' [+] Endpoint '+color.ORANGE+'SameSite Flag Cookie Validation'+color.END+' Present!') + verbout( + colors.R, + " [+] Endpoint " + + colors.ORANGE + + "SameSite Flag Cookie Validation" + + colors.END + + " Present!", + ) # Step 3: And finally comes the most important step. Lets see how # the site reacts to a valid cookie (ofc supplied by the user) coming @@ -108,65 +141,138 @@ def SameSite(url): # This is the most crucial part of the detection. # # TODO: Improve the logic in detection. - verbout(color.GREY,' [+] Lets examine how server reacts to valid cookie from a different referer...') + verbout( + colors.GREY, + " [+] Lets examine how server reacts to valid cookie from a different referer...", + ) gen_headers = HEADER_VALUES - gen_headers['User-Agent'] = USER_AGENT or RandomAgent() + gen_headers["User-Agent"] = USER_AGENT or RandomAgent() # Assigning a fake referer for third request, this time with cookie ;) - gen_headers['Referer'] = REFERER_URL + gen_headers["Referer"] = REFERER_URL if COOKIE_VALUE: - gen_headers['Cookie'] = ','.join(cookie for cookie in COOKIE_VALUE) + gen_headers["Cookie"] = ",".join(cookie for cookie in COOKIE_VALUE) getreq = Get(url, headers=gen_headers) - HEADER_VALUES.pop('Referer', None) + HEADER_VALUES.pop("Referer", None) head = getreq.headers for h in head: # if search('cookie', h, I) or search('set-cookie', h, I): - if 'Cookie'.lower() in h.lower(): - verbout(G,'Found cookie header value...') + if "Cookie".lower() in h.lower(): + verbout(colors.G, "Found cookie header value...") cookieval = head[h] - verbout(color.ORANGE,' [+] Cookie Received: '+color.CYAN+str(cookieval)) - m = cookieval.split(';') - verbout(GR,'Examining Cookie...') + verbout( + colors.ORANGE, " [+] Cookie Received: " + colors.CYAN + str(cookieval) + ) + m = cookieval.split(";") + verbout(colors.GR, "Examining Cookie...") for q in m: - if search('samesite', q.lower(), I): - verbout(G,'SameSite Flag '+color.ORANGE+' detected on cookie on Cross Origin Request!') + if search("samesite", q.lower(), I): + verbout( + colors.G, + "SameSite Flag " + + colors.ORANGE + + " detected on cookie on Cross Origin Request!", + ) foundx3 = 0x01 - q = q.split('=')[1].strip() - verbout(C, 'Cookie: '+color.ORANGE+q) + q = q.split("=")[1].strip() + verbout(colors.C, "Cookie: " + colors.ORANGE + q) break else: foundx3 = 0x02 if foundx1 == 0x01: - verbout(R,'Endpoint '+color.ORANGE+'SameSite Flag Cookie Validation'+color.END+' is Present!') + verbout( + colors.R, + "Endpoint " + + colors.ORANGE + + "SameSite Flag Cookie Validation" + + colors.END + + " is Present!", + ) if (foundx1 == 0x01 and foundx3 == 0x00) and (foundx2 == 0x00 or foundx2 == 0x01): - print(color.GREEN+' [+] Endpoint '+color.BG+' NOT VULNERABLE to ANY type of CSRF attacks! '+color.END) - print(color.GREEN+' [+] Protection Method Detected : '+color.BG+' SameSite Flag on Cookies '+color.END) - NovulLogger(url, 'SameSite Flag set on Cookies on Cross-Origin Requests.') + print( + colors.GREEN + + " [+] Endpoint " + + colors.BG + + " NOT VULNERABLE to ANY type of CSRF attacks! " + + colors.END + ) + print( + colors.GREEN + + " [+] Protection Method Detected : " + + colors.BG + + " SameSite Flag on Cookies " + + colors.END + ) + NovulLogger(url, "SameSite Flag set on Cookies on Cross-Origin Requests.") # If a SameSite flag is set on cookies, then the application is totally fool-proof # against CSRF attacks unless there is some XSS stuff on it. So for now the job of # this application is done. We need to confirm before we quit. - oq = input(color.BLUE+' [+] Continue scanning? (y/N) :> ') - if oq.lower().startswith('n'): - sys.exit('\n'+R+'Shutting down XSRFProbe...\n') + oq = input(colors.BLUE + " [+] Continue scanning? (y/N) :> ") + if oq.lower().startswith("n"): + sys.exit("\n" + colors.R + "Shutting down XSRFProbe...\n") elif foundx1 == 0x02 and foundx2 == 0x02 and foundx3 == 0x02: - print(color.GREEN+' [+] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to CSRF attacks!') - print(color.GREEN+' [+] Type: '+color.BG+' No Cookie Set while Cross Origin Requests '+color.END) - NovulLogger(url, 'No cookie set on Cross-Origin Requests.') + print( + colors.GREEN + + " [+] Endpoint " + + colors.BG + + " NOT VULNERABLE " + + colors.END + + colors.GREEN + + " to CSRF attacks!" + ) + print( + colors.GREEN + + " [+] Type: " + + colors.BG + + " No Cookie Set while Cross Origin Requests " + + colors.END + ) + NovulLogger(url, "No cookie set on Cross-Origin Requests.") else: - verbout(R,'Endpoint '+color.ORANGE+'Cross Origin Cookie Validation'+color.END+' Not Present!') - verbout(R,'Heuristic(s) reveal endpoint might be '+color.BY+' VULNERABLE '+color.END+' to CSRFs...') - print(color.CYAN+ ' [+] Possible CSRF Vulnerability Detected : '+color.GREY+url+'!') - print(color.ORANGE+' [!] Possible Vulnerability Type: '+color.BY+' No Cross Origin Cookie Validation Presence '+color.END) - VulnLogger(url, 'No Cookie Validation on Cross-Origin Requests.', '[i] Headers: '+str(head)) + verbout( + colors.R, + "Endpoint " + + colors.ORANGE + + "Cross Origin Cookie Validation" + + colors.END + + " Not Present!", + ) + verbout( + colors.R, + "Heuristic(s) reveal endpoint might be " + + colors.BY + + " VULNERABLE " + + colors.END + + " to CSRFs...", + ) + print( + colors.CYAN + + " [+] Possible CSRF Vulnerability Detected : " + + colors.GREY + + url + ) + print( + colors.ORANGE + + " [!] Possible Vulnerability Type: " + + colors.BY + + " No Cross Origin Cookie Validation Presence " + + colors.END + ) + VulnLogger( + url, + "No Cookie Validation on Cross-Origin Requests.", + "[i] Headers: " + str(head), + ) + def Cookie(url, request): - ''' + """ This module is for checking the varied HTTP Cookies and the related security on them to prevent CSRF attacks. - ''' - verbout(GR, 'Proceeding for cookie based checks...') + """ + verbout(colors.GR, "Proceeding for cookie based checks...") SameSite(url) - Persistence(url, request) \ No newline at end of file + Persistence(url, request) diff --git a/xsrfprobe/modules/Crawler.py b/xsrfprobe/modules/Crawler.py index 857f032..7db548f 100644 --- a/xsrfprobe/modules/Crawler.py +++ b/xsrfprobe/modules/Crawler.py @@ -1,37 +1,51 @@ #!/usr/bin/env python3 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# -#Author: 0xInfection -#This module requires XSRFProbe -#https://github.com/0xInfection/XSRFProbe +# Author: 0xInfection +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe -import re, sys +import re import urllib.error +import urllib.parse from bs4 import BeautifulSoup + from xsrfprobe.modules import Parser -from xsrfprobe.core.colors import * -from xsrfprobe.files.config import * -from xsrfprobe.files.dcodelist import * + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +from xsrfprobe.files.config import EXCLUDE_DIRS +from xsrfprobe.files.dcodelist import ( + RID_DOUBLE, + RID_COMPILE, + RID_SINGLE, + NUM_COM, + NUM_SUB, +) from xsrfprobe.core.request import Get from xsrfprobe.core.verbout import verbout from xsrfprobe.core.logger import ErrorLogger from xsrfprobe.files.discovered import INTERNAL_URLS -class Handler(): # Main Crawler Handler - ''' + +class Handler: # Main Crawler Handler + """ This is a crawler that is used to fetch all the Urls associated to the HTML page, and susequently crawl them and build checks for CSRFs. - ''' + """ + def __init__(self, start, opener): self.visited = [] # Visited stuff self.toVisit = [] # To visit self.uriPatterns = [] # Patterns to follow - self.currentURI = '' # What is it now? + self.currentURI = "" # What is it now? self.opener = opener # Init build_opener self.toVisit.append(start) # Lets add up urls @@ -63,14 +77,19 @@ def process(self, root): url = self.currentURI # Main Url (Current) try: query = Get(url) # Open it (to check if it exists) - if query != None and not str(query.status_code).startswith('40'): # Avoiding 40x errors + if query != None and not str(query.status_code).startswith( + "40" + ): # Avoiding 40x errors INTERNAL_URLS.append(url) # We append it to the list of valid urls else: if url in self.toVisit: self.toVisit.remove(url) - except (urllib.error.HTTPError, urllib.error.URLError) as msg: # Incase there isan exception connecting to Url - verbout(R, 'HTTP Request Error: '+msg.__str__()) + except ( + urllib.error.HTTPError, + urllib.error.URLError, + ) as msg: # Incase there is an exception connecting to Url + verbout(colors.R, "HTTP Request Error: " + msg.__str__()) ErrorLogger(url, msg.__str__()) if url in self.toVisit: self.toVisit.remove(url) # Remove non-existent / errored urls @@ -78,47 +97,82 @@ def process(self, root): # Making sure the content type is in HTML format, so that BeautifulSoup # can parse it... - if not query or not re.search('html', query.headers['Content-Type']): + if not query or not re.search("html", query.headers["Content-Type"]): return None # Just in case there is a redirection, we are supposed to follow it :D - verbout(GR, 'Making request to new location...') - if hasattr(query.headers, 'Location'): - url = query.headers['Location'] - verbout(O,'Reading response...') + verbout(colors.GR, "Making request to new location...") + + if hasattr(query.headers, "Location"): + url = query.headers["Location"] + + verbout(colors.O, "Reading response...") response = query.content # Read the response contents try: - verbout(O, 'Trying to parse response...') + verbout(colors.O, "Trying to parse crawler response...") soup = BeautifulSoup(response) # Parser init + verbout(colors.O, "Done parsing crawler response...") - except HTMLParser.HTMLParseError: - verbout(R, 'BeautifulSoup Error: '+url) + except Exception: + verbout(colors.R, f"BeautifulSoup Error: {url}") self.visited.append(url) if url in self.toVisit: self.toVisit.remove(url) return None - for m in soup.findAll('a', href=True): # find out all href^?://* - app = '' + verbout(colors.O, "Processing 'a' elements...") + for m in soup.findAll("a", href=True): # find out all href^?://* + app = "" # Making sure that href is not a function or doesn't begin with http:// - if not re.match(r'javascript:', m['href']) or re.match('http://', m['href']): - app = Parser.buildUrl(url, m['href']) + if not re.match(r"javascript:", m["href"]) or re.match( + r"http(s?)://", m["href"] + ): + app = Parser.buildUrl(url, m["href"]) # If we get a valid link - if app!='' and re.search(root, app): + if app != "" and re.search(root, app): # Getting rid of Urls starting with '../../../..' - while re.search(RID_DOUBLE, app): - p = re.compile(RID_COMPILE) - app = p.sub('/', app) - # Getting rid of Urls starting with './' + res = urllib.parse.urlparse(app) + path = res.path + + if "../" in path: # lets remove it + # Remove starting ""/../" + while path.startswith("/../"): + path = path[len("/..") :] + + endless_loop = 0 + while re.search(pattern=RID_DOUBLE, string=path): + endless_loop += 1 + + # Prevent an endless loop here + if endless_loop > 100: + verbout( + colors.R, + f"URL is causing an endless loop: {app}, " + "resetting path to: /", + ) + path = "/" + break + + p = re.compile(RID_COMPILE) + path = p.sub(repl="/", string=path) + + # Getting rid of URLs starting with './' p = re.compile(RID_SINGLE) - app = p.sub('', app) + path = p.sub("", path) + + app = f"{res.scheme}://{res.hostname}" + if res.port: + app += f":{res.port}" + app += path # Add new link to the queue only if its pattern has not been added yet - uriPattern=removeIDs(app) # remove IDs + uriPattern = removeIDs(app) # remove IDs if self.notExist(uriPattern) and app != url: - verbout(G, 'Added :> ' +color.BLUE+ app) # display what we have got! + verbout( + colors.G, f"Added :> {colors.BLUE}{app}" + ) # display what we have got! self.toVisit.append(app) # add up urls to visit self.uriPatterns.append(uriPattern) @@ -129,7 +183,7 @@ def getUriPatterns(self): # get uri patterns return self.uriPatterns def notExist(self, test): # 404 stuffs - if (test not in self.uriPatterns): # if non-existent + if test not in self.uriPatterns: # if non-existent return 1 return 0 # else existent @@ -139,13 +193,14 @@ def addUriPatterns(self, Parser): # append patterns to follow def addVisited(self, Parser): # visited stuffs added self.visited.append(Parser) + def removeIDs(Parser): - ''' + """ This function removes the Numbers from the Urls which are built. - ''' + """ p = re.compile(NUM_SUB) - Parser = p.sub('=', Parser) + Parser = p.sub("=", Parser) p = re.compile(NUM_COM) - Parser = p.sub('\\1', Parser) + Parser = p.sub("\\1", Parser) return Parser diff --git a/xsrfprobe/modules/Debugger.py b/xsrfprobe/modules/Debugger.py index 220d4cd..190292f 100644 --- a/xsrfprobe/modules/Debugger.py +++ b/xsrfprobe/modules/Debugger.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -11,141 +11,209 @@ import re import string -from xsrfprobe.core.colors import * from random import Random -from xsrfprobe.files.config import * + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +from xsrfprobe.files.config import EMAIL_VALUE, TEXT_VALUE, TOKEN_GENERATION_LENGTH from xsrfprobe.core.verbout import verbout -class Form_Debugger(): +class Form_Debugger: def prepareFormInputs(self, form): - ''' + """ This method parses form types and generates strings based on their input types. - ''' - verbout(O,'Crafting inputs as form type...') + """ + verbout(colors.O, "Crafting inputs as form type...") cr_input = {} totcr = [] - verbout(GR, 'Processing '+color.BOLD+'= max_length: - print(color.ORANGE+' [+] CSRF Token Length greater than '+color.CYAN+'256 bytes. '+color.GREEN+'Token value cannot be guessed/bruteforced...') - print(color.GREEN+' [+] Endpoint likely '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to CSRF Attacks...') - print(color.GREEN+' [!] CSRF Mitigation Method: '+color.BG+' Long Anti-CSRF Tokens '+color.END) - NovulLogger(url, 'Long Anti-CSRF tokens with Good Strength.') + print( + colors.ORANGE + + " [+] CSRF Token Length greater than " + + colors.CYAN + + "256 bytes. " + + colors.GREEN + + "Token value cannot be guessed/bruteforced..." + ) + print( + colors.GREEN + + " [+] Endpoint likely " + + colors.BG + + " NOT VULNERABLE " + + colors.END + + colors.GREEN + + " to CSRF Attacks..." + ) + print( + colors.GREEN + + " [!] CSRF Mitigation Method: " + + colors.BG + + " Long Anti-CSRF Tokens " + + colors.END + ) + NovulLogger(url, "Long Anti-CSRF tokens with Good Strength.") found = 0x01 + # Checking entropy - verbout(O, 'Proceeding to calculate '+color.GREY+'Shannon Entropy'+color.END+' of Token audited...') + verbout( + colors.O, + "Proceeding to calculate " + + colors.GREY + + "Shannon Entropy" + + colors.END + + " of Token audited...", + ) + entropy = calcEntropy(value) - verbout(GR, 'Calculating Entropy...') - verbout(color.BLUE, ' [+] Entropy Calculated: '+color.CYAN+str(entropy)) + verbout(colors.GR, "Calculating Entropy...") + verbout(colors.BLUE, " [+] Entropy Calculated: " + colors.CYAN + str(entropy)) + if entropy >= min_entropy: - verbout(color.ORANGE,' [+] Anti-CSRF Token Entropy Calculated is '+color.BY+' GREATER than 3.0 '+color.END+'... ') - print(color.ORANGE+' [+] Endpoint '+color.BY+' PROBABLY NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') - print(color.ORANGE+' [!] CSRF Mitigation Method: '+color.BY+' High Entropy Anti-CSRF Tokens '+color.END) - NovulLogger(url, 'High Entropy Anti-CSRF Tokens.') + verbout( + colors.ORANGE, + " [+] Anti-CSRF Token Entropy Calculated is " + + colors.BY + + " GREATER than 3.0 " + + colors.END + + "... ", + ) + print( + colors.ORANGE + + " [+] Endpoint " + + colors.BY + + " PROBABLY NOT VULNERABLE " + + colors.END + + colors.ORANGE + + " to CSRF Attacks..." + ) + print( + colors.ORANGE + + " [!] CSRF Mitigation Method: " + + colors.BY + + " High Entropy Anti-CSRF Tokens " + + colors.END + ) + NovulLogger(url, "High Entropy Anti-CSRF Tokens.") found = 0x01 else: - verbout(color.RED,' [-] Anti-CSRF Token Entropy Calculated is '+color.BY+' LESS than 3.0 '+color.END+'... ') - print(color.RED+' [-] Endpoint likely '+color.BR+' VULNERABLE '+color.END+color.RED+' to CSRF Attacks inspite of CSRF Tokens...') - print(color.RED+' [!] Vulnerability Type: '+color.BR+' Low Entropy Anti-CSRF Tokens '+color.END) - VulnLogger(url, 'Low Entropy Anti-CSRF Tokens.', 'Token: '+value) + verbout( + colors.RED, + " [-] Anti-CSRF Token Entropy Calculated is " + + colors.BY + + " LESS than 3.0 " + + colors.END + + "... ", + ) + print( + colors.RED + + " [-] Endpoint likely " + + colors.BR + + " VULNERABLE " + + colors.END + + colors.RED + + " to CSRF Attacks inspite of CSRF Tokens..." + ) + print( + colors.RED + + " [!] Vulnerability Type: " + + colors.BR + + " Low Entropy Anti-CSRF Tokens " + + colors.END + ) + VulnLogger(url, "Low Entropy Anti-CSRF Tokens.", "Token: " + value) + if found == 0x00: if m_name: - print(color.RED+'\n +---------+') - print(color.RED+' | PoC |') - print(color.RED+' +---------+\n') - print(color.BLUE+' [+] URL : ' +color.CYAN+url) - print(color.CYAN+' [+] Name : ' +color.ORANGE+m_name) - print(color.GREEN+' [+] Action : ' +color.ORANGE+m_action) + print(colors.RED + "\n +---------+") + print(colors.RED + " | PoC |") + print(colors.RED + " +---------+\n") + print(colors.BLUE + " [+] URL : " + colors.CYAN + url) + print(colors.CYAN + " [+] Name : " + colors.ORANGE + m_name) + print(colors.GREEN + " [+] Action : " + colors.ORANGE + m_action) else: # if value m_name not there :( - print(color.RED+'\n +---------+') - print(color.RED+' | PoC |') - print(color.RED+' +---------+\n') - print(color.BLUE+' [+] URL : ' +color.CYAN+url) - print(color.GREEN+' [+] Action : ' +color.ORANGE+m_action) + print(colors.RED + "\n +---------+") + print(colors.RED + " | PoC |") + print(colors.RED + " +---------+\n") + print(colors.BLUE + " [+] URL : " + colors.CYAN + url) + print(colors.GREEN + " [+] Action : " + colors.ORANGE + m_action) # Print out the params - print(color.ORANGE+' [+] Query : '+color.GREY+urllib.parse.urlencode(req)) - print('') + print( + colors.ORANGE + " [+] Query : " + colors.GREY + urllib.parse.urlencode(req) + ) + print("") + return (_q, para) # Return the query paramter and anti-csrf token + def calcEntropy(data): """ This function is used to calculate @@ -119,8 +219,8 @@ def calcEntropy(data): entropy = 0 # init for x in range(256): - p_x = float(data.count(chr(x)))/len(data) + p_x = float(data.count(chr(x))) / len(data) if p_x > 0: - entropy += - p_x * log(p_x, 2) + entropy += -p_x * log(p_x, 2) return entropy diff --git a/xsrfprobe/modules/Generator.py b/xsrfprobe/modules/Generator.py index b70bf64..ce89aea 100644 --- a/xsrfprobe/modules/Generator.py +++ b/xsrfprobe/modules/Generator.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe @@ -11,8 +11,12 @@ from ast import literal_eval from bs4 import BeautifulSoup -from yattag import Doc, indent -from xsrfprobe.core.colors import * +from yattag import Doc + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + from xsrfprobe.core.verbout import verbout from xsrfprobe.files.config import OUTPUT_DIR from xsrfprobe.core.prettify import formPrettify @@ -20,84 +24,129 @@ doc, tag, text = Doc().tagtext() -def GenNormalPoC(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): + +def GenNormalPoC( + action, fields, method="POST", encoding_type="application/x-www-form-urlencoded" +): """ Generate a normal CSRF PoC using basic form data """ - print(GR+'Generating normal PoC Form...' ) - verbout(color.RED, '\n +---------------------+') - verbout(color.RED, ' | Normal Form PoC |') - verbout(color.RED, ' +---------------------+\n'+color.CYAN) + print(colors.GR + "Generating normal PoC Form...") + verbout(colors.RED, "\n +---------------------+") + verbout(colors.RED, " | Normal Form PoC |") + verbout(colors.RED, " +---------------------+\n" + colors.CYAN) # Main starting which we will use to generate form. - with tag('html'): - with tag('title'): - text('CSRF PoC') - with tag('body'): - with tag('h2'): - text('Your CSRF PoC') + with tag("html"): + with tag("title"): + text("CSRF PoC") + with tag("body"): + with tag("h2"): + text("Your CSRF PoC") # Try not messing with this part. (1) - with tag('form', id='xsrfprobe_csrfpoc', action=action, enctype=encoding_type, method="POST"): + with tag( + "form", + id="xsrfprobe_csrfpoc", + action=action, + enctype=encoding_type, + method="POST", + ): for field in literal_eval(fields): - with tag('label'): - text(field['label'].title()) - doc.input(name=field['name'], type=field['type'], value=field['value']) + with tag("label"): + text(field["label"].title()) + doc.input( + name=field["name"], type=field["type"], value=field["value"] + ) # Adding the Submit Button - doc.stag('input', value='Submit', type='submit') - doc.stag('br') + doc.stag("input", value="Submit", type="submit") + doc.stag("br") # Brand tag :p ...I guess... - with tag('small'): - text('(o) This form was generated by ') - with tag('a', href='https://github.com/0xInfection/xsrfprobe'): - text('XSRFProbe') - text('.') - content = BeautifulSoup(doc.getvalue(), 'html.parser') + with tag("small"): + text("(o) This form was generated by ") + with tag("a", href="https://github.com/0xInfection/xsrfprobe"): + text("XSRFProbe") + text(".") + content = BeautifulSoup(doc.getvalue(), "html.parser") formPrettify(indentPrettify(content)) - print('') + print("") # Write out the file af... - if '//' in action: - splitterfunc = action.split('//', 1)[1].replace('/','-') - else: splitterfunc = action.replace('/', '-') - fi = open(OUTPUT_DIR+splitterfunc+'-csrf-poc.html', 'w+', encoding='utf-8') + if "//" in action: + splitterfunc = action.split("//", 1)[1].replace("/", "-") + else: + splitterfunc = action.replace("/", "-") + fi = open(OUTPUT_DIR + splitterfunc + "-csrf-poc.html", "w+", encoding="utf-8") fi.write(content.prettify()) fi.close() - print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+splitterfunc+'-csrf-poc.html') + print( + colors.G + + "PoC successfully saved under " + + colors.ORANGE + + OUTPUT_DIR + + splitterfunc + + "-csrf-poc.html" + ) + -def GenMalicious(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): +def GenMalicious( + action, fields, method="POST", encoding_type="application/x-www-form-urlencoded" +): """ - Generate a malicious CSRF PoC using basic form data + Generate a malicious CSRF PoC using basic form data """ - print(GR, 'Generating malicious PoC Form...' ) - verbout(color.RED, '\n +------------------------+') - verbout(color.RED, ' | Malicious Form PoC |') - verbout(color.RED, ' +------------------------+\n'+color.CYAN) + print(colors.GR, "Generating malicious PoC Form...") + verbout(colors.RED, "\n +------------------------+") + verbout(colors.RED, " | Malicious Form PoC |") + verbout(colors.RED, " +------------------------+\n" + colors.CYAN) # Main starting which we will use to generate form. - with tag('html'): - with tag('title'): - text('CSRF PoC') - with tag('body'): - with tag('script'): + with tag("html"): + with tag("title"): + text("CSRF PoC") + with tag("body"): + with tag("script"): doc.asis('alert("You have been pwned!!!")') # Try not messing with this part. (1) - with tag('form', id='xsrfprobe_csrfpoc', action=action, enctype=encoding_type, method="POST"): + with tag( + "form", + id="xsrfprobe_csrfpoc", + action=action, + enctype=encoding_type, + method="POST", + ): for field in literal_eval(fields): - if not field['value']: - val = input(C+'Enter value for form field '+color.GREEN+field['name'].title()+' :> '+color.CYAN) - doc.input(name=field['name'], type='hidden', value=val) + if not field["value"]: + val = input( + colors.C + + "Enter value for form field " + + colors.GREEN + + field["name"].title() + + " :> " + + colors.CYAN + ) + doc.input(name=field["name"], type="hidden", value=val) # The idea behind this is to generate PoC forms not requiring any # user interaction. As soon as the page loads, the form with submit automatically. - with tag('script'): + with tag("script"): # Try not messing with this part. (2) doc.asis('document.getElementById("xsrfprobe_csrfpoc").submit();') # Brand tag :p ...I guess... - doc.asis('') - content = BeautifulSoup(doc.getvalue(), 'html.parser') + doc.asis( + "" + ) + content = BeautifulSoup(doc.getvalue(), "html.parser") formPrettify(indentPrettify(content)) - print('') + print("") # Write out the file af... - if '//' in action: - splitterfunc = action.split('//', 1)[1].replace('/','-') - else: splitterfunc = action.replace('/', '-') - fi = open(OUTPUT_DIR+splitterfunc+'-csrf-poc.html', 'w+', encoding='utf-8') + if "//" in action: + splitterfunc = action.split("//", 1)[1].replace("/", "-") + else: + splitterfunc = action.replace("/", "-") + fi = open(OUTPUT_DIR + splitterfunc + "-csrf-poc.html", "w+", encoding="utf-8") fi.write(content.prettify()) fi.close() - print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+splitterfunc+'-malicious-poc.html') + print( + colors.G + + "PoC successfully saved under " + + colors.ORANGE + + OUTPUT_DIR + + splitterfunc + + "-malicious-poc.html" + ) diff --git a/xsrfprobe/modules/Origin.py b/xsrfprobe/modules/Origin.py index 794a57f..0b6736a 100644 --- a/xsrfprobe/modules/Origin.py +++ b/xsrfprobe/modules/Origin.py @@ -1,48 +1,54 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -import requests -from xsrfprobe.core.colors import * -from xsrfprobe.files.config import * +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +from xsrfprobe.files.config import HEADER_VALUES, ORIGIN_URL, COOKIE_VALUE from xsrfprobe.core.verbout import verbout from xsrfprobe.core.request import Get -from xsrfprobe.core.randua import RandomAgent from xsrfprobe.core.logger import VulnLogger, NovulLogger + def Origin(url): """ Check if the remote web application verifies the Origin before processing the HTTP request. """ - verbout(color.RED, '\n +-------------------------------------+') - verbout(color.RED, ' | Origin Based Request Validation |') - verbout(color.RED, ' +-------------------------------------+\n') + global HEADER_VALUES + + verbout(colors.RED, "\n +-------------------------------------+") + verbout(colors.RED, " | Origin Based Request Validation |") + verbout(colors.RED, " +-------------------------------------+\n") # Make the request normally and get content - verbout(O,'Making request on normal basis...') + verbout(colors.O, "Making request on normal basis...") req0x01 = Get(url) - global HEADER_VALUES # Set a fake Origin along with UA (pretending to be a # legitimate request from a browser) - verbout(GR,'Setting generic headers...') + verbout(colors.GR, "Setting generic headers...") gen_headers = HEADER_VALUES - gen_headers['Origin'] = ORIGIN_URL + gen_headers["Origin"] = ORIGIN_URL # We put the cookie in request, if cookie supplied :D if COOKIE_VALUE: - gen_headers['Cookie'] = ','.join(cookie for cookie in COOKIE_VALUE) + gen_headers["Cookie"] = ",".join(cookie for cookie in COOKIE_VALUE) # Make the request with different Origin header and get the content - verbout(O,'Making request with '+color.CYAN+'Tampered Origin Header'+color.END+'...') + verbout( + colors.O, + f"Making request with {colors.CYAN}Tampered Origin Header{colors.END}...", + ) req0x02 = Get(url, headers=gen_headers) - HEADER_VALUES.pop('Origin', None) + HEADER_VALUES.pop("Origin", None) # Comparing the length of the requests' responses. If both content # lengths are same, then the site actually does not validate Origin @@ -57,16 +63,49 @@ def Origin(url): # domain. # # TODO: This algorithm has lots of room for improvement + if req0x01 is None or req0x02 is None: + verbout( + colors.RED, + " [!] Cannot compare the two requests as at least one of them is None", + ) + return False + if len(req0x01.content) != len(req0x02.content): - verbout(color.GREEN,' [+] Endoint '+color.ORANGE+'Origin Validation'+color.GREEN+' Present!') - print(color.GREEN+' [-] Heuristics reveal endpoint might be '+color.BG+' NOT VULNERABLE '+color.END+'...') - print(color.ORANGE+' [+] Mitigation Method: '+color.BG+' Origin Based Request Validation '+color.END+'\n') - NovulLogger(url, 'Presence of Origin Header based request Validation.') + verbout( + colors.GREEN, + f" [+] Endoint {colors.ORANGE}Origin Validation{colors.GREEN} Present!", + ) + print( + f"{colors.GREEN} [-] Heuristics reveal endpoint might be " + f"{colors.BG} NOT VULNERABLE {colors.END}..." + ) + print( + f"{colors.ORANGE} [+] Mitigation Method: " + f"{colors.BG} Origin Based Request Validation {colors.END}" + ) + NovulLogger(url, "Presence of Origin Header based request Validation.") return True - else: - verbout(R,'Endpoint '+color.RED+'Origin Validation Not Present'+color.END+'!') - verbout(R,'Heuristics reveal endpoint might be '+color.BY+' VULNERABLE '+color.END+' to Origin Based CSRFs...') - print(color.CYAN+ ' [+] Possible CSRF Vulnerability Detected : '+color.GREY+url+'!') - print(color.ORANGE+' [!] Possible Vulnerability Type: '+color.BY+' No Origin Based Request Validation '+color.END+'\n') - VulnLogger(url, 'No Origin Header based request validation presence.', '[i] Response Headers: '+str(req0x02.headers)) - return False + + verbout( + colors.R, + "Endpoint " + colors.RED + "Origin Validation Not Present" + colors.END, + ) + verbout( + colors.R, + "Heuristics reveal endpoint might be " + f"{colors.BY} VULNERABLE {colors.END} to Origin Based CSRFs...", + ) + print( + f"{colors.CYAN} [+] Possible CSRF Vulnerability Detected : " + f"{colors.GREY}{url}" + ) + print( + f"{colors.ORANGE} [!] Possible Vulnerability Type: {colors.BY}" + f" No Origin Based Request Validation {colors.END}" + ) + VulnLogger( + url, + "No Origin Header based request validation presence.", + "[i] Response Headers: " + str(req0x02.headers), + ) + return False diff --git a/xsrfprobe/modules/Parser.py b/xsrfprobe/modules/Parser.py index 817b0d2..666e4f5 100644 --- a/xsrfprobe/modules/Parser.py +++ b/xsrfprobe/modules/Parser.py @@ -1,25 +1,29 @@ #!/usr/bin/env python3 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# -#Author: 0xInfection -#This module requires XSRFProbe -#https://github.com/0xInfection/XSRFProbe +# Author: 0xInfection +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe import re from urllib.parse import urlsplit from xsrfprobe.core.verbout import verbout from xsrfprobe.files.dcodelist import PROTOCOLS from xsrfprobe.files.paramlist import EXCLUSIONS_LIST -from xsrfprobe.core.colors import * # import ends + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + def buildUrl(url, href): # receive form input type / url - ''' + """ This function is for building a proper URL based on comparison to 'href'. - ''' + """ # Making an exclusion list, so as to stop detection of Self-CSRF (Logout-CSRF) # # This is yet another step is reducing false positives [[ significantly ]]. @@ -27,39 +31,57 @@ def buildUrl(url, href): # receive form input type / url # of as low quality CSRF (bugs). # # TODO: Add more to EXCLUSIONS_LIST. - if href == "http://localhost" or any((re.search(s, href, re.IGNORECASE)) for s in EXCLUSIONS_LIST): + if href == "http://localhost" or any( + (re.search(s, href, re.IGNORECASE)) for s in EXCLUSIONS_LIST + ): return None url_parts = urlsplit(url) # SplitResult(scheme, netloc, path, query, fragment) + port_part = "" + if url_parts.port is not None: + port_part = f":{url_parts.port}" + href_parts = urlsplit(href) - app = '' # Init to the Url that will be built + app = "" # Init to the Url that will be built # If Url and Destination have the same domain... if href_parts.netloc == url_parts.netloc: app = href # Assigning the main netloc + return app + + # if netloc of href_parts is empty, but we have a path or query + # build it from scratch + if href_parts.netloc == "" and (href_parts.path != "" or href_parts.query != ""): + domain = url_parts.hostname # Assigning the main domain + if href_parts.path.startswith("/"): + # If the href starts with a '/', it is an internal Url + app = f"{url_parts.scheme}://{domain}{port_part}" + + app += f"{href_parts.path}" # Startpage + else: + try: + app = f"{url_parts.scheme}://{domain}{port_part}" + + app += re.findall(PROTOCOLS, url_parts.path)[0] + href_parts.path + # Get real protocol urls + except IndexError: + app = f"{url_parts.scheme}://{domain}{port_part}{href_parts.path}" + + if href_parts.query: # Checking if any queries were there... + app += "?" + href_parts.query # Adding the query parameters to Url - else: # If the destination Url doesn't have a domain - if len(href_parts.netloc) == 0 and (len(href_parts.path) != 0 or len(href_parts.query) != 0): - domain = url_parts.netloc # Assigning the main domain - if href_parts.path.startswith('/'): # If the href starts with a '/', it is an internal Url - app = url_parts.scheme + '://' + domain + href_parts.path # Startpage - else: - try: - app = 'http://' + domain + re.findall(PROTOCOLS, url_parts.path)[0] + href_parts.path - # Get real protocol urls - except IndexError: - app = 'http://' + domain + href_parts.path - if href_parts.query: # Checking if any queries were there... - app += '?' + href_parts.query # Adding the query paramaters to Url # Return '' for invalid url, url otherwise return app + def buildAction(url, action): - ''' + """ The main function of this is to create an action Url based on Current Location and Destination. - ''' - verbout(O,'Parsing URL parameters...') - if action and not action.startswith('#'): # make sure it is not a fragment (eg. http://site.tld/index.php#search) + """ + verbout(colors.O, "Parsing URL parameters...") + if action and not action.startswith( + "#" + ): # make sure it is not a fragment (eg. http://site.tld/index.php#search) return buildUrl(url, action) # get the url and reutrn it! return url # return the url itself if buildAction didn't identify the action diff --git a/xsrfprobe/modules/Persistence.py b/xsrfprobe/modules/Persistence.py index 8cf168b..3319eda 100644 --- a/xsrfprobe/modules/Persistence.py +++ b/xsrfprobe/modules/Persistence.py @@ -1,38 +1,45 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -import time, os, sys -from re import search, I +import time from datetime import datetime -from xsrfprobe.core.colors import * -from xsrfprobe.files.config import * + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +from xsrfprobe.files.config import HEADER_VALUES, COOKIE_VALUE from xsrfprobe.core.verbout import verbout from xsrfprobe.core.request import Get -from xsrfprobe.core.randua import RandomAgent from xsrfprobe.core.utils import checkDuplicates from xsrfprobe.core.logger import VulnLogger, NovulLogger -from urllib.parse import urlencode, unquote, urlsplit + # Response storing list init resps = [] + def Persistence(url, postq): - ''' + """ The main idea behind this is to check for Cookie Persistence. - ''' - verbout(color.RED, '\n +-----------------------------------+') - verbout(color.RED, ' | Cookie Persistence Validation |') - verbout(color.RED, ' +-----------------------------------+\n') + """ + verbout(colors.RED, "\n +-----------------------------------+") + verbout(colors.RED, " | Cookie Persistence Validation |") + verbout(colors.RED, " +-----------------------------------+\n") # Checking if user has supplied a value. - verbout(GR,'Proceeding to test for '+color.GREY+'Cookie Persistence'+color.END+'...') + verbout( + colors.GR, + f"Proceeding to test for {colors.GREY}Cookie Persistence{colors.END}...", + ) + time.sleep(0.7) found = 0x00 # Now let the real test begin... @@ -42,26 +49,48 @@ def Persistence(url, postq): # time when the cookie expires. Lets do it! # # First its time for GET type requests. Lets prepare our request. - cookies = [] - verbout(C, 'Proceeding to test cookie persistence via '+color.CYAN+'Prepared GET Requests'+color.END+'...') - verbout(GR,'Making the request...') + # cookies = [] + verbout( + colors.C, + f"Proceeding to test cookie persistence via {colors.CYAN}Prepared GET Requests{colors.END}...", + ) + verbout(colors.GR, "Making the request...") req = Get(url, headers=HEADER_VALUES) + if req.cookies: for cook in req.cookies: if cook.expires: - print(color.GREEN+' [+] Persistent Cookies found in Response Headers!') - print(color.GREY+' [+] Cookie: '+color.CYAN+cook.__str__()) - # cookie.expires returns a timestamp value. I didn't know it. :( Spent over 2+ hours scratching my head - # over this, until I stumbled upon a stackoverflow answer comment. So to decode this, we'd need to + print( + f"{colors.GREEN} [+] Persistent Cookies found in Response Headers!" + ) + print(f"{colors.GREY} [+] Cookie: {colors.CYAN}{cook}") + # cookie.expires returns a timestamp value. I didn't know it. + # :( Spent over 2+ hours scratching my head + # over this, until I stumbled upon a stackoverflow answer comment. + # So to decode this, we'd need to # convert it a human readable format. - print(color.GREEN+' [+] Cookie Expiry Period: '+color.ORANGE+datetime.fromtimestamp(cook.expires).__str__()) + print( + f"{colors.GREEN} [+] Cookie Expiry Period: " + f"{colors.ORANGE}{datetime.fromtimestamp(cook.expires)}" + ) found = 0x01 - VulnLogger(url, 'Persistent Session Cookies Found.', '[i] Cookie: '+req.headers.get('Set-Cookie')) + VulnLogger( + url, + "Persistent Session Cookies Found.", + "[i] Cookie: " + req.headers.get("Set-Cookie"), + ) else: - NovulLogger(url, 'No Persistent Session Cookies.') + NovulLogger(url, "No Persistent Session Cookies.") + if found == 0x00: - verbout(R, 'No persistent session cookies identified on GET Type Requests!') - verbout(C, 'Proceeding to test cookie persistence on '+color.CYAN+'POST Requests'+color.END+'...') + verbout( + colors.R, "No persistent session cookies identified on GET Type Requests!" + ) + + verbout( + colors.C, + f"Proceeding to test cookie persistence on {colors.CYAN}POST Requests{colors.END}...", + ) # Now its time for POST Based requests. # # NOTE: As a standard method, every web application should supply a cookie upon a POST query. @@ -69,19 +98,42 @@ def Persistence(url, postq): if postq.cookies: for cookie in postq.cookies: if cookie.expires: - print(color.GREEN+' [+] Persistent Cookies found in Response Headers!') - print(color.GREY+' [+] Cookie: '+color.CYAN+cookie.__str__()) + print( + f"{colors.GREEN} [+] Persistent Cookies found in Response Headers!" + ) + print(f"{colors.GREY} [+] Cookie: {colors.CYAN}{cookie}") # So to decode this, we'd need to convert it a human readable format. - print(color.GREEN+' [+] Cookie Expiry Period: '+color.ORANGE+datetime.fromtimestamp(cookie.expires).__str__()) + print( + f"{colors.GREEN} [+] Cookie Expiry Period: {colors.ORANGE}" + f"{datetime.fromtimestamp(cookie.expires)}" + ) found = 0x01 - VulnLogger(url, 'Persistent Session Cookies Found.', '[i] Cookie: '+req.headers.get('Set-Cookie')) - print(color.ORANGE+' [!] Probable Insecure Practice: '+color.BY+' Persistent Session Cookies '+color.END) + VulnLogger( + url, + "Persistent Session Cookies Found.", + "[i] Cookie: " + req.headers.get("Set-Cookie"), + ) + print( + colors.ORANGE + + " [!] Probable Insecure Practice: " + + colors.BY + + " Persistent Session Cookies " + + colors.END + ) else: - NovulLogger(url, 'No Persistent Cookies.') + NovulLogger(url, "No Persistent Cookies (via POST).") + if found == 0x00: - verbout(R, 'No persistent session cookies identified upon POST Requests!') - print(color.ORANGE+' [+] Endpoint might be '+color.BY+' NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF attacks!') - print(color.ORANGE+' [+] Detected : '+color.BY+' No Persistent Cookies '+color.END) + verbout( + colors.R, "No persistent session cookies identified upon POST Requests!" + ) + print( + f"{colors.ORANGE} [+] Endpoint might be {colors.BY} NOT VULNERABLE " + f"{colors.END}{colors.ORANGE} to CSRF attacks!" + ) + print( + f"{colors.ORANGE} [+] Detected : {colors.BY} No Persistent Cookies {colors.END}" + ) # [Step 2]: The idea here is to try to identify cookie persistence on basis of observing # variations in cases of using different user-agents. For this test we have chosen 5 different @@ -90,40 +142,90 @@ def Persistence(url, postq): # # We'll test this method only when we haven't identified requests based on previous algo. if found != 0x01: - verbout(C, 'Proceeding to test cookie persistence via '+color.CYAN+'User-Agent Alteration'+color.END+'...') + verbout( + colors.C, + "Proceeding to test cookie persistence via " + f"{colors.CYAN}User-Agent Alteration{colors.END}...", + ) + user_agents = { - 'Chrome on Windows 8.1' : 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36', - 'Safari on iOS' : 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4', - 'IE6 on Windows XP' : 'Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)', - 'Opera on Windows 10' : 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 OPR/43.0.2442.991', - 'Chrome on Android' : 'Mozilla/5.0 (Linux; U; Android 2.3.1; en-us; MID Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1' - } - verbout(GR,'Setting custom generic headers...') + "Chrome on Windows 8.1": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36", + "Safari on iOS": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4", + "IE6 on Windows XP": "Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", + "Opera on Windows 10": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 OPR/43.0.2442.991", + "Chrome on Android": "Mozilla/5.0 (Linux; U; Android 2.3.1; en-us; MID Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", + } + + verbout(colors.GR, "Setting custom generic headers...") gen_headers = HEADER_VALUES + for name, agent in user_agents.items(): - verbout(C, 'Using User-Agent : '+color.CYAN+name) - verbout(GR, 'Value : '+color.ORANGE+agent) - gen_headers['User-Agent'] = agent + verbout(colors.C, f"Using User-Agent : {colors.CYAN}{name}") + verbout(colors.GR, f"Value : {colors.ORANGE}{agent}") + gen_headers["User-Agent"] = agent + if COOKIE_VALUE: - gen_headers['Cookie'] = ','.join(cookie for cookie in COOKIE_VALUE) + gen_headers["Cookie"] = ",".join(cookie for cookie in COOKIE_VALUE) + req = Get(url, headers=gen_headers) + # We will append this to stuff only when set-cookie is being supplied. - if req.headers.get('Set-Cookie'): - resps.append(req.headers.get('Set-Cookie')) - HEADER_VALUES.pop('User-Agent', None) - HEADER_VALUES['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36' + if req.headers.get("Set-Cookie"): + resps.append(req.headers.get("Set-Cookie")) + + HEADER_VALUES.pop("User-Agent", None) + HEADER_VALUES[ + "User-Agent" + ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36" + if resps: if checkDuplicates(resps): - verbout(G, 'Set-Cookie header does not change with varied User-Agents...') - verbout(color.ORANGE, ' [+] Possible persistent session cookies found...') - print(color.RED+' [+] Possible CSRF Vulnerability Detected : '+color.ORANGE+url+'!') - print(color.ORANGE+' [!] Probable Insecure Practice: '+color.BY+' Persistent Session Cookies '+color.END) - VulnLogger(url, 'Persistent Session Cookies Found.', '[i] Cookie: '+req.headers.get('Set-Cookie')) + verbout( + colors.G, + "Set-Cookie header does not change with different User-Agents...", + ) + verbout( + colors.ORANGE, " [+] Possible persistent session cookies found..." + ) + print( + colors.RED + + " [+] Possible CSRF Vulnerability Detected : " + + colors.ORANGE + + url + ) + print( + colors.ORANGE + + " [!] Probable Insecure Practice: " + + colors.BY + + " Persistent Session Cookies " + + colors.END + ) + VulnLogger( + url, + "Persistent Session Cookies Found.", + "[i] Cookie: " + req.headers.get("Set-Cookie"), + ) else: - verbout(G, 'Set-Cookie header changes with varied User-Agents...') - verbout(R, 'No possible persistent session cookies found...') - verbout(color.ORANGE, ' [+] Endpoint '+color.BY+' PROBABLY NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF attacks!') - verbout(color.ORANGE, ' [+] Application Practice Method Detected : '+color.BY+' No Persistent Cookies '+color.END) - NovulLogger(url, 'No Persistent Cookies.') + verbout( + colors.G, "Set-Cookie header changes with different User-Agents..." + ) + verbout(colors.R, "No possible persistent session cookies found...") + verbout( + colors.ORANGE, + " [+] Endpoint " + + colors.BY + + " PROBABLY NOT VULNERABLE " + + colors.END + + colors.ORANGE + + " to CSRF attacks!", + ) + verbout( + colors.ORANGE, + " [+] Application Practice Method Detected : " + + colors.BY + + " No Persistent Cookies " + + colors.END, + ) + NovulLogger(url, "No Persistent Cookies (via different User-Agent).") else: - verbout(R, 'No cookies are being set on any requests.') \ No newline at end of file + verbout(colors.R, "No cookies are being set on any requests.") diff --git a/xsrfprobe/modules/Referer.py b/xsrfprobe/modules/Referer.py index fe3dba9..4cdf26c 100644 --- a/xsrfprobe/modules/Referer.py +++ b/xsrfprobe/modules/Referer.py @@ -1,49 +1,62 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -import requests -from xsrfprobe.core.colors import * -from xsrfprobe.files.config import * +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + +from xsrfprobe.files.config import HEADER_VALUES, REFERER_URL, COOKIE_VALUE from xsrfprobe.core.verbout import verbout from xsrfprobe.core.request import Get from xsrfprobe.core.logger import VulnLogger, NovulLogger + def Referer(url): """ Check if the remote web application verifies the Referer before processing the HTTP request. """ - verbout(color.RED, '\n +--------------------------------------+') - verbout(color.RED, ' | Referer Based Request Validation |') - verbout(color.RED, ' +--------------------------------------+\n') + verbout(colors.RED, "\n +--------------------------------------+") + verbout(colors.RED, " | Referer Based Request Validation |") + verbout(colors.RED, " +--------------------------------------+\n") # Make the request normally and get content - verbout(O,'Making request on normal basis...') + verbout(colors.O, "Making request on normal basis...") req0x01 = Get(url) # Set normal headers... - verbout(GR,'Setting generic headers...') + verbout(colors.GR, "Setting generic headers...") gen_headers = HEADER_VALUES # Set a fake Referer along with UA (pretending to be a # legitimate request from a browser) - gen_headers['Referer'] = REFERER_URL + gen_headers["Referer"] = REFERER_URL # We put the cookie in request, if cookie supplied :D if COOKIE_VALUE: - gen_headers['Cookie'] = ','.join(cookie for cookie in COOKIE_VALUE) + gen_headers["Cookie"] = ",".join(cookie for cookie in COOKIE_VALUE) # Make the request with different referer header and get the content - verbout(O,'Making request with '+color.CYAN+'Tampered Referer Header'+color.END+'...') + verbout( + colors.O, + f"Making request with {colors.CYAN}Tampered Referer Header{colors.END}...", + ) req0x02 = Get(url, headers=gen_headers) - HEADER_VALUES.pop('Referer', None) + HEADER_VALUES.pop("Referer", None) + + if req0x01 is None or req0x02 is None: + verbout( + colors.RED, + " [!] Cannot compare the two requests as at least one of them is None", + ) + return False # Comparing the length of the requests' responses. If both content # lengths are same, then the site actually does not validate referer @@ -59,15 +72,39 @@ def Referer(url): # # TODO: This algorithm has lots of room for improvement. if len(req0x01.content) != len(req0x02.content): - print(color.GREEN+' [+] Endoint '+color.ORANGE+'Referer Validation'+color.GREEN+' Present!') - print(color.GREEN+' [-] Heuristics reveal endpoint might be '+color.BG+' NOT VULNERABLE '+color.END+'...') - print(color.ORANGE+' [+] Mitigation Method: '+color.BG+' Referer Based Request Validation '+color.END) - NovulLogger(url, 'Presence of Referer Header based Request Validation.') + print( + f"{colors.GREEN} [+] Endoint {colors.ORANGE}Referer Validation{colors.GREEN} Present!" + ) + print( + f"{colors.GREEN} [-] Heuristics reveal endpoint might be " + f"{colors.BG} NOT VULNERABLE {colors.END}..." + ) + print( + f"{colors.ORANGE} [+] Mitigation Method: {colors.BG} " + f"Referer Based Request Validation {colors.END}" + ) + NovulLogger(url, "Presence of Referer Header based Request Validation.") return True - else: - verbout(R,'Endpoint '+color.RED+'Referer Validation Not Present'+color.END+'!') - verbout(R,'Heuristics reveal endpoint might be '+color.BY+' VULNERABLE '+color.END+' to Origin Based CSRFs...') - print(color.CYAN+ ' [+] Possible CSRF Vulnerability Detected : '+color.GREY+url+'!') - print(color.ORANGE+' [+] Possible Vulnerability Type: '+color.BY+' No Referer Based Request Validation '+color.END) - VulnLogger(url, 'No Referer Header based Request Validation presence.', '[i] Response Headers: '+str(req0x02.headers)) - return False + + verbout( + colors.R, + f"Endpoint {colors.RED}Referer Validation Not Present{colors.END}", + ) + verbout( + colors.R, + f"Heuristics reveal endpoint might be {colors.BY} " + f"VULNERABLE {colors.END} to Origin Based CSRFs...", + ) + print( + f"{colors.CYAN} [+] Possible CSRF Vulnerability Detected : {colors.GREY}{url}" + ) + print( + f"{colors.ORANGE} [+] Possible Vulnerability Type: {colors.BY} " + f"No Referer Based Request Validation {colors.END}" + ) + VulnLogger( + url, + "No Referer Header based Request Validation presence.", + f"[i] Response Headers: {req0x02.headers}", + ) + return False diff --git a/xsrfprobe/modules/Tamper.py b/xsrfprobe/modules/Tamper.py index e5a8636..220283b 100644 --- a/xsrfprobe/modules/Tamper.py +++ b/xsrfprobe/modules/Tamper.py @@ -1,44 +1,47 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # XSRFProbe # -#-:-:-:-:-:-:-:-:-:# +# -:-:-:-:-:-:-:-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe from re import search, I -from urllib.parse import urlencode, quote -from xsrfprobe.core.colors import * + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + from xsrfprobe.core.request import Post -from xsrfprobe.files.config import * from xsrfprobe.core.verbout import verbout from xsrfprobe.core.utils import replaceStrIndex from xsrfprobe.files.paramlist import TOKEN_ERRORS from xsrfprobe.core.logger import VulnLogger, NovulLogger + def Tamper(url, action, req, body, query, para): - ''' + """ The main idea behind this is to tamper the Anti-CSRF tokens found and check the content length for related vulnerabilities. - ''' - verbout(color.RED, '\n +---------------------------------------+') - verbout(color.RED, ' | Anti-CSRF Token Tamper Validation |') - verbout(color.RED, ' +---------------------------------------+\n') + """ + verbout(colors.RED, "\n +---------------------------------------+") + verbout(colors.RED, " | Anti-CSRF Token Tamper Validation |") + verbout(colors.RED, " +---------------------------------------+\n") # Null char flags (hex) flagx1, destx1 = 0x00, 0x00 flagx2, destx2 = 0x00, 0x00 flagx3, destx3 = 0x00, 0x00 - verbout(GR, 'Proceeding for CSRF attack via Anti-CSRF token tampering...') + verbout(colors.GR, "Proceeding for CSRF attack via Anti-CSRF token tampering...") # First of all lets get out token from request - if para == '': + if para == "": return True # Coverting the token to a raw string, cause some special # chars might fu*k with the operation. - value = r'%s' % para + value = r"%s" % para copy = req # Alright lets start... @@ -47,13 +50,16 @@ def Tamper(url, action, req, body, query, para): # # Required check for checking if string at that position isn't the # same char we are going to replace with. - verbout(GR, 'Tampering Token by '+color.GREY+'index replacement'+color.END+'...') - if value[3] != 'a': - tampvalx1 = replaceStrIndex(value, 3, 'a') + verbout( + colors.GR, + "Tampering Token by " + colors.GREY + "index replacement" + colors.END + "...", + ) + if value[3] != "a": + tampvalx1 = replaceStrIndex(value, 3, "a") else: - tampvalx1 = replaceStrIndex(value, 3, 'x') - verbout(color.BLUE, ' [+] Original Token: '+color.CYAN+value) - verbout(color.BLUE, ' [+] Tampered Token: '+color.CYAN+tampvalx1) + tampvalx1 = replaceStrIndex(value, 3, "x") + verbout(colors.BLUE, " [+] Original Token: " + colors.CYAN + value) + verbout(colors.BLUE, " [+] Tampered Token: " + colors.CYAN + tampvalx1) # Lets build up the request... req[query] = tampvalx1 resp = Post(url, action, req) @@ -66,23 +72,45 @@ def Tamper(url, action, req, body, query, para): # request, then we have the vulnerability. # # NOTE: This algorithm has lots of room for improvement. - if str(resp.status_code).startswith('2'): destx1 = 0x01 - if not any(search(s, resp.text, I) for s in TOKEN_ERRORS): destx2 = 0x01 - if len(body) == len(resp.text): destx3 = 0x01 - if ((destx1 == 0x01 and destx2 == 0x01) or (destx3 == 0x01)): - verbout(color.RED,' [-] Anti-CSRF Token tamper by '+color.GREY+'index replacement'+color.RED+' returns valid response!') + if str(resp.status_code).startswith("2"): + destx1 = 0x01 + if not any(search(s, resp.text, I) for s in TOKEN_ERRORS): + destx2 = 0x01 + if len(body) == len(resp.text): + destx3 = 0x01 + if (destx1 == 0x01 and destx2 == 0x01) or (destx3 == 0x01): + verbout( + colors.RED, + " [-] Anti-CSRF Token tamper by " + + colors.GREY + + "index replacement" + + colors.RED + + " returns valid response!", + ) flagx1 = 0x01 - VulnLogger(url, 'Anti-CSRF Token tamper by index replacement returns valid response.', '[i] POST Query: '+req.__str__()) + VulnLogger( + url, + "Anti-CSRF Token tamper by index replacement returns valid response.", + "[i] POST Query: " + req.__str__(), + ) else: - verbout(color.RED,' [+] Token tamper in request does not return valid response!') - NovulLogger(url, 'Anti-CSRF Token tamper by index replacement does not return valid response.') + verbout( + colors.RED, " [+] Token tamper in request does not return valid response!" + ) + NovulLogger( + url, + "Anti-CSRF Token tamper by index replacement does not return valid response.", + ) # [Step 2]: Second we take the token and then remove a character # at a specific position and test the response body. - verbout(GR, 'Tampering Token by '+color.GREY+'index removal'+color.END+'...') + verbout( + colors.GR, + "Tampering Token by " + colors.GREY + "index removal" + colors.END + "...", + ) tampvalx2 = replaceStrIndex(value, 3) - verbout(color.BLUE, ' [+] Original Token: '+color.CYAN+value) - verbout(color.BLUE, ' [+] Tampered Token: '+color.CYAN+tampvalx2) + verbout(colors.BLUE, " [+] Original Token: " + colors.CYAN + value) + verbout(colors.BLUE, " [+] Tampered Token: " + colors.CYAN + tampvalx2) # Lets build up the request... req[query] = tampvalx2 resp = Post(url, action, req) @@ -92,23 +120,45 @@ def Tamper(url, action, req, body, query, para): # (Accepted) or a 30x (Redirection), then we know it worked. # # NOTE: This algorithm has lots of room for improvement. - if str(resp.status_code).startswith('2'): destx1 = 0x02 - if not any(search(s, resp.text, I) for s in TOKEN_ERRORS): destx2 = 0x02 - if len(body) == len(resp.text): destx3 = 0x02 - if ((destx1 == 0x02 and destx2 == 0x02) or destx3 == 0x02): - verbout(color.RED,' [-] Anti-CSRF Token tamper by '+color.GREY+'index removal'+color.RED+' returns valid response!') + if str(resp.status_code).startswith("2"): + destx1 = 0x02 + if not any(search(s, resp.text, I) for s in TOKEN_ERRORS): + destx2 = 0x02 + if len(body) == len(resp.text): + destx3 = 0x02 + if (destx1 == 0x02 and destx2 == 0x02) or destx3 == 0x02: + verbout( + colors.RED, + " [-] Anti-CSRF Token tamper by " + + colors.GREY + + "index removal" + + colors.RED + + " returns valid response!", + ) flagx2 = 0x01 - VulnLogger(url, 'Anti-CSRF Token tamper by index removal returns valid response.', '[i] POST Query: '+req.__str__()) + VulnLogger( + url, + "Anti-CSRF Token tamper by index removal returns valid response.", + "[i] POST Query: " + req.__str__(), + ) else: - verbout(color.RED,' [+] Token tamper in request does not return valid response!') - NovulLogger(url, 'Anti-CSRF Token tamper by index removal does not return valid response.') + verbout( + colors.RED, " [+] Token tamper in request does not return valid response!" + ) + NovulLogger( + url, + "Anti-CSRF Token tamper by index removal does not return valid response.", + ) # [Step 3]: Third we take the token and then remove the whole # anticsrf token and test the response body. - verbout(GR, 'Tampering Token by '+color.GREY+'Token removal'+color.END+'...') + verbout( + colors.GR, + "Tampering Token by " + colors.GREY + "Token removal" + colors.END + "...", + ) # Removing the anti-csrf token from request del req[query] - verbout(color.GREY, ' [+] Removed token parameter from request!') + verbout(colors.GREY, " [+] Removed token parameter from request!") # Lets build up the request... resp = Post(url, action, req) @@ -117,28 +167,94 @@ def Tamper(url, action, req, body, query, para): # (Accepted) or a 30x (Redirection), then we know it worked. # # NOTE: This algorithm has lots of room for improvement. - if str(resp.status_code).startswith('2'): destx1 = 0x03 - if not any(search(s, resp.text, I) for s in TOKEN_ERRORS): destx2 = 0x03 - if len(body) == len(resp.text): destx3 = 0x03 - if ((destx1 == 0x03 and destx2 == 0x03) or destx3 == 0x03): - verbout(color.RED,' [-] Anti-CSRF'+color.GREY+' Token removal'+color.RED+' returns valid response!') + if str(resp.status_code).startswith("2"): + destx1 = 0x03 + if not any(search(s, resp.text, I) for s in TOKEN_ERRORS): + destx2 = 0x03 + if len(body) == len(resp.text): + destx3 = 0x03 + if (destx1 == 0x03 and destx2 == 0x03) or destx3 == 0x03: + verbout( + colors.RED, + " [-] Anti-CSRF" + + colors.GREY + + " Token removal" + + colors.RED + + " returns valid response!", + ) flagx3 = 0x01 - VulnLogger(url, 'Anti-CSRF Token removal returns valid response.', '[i] POST Query: '+req.__str__()) + VulnLogger( + url, + "Anti-CSRF Token removal returns valid response.", + "[i] POST Query: " + req.__str__(), + ) else: - verbout(color.RED,' [+] Token tamper in request does not return valid response!') - NovulLogger(url, 'Anti-CSRF Token removal does not return valid response.') + verbout( + colors.RED, " [+] Token tamper in request does not return valid response!" + ) + NovulLogger(url, "Anti-CSRF Token removal does not return valid response.") # If any of the forgeries worked... - if ((flagx1==0x01 and flagx2==0x01) or (flagx1==0x01 and flagx3==0x01) or (flagx2==0x01 and flagx3==0x01)): - verbout(color.RED,' [+] The tampered token value works! Endpoint '+color.BR+' VULNERABLE to Replay Attacks '+color.END+'!') - verbout(color.ORANGE,' [-] The Tampered Anti-CSRF Token requested does NOT return a 40x or 50x response! ') - print(color.RED+' [-] Endpoint '+color.BR+' CONFIRMED VULNERABLE '+color.END+color.RED+' to Request Forgery Attacks...') - print(color.ORANGE+' [!] Vulnerability Type: '+color.BR+' Non-Unique Anti-CSRF Tokens in Requests '+color.END+'\n') - VulnLogger(url, 'Anti-CSRF Tokens are not Unique. Token Reuse detected.', '[i] Request: '+str(copy)) + if ( + (flagx1 == 0x01 and flagx2 == 0x01) + or (flagx1 == 0x01 and flagx3 == 0x01) + or (flagx2 == 0x01 and flagx3 == 0x01) + ): + verbout( + colors.RED, + " [+] The tampered token value works! Endpoint " + + colors.BR + + " VULNERABLE to Replay Attacks " + + colors.END, + ) + verbout( + colors.ORANGE, + " [-] The Tampered Anti-CSRF Token requested does NOT return a 40x or 50x response! ", + ) + print( + colors.RED + + " [-] Endpoint " + + colors.BR + + " CONFIRMED VULNERABLE " + + colors.END + + colors.RED + + " to Request Forgery Attacks..." + ) + print( + colors.ORANGE + + " [!] Vulnerability Type: " + + colors.BR + + " Non-Unique Anti-CSRF Tokens in Requests " + + colors.END + + "\n" + ) + VulnLogger( + url, + "Anti-CSRF Tokens are not Unique. Token Reuse detected.", + "[i] Request: " + str(copy), + ) return True else: - print(color.RED+' [-] The Tampered Anti-CSRF Token requested returns a 40x or 50x response... ') - print(color.GREEN+' [-] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') - print(color.ORANGE+' [!] CSRF Mitigation Method: '+color.BG+' Unique Anti-CSRF Tokens '+color.END+'\n') - NovulLogger(url, 'Unique Anti-CSRF Tokens. No token reuse.') + print( + colors.RED + + " [-] The Tampered Anti-CSRF Token requested returns a 40x or 50x response... " + ) + print( + colors.GREEN + + " [-] Endpoint " + + colors.BG + + " NOT VULNERABLE " + + colors.END + + colors.ORANGE + + " to CSRF Attacks..." + ) + print( + colors.ORANGE + + " [!] CSRF Mitigation Method: " + + colors.BG + + " Unique Anti-CSRF Tokens " + + colors.END + + "\n" + ) + NovulLogger(url, "Unique Anti-CSRF Tokens. No token reuse.") return False diff --git a/xsrfprobe/modules/Token.py b/xsrfprobe/modules/Token.py index d75d83a..e6d1b08 100644 --- a/xsrfprobe/modules/Token.py +++ b/xsrfprobe/modules/Token.py @@ -1,47 +1,66 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -from re import search, I -from time import sleep +from re import I from xsrfprobe.files import config -from xsrfprobe.core.colors import * + +import xsrfprobe.core.colors + +colors = xsrfprobe.core.colors.color() + from xsrfprobe.core.verbout import verbout from xsrfprobe.files import discovered from urllib.parse import urlencode, unquote from xsrfprobe.files.paramlist import COMMON_CSRF_NAMES, COMMON_CSRF_HEADERS + def Token(req, headers): - ''' + """ This method checks for whether Anti-CSRF Tokens are present in the request. - ''' - verbout(color.RED, '\n +---------------------------+') - verbout(color.RED, ' | Anti-CSRF Token Check |') - verbout(color.RED, ' +---------------------------+\n') - param = '' # Initializing param - query = '' + """ + verbout(colors.RED, "\n +---------------------------+") + verbout(colors.RED, " | Anti-CSRF Token Check |") + verbout(colors.RED, " +---------------------------+\n") + param = "" # Initializing param + query = "" found = False # First lets have a look at config.py and see if its set if config.TOKEN_CHECKS: - verbout(O,'Parsing request for detecting anti-csrf tokens...') + verbout(colors.O, "Parsing request for detecting anti-csrf tokens...") try: # Lets check for the request values. But before that lets encode and unquote the request :D - con = unquote(urlencode(req)).split('&') + con = unquote(urlencode(req)).split("&") for c in con: for name in COMMON_CSRF_NAMES: # Iterate over the list - qu = c.split('=') + qu = c.split("=") # Search if the token is there in request... if name.lower() in qu[0].lower(): - verbout(color.GREEN, ' [+] The form was requested with an '+color.BG+' Anti-CSRF Token '+color.END+color.GREEN+'!') - verbout(color.GREY, ' [+] Token Parameter: '+color.CYAN+qu[0]+'='+color.ORANGE+qu[1]) + verbout( + colors.GREEN, + " [+] The form was requested with an " + + colors.BG + + " Anti-CSRF Token " + + colors.END + + colors.GREEN, + ) + verbout( + colors.GREY, + " [+] Token Parameter: " + + colors.CYAN + + qu[0] + + "=" + + colors.ORANGE + + qu[1], + ) query, param = qu[0], qu[1] # We are appending the token to a variable for further analysis discovered.REQUEST_TOKENS.append(param) @@ -53,17 +72,51 @@ def Token(req, headers): for name in COMMON_CSRF_HEADERS: # Iterate over the list # Search if the token is there in request... if name.lower() in key.lower(): - verbout(color.GREEN, ' [+] The form was requested with an '+color.BG+' Anti-CSRF Token Header '+color.END+color.GREEN+'!') - verbout(color.GREY, ' [+] Token Parameter: '+color.CYAN+qu[0]+'='+color.ORANGE+qu[1]) + verbout( + colors.GREEN, + " [+] The form was requested with an " + + colors.BG + + " Anti-CSRF Token Header " + + colors.END + + colors.GREEN, + ) + verbout( + colors.GREY, + " [+] Token Parameter: " + + colors.CYAN + + qu[0] + + "=" + + colors.ORANGE + + qu[1], + ) query, param = key, value # We are appending the token to a variable for further analysis discovered.REQUEST_TOKENS.append(param) break # Break execution if a Anti-CSRF token is found except Exception as e: - verbout(R, 'Request Parsing Exception!') - verbout(R, 'Error: '+e.__str__()) + verbout(colors.R, "Request Parsing Exception!") + verbout(colors.R, "Error: " + e.__str__()) if param: return (query, param) - verbout(color.ORANGE,' [-] The form was requested '+color.RED+' Without an Anti-CSRF Token '+color.END+color.ORANGE+'...') - print(color.RED+' [-] Endpoint seems '+color.BR+' VULNERABLE '+color.END+color.RED+' to '+color.BR+' POST-Based Request Forgery '+color.END) + verbout( + colors.ORANGE, + " [-] The form was requested " + + colors.RED + + " Without an Anti-CSRF Token " + + colors.END + + colors.ORANGE + + "...", + ) + print( + colors.RED + + " [-] Endpoint seems " + + colors.BR + + " VULNERABLE " + + colors.END + + colors.RED + + " to " + + colors.BR + + " POST-Based Request Forgery " + + colors.END + ) return (None, None) diff --git a/xsrfprobe/modules/__init__.py b/xsrfprobe/modules/__init__.py index 800794b..7dc4748 100644 --- a/xsrfprobe/modules/__init__.py +++ b/xsrfprobe/modules/__init__.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection -#This module requires XSRFProbe -#https://github.com/0xInfection/XSRFProbe \ No newline at end of file +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe diff --git a/xsrfprobe/xsrfprobe.py b/xsrfprobe/xsrfprobe.py index 34f0f28..a0a902e 100644 --- a/xsrfprobe/xsrfprobe.py +++ b/xsrfprobe/xsrfprobe.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # XSRF Probe # -#-:-:-:-:-:-:-::-:-:# +# -:-:-:-:-:-:-::-:-:# # Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe + def startEngine(): from xsrfprobe.core import main # import stuff + main.Engine() # start the Scanner Engine ;)