diff --git a/.travis.yml b/.travis.yml index bdaf0a2..6a258b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,12 @@ before_script: # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --quiet script: + # Help message. - python xsrfprobe.py --help - - python xsrfprobe.py -u http://www.webscantest.com --timeout 5 --max-chars 3 --quiet + # Crawl entire www.webscantest.com and submit forms. + - python xsrfprobe.py -u http://www.webscantest.com --crawl --timeout 5 --max-chars 3 --quiet + # Test only a single endpoint vulnerability. + - python xsrfprobe.py -u http://www.webscantest.com/csrf/csrfpost.php notifications: on_success: change on_failure: change # `always` will be the setting once code changes slow down diff --git a/core/banner.py b/core/banner.py index afd8ea9..2b42e7c 100644 --- a/core/banner.py +++ b/core/banner.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -#Author: 0xInfection (@_tID) +#Author: 0xInfection #This module requires XSRF-Probe #https://github.com/0xInfection/XSRF-Probe @@ -17,31 +17,31 @@ def banner(): print('\n\n') - time.sleep(0.1) + time.sleep(0.05) print(color.ORANGE+' _____ _____ _____ _____ _____ ') - time.sleep(0.1) + 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.1) + 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.1) + 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.1) + 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.1) + time.sleep(0.05) print(color.ORANGE+' |_____| |_____| |_____| |_____| |_____| \n\n') - time.sleep(0.1) + time.sleep(0.05) def banabout(): # some fancy banner stuff :p - print(color.BLUE+' [---] '+color.GREY+'XSRF Probe |'+color.RED+' A'+color.ORANGE+' Cross Site Request Forgery '+color.RED+'Audit Toolkit '+color.BLUE+'[---]') - time.sleep(0.2) + print(color.BLUE+' [---] '+color.GREY+'XSRF Probe,'+color.RED+' A'+color.ORANGE+' Cross Site Request Forgery '+color.RED+'Audit Toolkit '+color.BLUE+'[---]') + time.sleep(0.1) print(color.BLUE+' [---] [---]') - time.sleep(0.2) + time.sleep(0.1) print(color.BLUE+' [---] '+color.PURPLE+' '+color.GREEN+'~ Author : '+color.CYAN+'The Infected Drake ~ '+color.BLUE+' [---]') - time.sleep(0.2) + time.sleep(0.1) print(color.BLUE+' [---] '+color.CYAN+' ~ github.com / '+color.GREY+'0xInfection ~ '+color.BLUE+' [---]') - time.sleep(0.2) + time.sleep(0.1) print(color.BLUE+' [---] [---]') - time.sleep(0.2) - print(color.BLUE+' [---] '+color.ORANGE+' ~ Version '+color.RED+open('files/VersionNum').read().strip()+color.ORANGE+' ~ '+color.BLUE+' [---]\n') - time.sleep(0.2) + time.sleep(0.1) + print(color.BLUE+' [---] '+color.ORANGE+' ~ Version '+color.RED+open('files/VersionNum').read().strip()+color.ORANGE+' ~ '+color.BLUE+' [---]\n') + time.sleep(0.1) diff --git a/core/colors.py b/core/colors.py index f3ad717..824a6a9 100644 --- a/core/colors.py +++ b/core/colors.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe diff --git a/core/forms.py b/core/forms.py index b3a7646..2e79db9 100644 --- a/core/forms.py +++ b/core/forms.py @@ -9,42 +9,42 @@ #This module requires XSRF-Probe #https://github.com/0xInfection/XSRF-Probe -def form10(): # an example form to make sure the stuff works properly ;) +def testFormx1(): # an example xsrfprobe-test-form to make sure the stuff works properly ;) - form0x01 = """
-
- - + test_form_0x01 = """ +
+ +
-
- - +
+ +
- - - + + +
""" - return form0x01 + return test_form_0x01 -def form20(): # an example of a form (used drupal) +def testFormx2(): # an example of a xsrfprobe-test-form (used drupal) - form0x02 = """
-
- - + test_form_0x02 = """ +
+ +
-
- - +
+ +
- - - + + +
""" - return form0x02 + return test_form_0x02 diff --git a/core/inputin.py b/core/inputin.py index 3436fea..9dd2ff5 100644 --- a/core/inputin.py +++ b/core/inputin.py @@ -9,8 +9,8 @@ #This module requires XSRF-Probe #https://github.com/0xInfection/XSRF-Probe -import sys -import socket +import socket, requests +from tld import get_fld from core.colors import * from files.config import SITE_URL @@ -22,15 +22,37 @@ def inputin(): if 'http' not in web: # add protocol to site web = 'http://' + web - web0 = web.split('//')[1] + web0 = get_fld(web) try: - print(O+'Testing site status...') + print(O+'Testing site '+color.GREY+web0+color.END+' status...') socket.gethostbyname(web0) # test whether site is up or not print(color.GREEN+' [+] Site seems to be up!'+color.END) except socket.gaierror: # if site is down print(R+'Site seems to be down...') - sys.exit(0) - - if not web.endswith('/'): # check - web = web + '/' # make sure the site address ends with '/' - return web + quit() + try: + print(O+'Testing '+color.CYAN+web.split('//')[1].replace(web0,'')+color.END+' endpoint status...') + requests.get(web) + print(color.GREEN+' [+] Endpoint seems to be up!'+color.END) + except requests.exceptions.MissingSchema as e: + verbout(R, 'Exception at: '+color.GREY+url) + verbout(R, 'Error: Invalid URL Format') + ErrorLogger(url, e.__str__()) + quit() + except requests.exceptions.HTTPError as e: # if error + verbout(R, "HTTP Error : "+main_url) + ErrorLogger(main_url, e.__str__()) + quit() + except requests.exceptions.ConnectionError as e: + verbout(R, 'Connection Aborted : '+main_url) + ErrorLogger(main_url, e.__str__()) + quit() + except Exception as e: + verbout(R, "Exception Caught: "+e.__str__()) + ErrorLogger(main_url, e.__str__()) + quit() # if at all nothing happens :( + if not web0.endswith('/'): + web0 = web0 + '/' + if web.split('//')[1] == web0: + return web, '' + return (web, web0) diff --git a/core/logger.py b/core/logger.py index f2cb8ed..9ac1458 100644 --- a/core/logger.py +++ b/core/logger.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -13,16 +13,21 @@ from core.colors import * from files.config import * from core.verbout import verbout +from files.discovered import INTERNAL_URLS, FILES_EXEC, SCAN_ERRORS +from files.discovered import 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 + '.txt' + output_file = OUTPUT_DIR + filename + '.log' with open(output_file, 'w+', encoding='utf8') as f: - for m in content: - f.write(m+'\n') + if type(content) is tuple or type(content) is list: + for m in content: # if it is list or tuple, it is iterable + f.write(m+'\n') + else: + f.write(content) # else we write out as it is... ;) f.write('\n') def pheaders(tup): @@ -35,3 +40,31 @@ def pheaders(tup): for key, val in tup.items(): verbout(' ',color.CYAN+key+': '+color.ORANGE+val) verbout('','') + +def GetLogger(): + if INTERNAL_URLS: + logger('internal-links', INTERNAL_URLS) + if SCAN_ERRORS: + logger('errored', SCAN_ERRORS) + if FILES_EXEC: + logger('files-found', FILES_EXEC) + if REQUEST_TOKENS: + logger('anti-csrf-tokens', REQUEST_TOKENS) + if FORMS_TESTED: + logger('forms-tested', FORMS_TESTED) + if VULN_LIST: + logger('vulnerabilities', VULN_LIST) + if STRENGTH_LIST: + logger('strengths', STRENGTH_LIST) + +def ErrorLogger(url, error): + con = '(i) '+url+' -> '+error.__str__() + SCAN_ERRORS.append(con) + +def VulnLogger(url, vuln, content=''): + tent = '[!] '+url+' -> '+vuln+'\n\n'+str(content)+'\n\n' + VULN_LIST.append(tent) + +def NovulLogger(url, strength): + tent = '[+] '+url+' -> '+strength + STRENGTH_LIST.append(tent) diff --git a/core/main.py b/core/main.py index 69794e4..b2e24d1 100644 --- a/core/main.py +++ b/core/main.py @@ -5,7 +5,7 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -25,7 +25,7 @@ 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.") quit() try: - import requests, stringdist, lxml, bs4 + 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.') @@ -35,11 +35,15 @@ from core.inputin import inputin from core.request import Get, Post from core.verbout import verbout -from core.forms import form10, form20 +from core.prettify import formPrettify from core.banner import banner, banabout +from core.forms import testFormx1, testFormx2 +from core.logger import ErrorLogger, GetLogger +from core.logger import VulnLogger, NovulLogger # Imports from files from files.config import * +from files.discovered import FORMS_TESTED # Imports from modules from modules import Debugger @@ -63,125 +67,230 @@ def Engine(): # lets begin it! os.system('clear') # Clear shit from terminal :p banner() # Print the banner banabout() # The second banner - web = inputin() # Take the input - form1 = form10() # Get the form 1 ready - form2 = form20() # Get the form 2 ready - + web, fld = inputin() # Take the input + form1 = testFormx1() # Get the form 1 ready + form2 = testFormx2() # Get the form 2 ready # For the cookies that we encounter during requests... Cookie0 = http.cookiejar.CookieJar() # First as User1 Cookie1 = http.cookiejar.CookieJar() # Then as User2 resp1 = build_opener(HTTPCookieProcessor(Cookie0)) # Process cookies resp2 = build_opener(HTTPCookieProcessor(Cookie1)) # Process cookies - actionDone = [] # init to the done stuff - csrf = '' # no token initialise / invalid token - ref_detect = 0x00 # Null Char - ori_detect = 0x00 - init1 = web # get the starting page + 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 + init1 = web # First init + resp1.open(init1) # Makes request as User2 + resp2.open(init1) # Make request as User1 - 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 - - action = init1 # First init - - resp1.open(action) # Makes request as User2 - resp2.open(action) # Make request as User1 - - verbout(GR, "Initializing crawling and scanning...") - crawler = Crawler.Handler(init1, resp1) # Init to the Crawler handler - + # Now there are 2 different modes of scanning and crawling here. + # 1st -> Testing a single endpoint without the --crawl flag. + # 2nd -> Testing all endpoints with the --crawl flag. try: - while crawler.noinit(): # Until 0 urls left - url = next(crawler) # Go for next! - - print(C+'Crawling :> '+color.CYAN+url) # Display what url its crawling - + # Implementing the first mode. [NO CRAWL] + if not CRAWL_SITE: + url = web + response = Get(url).text try: - soup = crawler.process(web) # Start the parser - if not soup: - continue # Making sure not to end the program yet... - i = 0 # Set count = 0 - if REFERER_ORIGIN_CHECKS: - # Referer Based Checks if True... - verbout(O, 'Checking endpoint request validation via '+color.GREY+'Referer'+color.END+' Checks...') - if Referer(url): - ref_detect = 0x01 - verbout(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...') - if Origin(url): - ori_detect = 0x01 - - if COOKIE_BASED: - Cookie(url) - - # 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 - 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: + verbout(O,'Trying to parse response...') + soup = BeautifulSoup(response) # Parser init + except HTMLParser.HTMLParseError: + verbout(R,'BeautifulSoup Error: '+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...') + if Referer(url): + ref_detect = 0x01 + verbout(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...') + 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 + verbout(O,'Testing form:\n'+color.CYAN) + formPrettify(m.prettify()) + verbout('', '') + FORMS_TESTED.append('(i) '+url+':\n\n'+m.prettify()+'\n') + try: + if m['action']: + pass + except KeyError: + 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... + # If form submission is kept to True + if FORM_SUBMISSION: + try: + # NOTE: Slow connections may cause read timeouts which may result in AttributeError + # So the idea here is tp make requests pretending to be 3 different users. + # Now a series of requests will be targeted against the site with different + # 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 + # 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']) + except KeyError: + 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) + else: + 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) + o2 = resp2.open(url).read() # make request as user2 try: - result = form.prepareFormInputs(m) # prepare inputs - r1 = Post(url, action, result).text # make request with token values generated as user1 - result = form.prepareFormInputs(m) # prepare the input types - r2 = Post(url, action, result).text # again make request with token values generated as user2 - # Go for token based entropy checks... + form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form + except IndexError: + verbout(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 ((not query) or (txor)): try: if m['name']: - query, token = Entropy(result, url, m['action'], m['name']) + PostBased(url, r1.text, r2.text, r3.text, m['action'], result, genpoc, m.prettify(), m['name']) except KeyError: - query, token = Entropy(result, url, m['action']) - # Go for token parameter tamper checks. - if (query and token): - Tamper(url, action, result, r2, query, token) - o2 = resp2.open(url).read() # make request as user2 + 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 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 :( + ErrorLogger(url, msg) + actionDone.append(action) # add the stuff done + i+=1 # Increase user iteration + else: + # Implementing the 2nd mode [CRAWLING AND SCANNING]. + verbout(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 + try: + soup = crawler.process(fld) # Start the parser + if not soup: + 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...') + if Referer(url): + ref_detect = 0x01 + verbout(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...') + 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') + try: + if m['action']: + pass + except KeyError: + 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... + # If form submission is kept to True + if FORM_SUBMISSION: try: - form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form - except IndexError: - verbout(R, 'Form Error') - continue # making sure program won't end here (dirty fix :( ) - verbout(GR, 'Preparing form inputs...') - contents2 = form.prepareFormInputs(form2) # prepare for form 2 as user2 - r3 = Post(url,action,contents2).text # make request as user3 with user2's form - if POST_BASED: + 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']: - PostBased(url, r1, r2, r3, m['action'], result, m['name']) + query, token = Entropy(result, url, r1.headers, m.prettify(), m['action'], m['name']) except KeyError: - PostBased(url, r1, r2, r3, m['action'], result) - - except HTTPError as msg: # if runtime exception... - verbout(R, 'Exception : '+msg.__str__()) # again exception :( - - actionDone.append(action) # add the stuff done - i+=1 # Increase user iteration - - except URLError: # if again... - verbout(R, 'Exception at : '+url) # again exception -_- - time.sleep(0.4) - verbout(O, 'Moving on...') - continue # make sure it doesn't stop - + 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) + else: + 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) + o2 = resp2.open(url).read() # make request as user2 + try: + form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form + except IndexError: + verbout(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)): + try: + 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()) + 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.') + except HTTPError as msg: # if runtime exception... + verbout(color.RED, ' [-] Exception : '+color.END+msg.__str__()) # again exception :( + ErrorLogger(url, msg) + actionDone.append(action) # add the stuff done + i+=1 # Increase user iteration + except URLError as e: # if again... + verbout(R, 'Exception at : '+url) # again exception -_- + time.sleep(0.4) + verbout(O, 'Moving on...') + ErrorLogger(url, e) + continue # make sure it doesn't stop at exceptions + # 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)) + ErrorLogger(url, e) + quit() + GetLogger() # The scanning has finished, so now we can log out all the links ;) print('\n'+G+"Scan completed!"+'\n') Analysis() # For Post Scan Analysis - - # 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)) - quit() - - except KeyboardInterrupt: # incase user wants to exit ;-; (while crawling) + except KeyboardInterrupt as e: # Incase user wants to exit :') (while crawling) verbout(R, 'User Interrupt!') time.sleep(1.5) Analysis() # For Post scan Analysis print(R+'Aborted!') # say goodbye + ErrorLogger('KeyBoard Interrupt', 'Aborted') + GetLogger() # The scanning has interrupted, so now we can log out all the links ;) quit() +# except Exception as e: + # verbout(R, e.__str__()) + # ErrorLogger(url, e) diff --git a/core/options.py b/core/options.py index d02d253..0ec3f2a 100644 --- a/core/options.py +++ b/core/options.py @@ -5,15 +5,15 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe # Importing stuff -import argparse, sys -import urllib.parse +import argparse, sys, tld +import urllib.parse, os from files import config -from core.colors import R +from core.colors import R, G from core.updater import updater # Processing command line arguments @@ -32,17 +32,22 @@ optional.add_argument('-o', '--output', help='Output directory where files to be stored. Default is the`files` 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('-v', '--verbose', help='Increase the verbosity of the output (e.g., -vv is more than -v). ', dest='verbose', action='store_true') # 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('--headers', help='Comma separated list of custom headers you\'d want to use. For example: ``--headers Accept=text/php, DNT=1``.', dest='headers', type=str) +optional.add_argument('--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('--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('--timeout', help='HTTP request timeout value in seconds. The entered value must be in floating point decimal. Example: ``--timeout 10.0``', dest='timeout', type=float) +optional.add_argument('--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('--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('--skip-analysis', help='Skip the Post-Scan Analysis of Tokens which were gathered during requests', dest='skipal', action='store_true') +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('--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('--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') args = parser.parse_args() @@ -75,6 +80,14 @@ if args.skipal: config.SCAN_ANALYSIS = False +# Option to skip poc generation +if args.skippoc: + config.POC_GENERATION = False + +# Option to generate malicious form +if args.malicious: + config.GEN_MALICIOUS = True + # Updating main root url if not args.version and not args.update: if args.url: # and not args.help: @@ -83,7 +96,13 @@ else: config.SITE_URL = 'http://'+args.url else: - print(R+'You must supply a url.') + print(R+'You must supply a url/endpoint.') + +# Crawl the site if --crawl supplied. +if args.crawl: + config.CRAWL_SITE = True + # Turning off the display header feature due to too much log generation. + config.DISPLAY_HEADERS = False if args.cookie: # Assigning Cookie @@ -96,6 +115,10 @@ # security mechanisms (or worse, perhaps block your ip?) config.USER_AGENT_RANDOM = False +# Set the headers displayer to 1 (actively display headers) +if args.disphead: + config.DISPLAY_HEADERS = True + # Timeout value if args.timeout: config.TIMEOUT_VALUE = args.timeout @@ -110,7 +133,7 @@ # #config.HEADER_VALUES = {} for m in args.headers.split(','): - config.HEADER_VALUES[m.split('=')[0]] = m.split('=')[1] + config.HEADER_VALUES[m.split('=')[0]] = m.split('=')[1] # nice hack ;) if args.exclude: exc = args.exclude @@ -122,15 +145,24 @@ if args.randagent: # If random-agent argument supplied... config.USER_AGENT_RANDOM = True - # Turn off a single User-Agent mechanism + # Turn off a single User-Agent mechanism... config.USER_AGENT = '' if config.SITE_URL: if args.output: # If output directory is mentioned... - config.OUTPUT_DIR = args.output + config.SITE_URL.split('//')[1] + try: + if not os.path.exists(args.output+tld.get_fld(config.SITE_URL)): + os.makedirs(args.output+tld.get_fld(config.SITE_URL)) + except FileExistsError: + pass + config.OUTPUT_DIR = args.output+tld.get_fld(config.SITE_URL) + '/' else: - config.OUTPUT_DIR = 'files' + config.SITE_URL.split('//')[1] + try: + os.makedirs('output/'+tld.get_fld(config.SITE_URL)) + except FileExistsError: + pass + config.OUTPUT_DIR = 'output/'+tld.get_fld(config.SITE_URL) + '/' if args.quiet: config.DEBUG = False diff --git a/core/prettify.py b/core/prettify.py new file mode 100644 index 0000000..a4339de --- /dev/null +++ b/core/prettify.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +#-:-:-:-:-:-:-::-:-:# +# XSRF Probe # +#-:-:-:-:-:-:-::-:-:# + +# Author: 0xInfection +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe + +import re +from core.colors import * + +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: + line = newLine + # Find starting tags + pattern = re.findall(r"""(<+\w+>)""", line) + for grp in pattern: + starttag = ''.join(grp) + if starttag: + line = line.replace(starttag, color.BLUE + starttag + color.END) + # Find attributes + pattern = re.findall(r'''(\s\w+=)''', line) + for grp in pattern: + stu = ''.join(grp) + if stu: + line = line.replace(stu, color.CYAN + stu + color.END) + # Find ending tags + pattern = re.findall(r'''()''', line) + for grp in pattern: + endtag = ''.join(grp) + if endtag: + line = line.replace(endtag, color.CYAN + endtag + color.END) + if line != newLine: + highlighted.append(line) + else: + highlighted.append(color.GREY+newLine) + for h in highlighted: + 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 + +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 diff --git a/core/randua.py b/core/randua.py index 591c898..581c881 100644 --- a/core/randua.py +++ b/core/randua.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -37,7 +37,21 @@ '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 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' ] def RandomAgent(): diff --git a/core/request.py b/core/request.py index 7e6e3bf..7500dfb 100644 --- a/core/request.py +++ b/core/request.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -13,10 +13,10 @@ from core.colors import * from files.config import * from core.verbout import verbout -from core.logger import pheaders from core.randua import RandomAgent from urllib.parse import urljoin -from files.discovered import FILES_EXEC # import ends +from files.discovered import FILES_EXEC +from core.logger import pheaders, ErrorLogger # import ends headers = HEADER_VALUES # set the headers @@ -26,7 +26,7 @@ headers['Cookie'] = cookie # Set User-Agent -if USER_AGENT_RANDOM: +if USER_AGENT_RANDOM or not USER_AGENT: headers['User-Agent'] = RandomAgent() else: headers['User-Agent'] = USER_AGENT @@ -38,22 +38,29 @@ def Post(url, action, data): ''' time.sleep(DELAY_VALUE) # If delay param has been supplied verbout(GR, 'Processing the '+color.GREY+'POST'+color.END+' Request...') - main_url = urljoin(url, action) # encode stuff to make callable + 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) if DISPLAY_HEADERS: pheaders(response.headers) return response # read data content - except requests.exceptions: # if error - verbout(R, "HTTP Error : "+action) - return - except ValueError: # again if valuerror - verbout(R, "Value Error : "+action) - return + except requests.exceptions.HTTPError as e: # if error + verbout(R, "HTTP Error : "+main_url) + ErrorLogger(main_url, e.__str__()) + return None + except requests.exceptions.ConnectionError as e: + verbout(R, 'Connection Aborted : '+main_url) + ErrorLogger(main_url, e.__str__()) + return None + except ValueError as e: # again if valuerror + verbout(R, "Value Error : "+main_url) + ErrorLogger(main_url, e.__str__()) + return None except Exception as e: verbout(R, "Exception Caught: "+e.__str__()) - return '' # if at all nothing happens :( + ErrorLogger(main_url, e.__str__()) + return None # if at all nothing happens :( def Get(url, headers=headers): ''' @@ -65,17 +72,30 @@ def Get(url, headers=headers): # Making sure the url is not a file if url.split('.')[-1].lower() in (FILE_EXTENSIONS or EXECUTABLES): FILES_EXEC.append(url) - print(G+'Found File: '+color.BLUE+url) - return + verbout(G, 'Found File: '+color.BLUE+url) + return None try: verbout(GR, 'Processing the '+color.GREY+'GET'+color.END+' Request...') - req = requests.get(url, headers=headers, timeout=TIMEOUT_VALUE, stream=False, verify=False) + req = requests.get(url, headers=headers, timeout=TIMEOUT_VALUE, stream=False) # Displaying headers if DISPLAY_HEADERS is 'True' if DISPLAY_HEADERS: pheaders(req.headers) # Return the object return req except requests.exceptions.MissingSchema as e: - print(R+'Exception at: '+color.GREY+url) - print(R+'Error: Invalid URL Format') - return + verbout(R, 'Exception at: '+color.GREY+url) + verbout(R, 'Error: Invalid URL Format') + ErrorLogger(url, e.__str__()) + return None + except requests.exceptions.HTTPError as e: # if error + verbout(R, "HTTP Error : "+main_url) + ErrorLogger(main_url, e.__str__()) + return None + except requests.exceptions.ConnectionError as e: + verbout(R, 'Connection Aborted : '+main_url) + ErrorLogger(main_url, e.__str__()) + return None + except Exception as e: + verbout(R, "Exception Caught: "+e.__str__()) + ErrorLogger(main_url, e.__str__()) + return None # if at all nothing happens :( diff --git a/core/updater.py b/core/updater.py index f41d02a..f54e09b 100644 --- a/core/updater.py +++ b/core/updater.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -14,6 +14,9 @@ from requests import get def updater(): + ''' + Function to update XSRFProbe seamlessly. + ''' print(GR+'Checking for updates...') vno = get('https://github.com/raw/0xInfection/XSRFProbe/master/files/VersionNum').text print(GR+'Version on GitHub: '+color.CYAN+vno.strip()) diff --git a/core/utils.py b/core/utils.py index 1b27e93..5f49e4c 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- #-:-:-:-:-:-:-:-:-:# # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -#Author: @_tID -#This module requires XSRFProbe -#https://github.com/0xInfection/XSRFProbe +# Author: 0xInfection +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe from difflib import SequenceMatcher @@ -18,7 +18,7 @@ def sameSequence(str1,str2): ''' # Initialize SequenceMatcher object with # Input string - seqMatch = SequenceMatcher(None,str1,str2) + seqMatch = SequenceMatcher(None, str1, str2) # Find match of longest sub-string # Output will be like Match(a=0, b=0, size=5) @@ -60,10 +60,6 @@ def byteString(s, encoding='utf8'): s = str(s) return s -# Iterative Python program to check if a string is subsequence of another string - -# Returns true if str1 is a subsequence of str2 -# m is length of str1, n is length of str2 def subSequence(str1,str2): ''' Returns whether 'str1' and 'str2' are subsequence diff --git a/core/verbout.py b/core/verbout.py index 4d7487d..57fb174 100644 --- a/core/verbout.py +++ b/core/verbout.py @@ -5,14 +5,14 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe from core.colors import * from files.config import DEBUG as verbose -def verbout(stat,content_info): +def verbout(stat, content_info): ''' This module is for giving a verbose output. @@ -22,3 +22,7 @@ def verbout(stat,content_info): if verbose: # Concatenate the stat type and string value and print out print(stat+content_info) + +#def verbo_sity(*verb_args): +# if verb_args[0] > (3 - args.verbose): +# print verb_args[1] diff --git a/files/VersionNum b/files/VersionNum index 46b105a..437382d 100644 --- a/files/VersionNum +++ b/files/VersionNum @@ -1 +1 @@ -v2.0.0 +v2 (stable) diff --git a/files/__init__.py b/files/__init__.py index 793ff4b..310ac79 100644 --- a/files/__init__.py +++ b/files/__init__.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection #This module requires XSRFProbe #https://github.com/0xInfection/XSRFProbe diff --git a/files/config.py b/files/config.py index c62c43a..039b9b7 100644 --- a/files/config.py +++ b/files/config.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -14,14 +14,22 @@ # Lets assign some global variables... global SITE_URL, DEBUG, USER_AGENT, USER_AGENT_RANDOM, COOKIE_BASED, COOKIE_VALUE global HEADER_VALUES, TIMEOUT_VALUE, REFERER_ORIGIN_CHECKS, REFERER_URL, POST_BASED +global DISPLAY_HEADERS, EXECUTABLES, FILE_EXTENSIONS, POC_GENERATION, OUTPUT_DIR +global CRAWL_SITE, TOKEN_CHECKS, DELAY_VALUE, SCAN_ANALYSIS, EXCLUDE_DIRS, GEN_MALICIOUS # Site Url to be scanned (Required) SITE_URL = '' +# Switch for whether to crawl the site or not +CRAWL_SITE = False + # Print out verbose (turn it off for only brief outputs). # Turning off is Highly Discouraged, since you will miss what the tool is doing. DEBUG = True +# Debug level of the output (beta test feature) +DEBUG_LEVEL = 3 + # User-Agent to be used (If COOKIE_VALUE is not supplied) USER_AGENT_RANDOM = False @@ -126,6 +134,14 @@ # results in not analysing the tokens gathered. SCAN_ANALYSIS = True +# Option to skip PoC Form Generation of POST_BASED Request Forgeries. +# The form will not be generated. +POC_GENERATION = True + +# Option whether or not to generate a malicious CSRF form with all +# hidden fields. +GEN_MALICIOUS = False + # 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',] diff --git a/files/dcodelist.py b/files/dcodelist.py index 4448ca6..9c4498b 100644 --- a/files/dcodelist.py +++ b/files/dcodelist.py @@ -5,13 +5,82 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe # This file contains various regex expressions for detecting the # encoding type of strings. +# Token hash encoding detection regex database +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}$"), + ("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}$"), + ("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}$"), ( + "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}$"), + ) + # Get rid of Double ../../ RID_DOUBLE = r'/\.\./' @@ -35,19 +104,3 @@ # Protocol Types PROTOCOLS = r'(.*\/)[^\/]*' - -# Token encoding detection -token = { - # Base64 encoded strings. - 'BASE64': r'^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$', - # SHA2 encoded strings/hashes. - 'SHA2' : r'^([a-f0-9]{64})$', - # SHA1 encoded strings/hashes. - 'SHA1' : r'^([a-f0-9]{40})$', - # MD5 encoded strings/hashes. - 'MD5' : r'^([a-f0-9]{32})$', - # Hex Strings. - 'HEX' : r'^(0x|0X)?[a-fA-F0-9]+$', - # FromChar Strings - 'FROMCHAR': r'\d*, \d*,' - } diff --git a/files/discovered.py b/files/discovered.py index 5debcda..63bf204 100644 --- a/files/discovered.py +++ b/files/discovered.py @@ -5,13 +5,19 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe # INFO: This file is for storing the various important parts of # requests discovered during making of various requests. +# Vulnerabilities which were noticed +VULN_LIST = [] + +# Strengths or positive sides of the application +STRENGTH_LIST = [] + # This is for storing the various tokens which got discovered # during making the requests. This will be used for various # analysis of token generation prototypes and logic used in @@ -21,5 +27,11 @@ # List of all Urls that we found INTERNAL_URLS = [] -# Files discovered during crawling +# Files/executables discovered during crawling FILES_EXEC = [] + +# Forms that were tested +FORMS_TESTED = [] + +# Errors that were encountered +SCAN_ERRORS = [] diff --git a/files/paramlist.py b/files/paramlist.py index ad86cb8..0f6ef2d 100644 --- a/files/paramlist.py +++ b/files/paramlist.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -18,36 +18,40 @@ # 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', # 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 + '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 + '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', - 'csrf', 'authenticity', 'auth_token', 'auth', 'anti_csrf', - 'auth_value', + 'debug_token', 'csrf_value', '_debugval', 'csrf_token', - 'VerificationToken', '__authvalue', - 'authenticity_value', '__token', '__auth', 'secret', @@ -66,6 +70,19 @@ 'vtoken' ) +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' + ) + # TODO: Add and replace with more valid and arguable exclusion lists EXCLUSIONS_LIST = ( 'logout', @@ -76,3 +93,27 @@ 'osCsid', 'action=logout', ) + +# List of common errors shown when token is tampered. +TOKEN_ERRORS = ( + 'the required form field', + 'token could not be decrypted', + 'invalid token', + 'wrong', + 'error', + 'not valid', + 'please check your request', + 'your browser did something unexpected', + 'clearing your cookies', + 'tampered token', + 'null', + 'unacceptable', + 'false', + 'void', + 'incorrect', + 'inoperative', + 'faulty', + 'absurd', + 'inconsistent', + 'not acceptable', + ) diff --git a/modules/Analysis.py b/modules/Analysis.py index 8d372e0..ed11dce 100644 --- a/modules/Analysis.py +++ b/modules/Analysis.py @@ -5,16 +5,18 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe import stringdist import itertools, time from core.colors import * +from .Entropy import calcEntropy from core.verbout import verbout from core.utils import sameSequence, byteString from files.discovered import REQUEST_TOKENS +from core.logger import VulnLogger, NovulLogger def Analysis(): ''' @@ -25,9 +27,9 @@ def Analysis(): ctr = 0 # Counter variable set to 0 # Checking if the no of tokens is greater than 1 if len(REQUEST_TOKENS) > 1: - print(color.RED+'\n +--------------+') - print(color.RED+' | Analysis |') - print(color.RED+' +--------------+\n') + 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))) # The idea behind this is to generate all possible combinations (not @@ -36,8 +38,10 @@ def Analysis(): for tokenx1, tokenx2 in itertools.combinations(REQUEST_TOKENS, 2): try: verbout(GR, 'Analysing 2 Anti-CSRF Tokens from gathered requests...') - verbout(C, 'First Token: '+color.ORANGE+str(tokenx1)) - verbout(C, 'Second Token: '+color.ORANGE+str(tokenx2)) + 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))) # Calculating the edit distance via Damerau Levenshtein algorithm m = stringdist.rdlevenshtein(tokenx1, tokenx2) verbout(color.CYAN, ' [+] Edit Distance Calculated: '+color.GREY+str(m)+'%') @@ -45,7 +49,7 @@ def Analysis(): n = stringdist.rdlevenshtein_norm(tokenx1, tokenx2) verbout(color.CYAN, ' [+] Alignment Ratio Calculated: '+color.GREY+str(n)) # If both tokens are same, then - if tokenx1 == tokenx2: + if len(tokenx1) == len(tokenx2): verbout(C, 'Token length calculated is same: '+color.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)))) @@ -54,41 +58,44 @@ def Analysis(): # is composed of two parts, one of them remains static while the other one dynamic. # # For example, if the Anti CSRF Tokens “837456mzy29jkd911139” for one request, the - # other time “837456mzy29jkd337221”, “837456mzy29jkd” part of the token remains same + # other is “837456mzy29jkd337221”, “837456mzy29jkd” part of the token remains same # in both requests. # # 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)... ') - p = sameSequence(tokenx1, tokenx2) - verbout(C, 'Static Part : '+color.GREY+p+color.END+' | Length: '+str(len(p))) - verbout(O, 'Dynamic Part of Token 0x1: '+color.GREY+tokenx1.replace(p,'')+color.END+' | Length: '+str(len(tokenx1.replace(p,'')))) - verbout(O, 'Dynamic Part of Token 0x2: '+color.GREY+tokenx2.replace(p,'')+color.END+' | Length: '+str(len(tokenx2.replace(p,'')))) + 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.GREEN+ ' [+] Possible CSRF Vulnerability Detected!') + 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+'!') + 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!') - p = sameSequence(tokenx1, tokenx2) - verbout(C, 'Static Part : '+color.GREY+p+color.END+' | Length: '+str(len(p))) - verbout(O, 'Dynamic Part of Token 0x1: '+color.GREY+tokenx1.replace(p,'')+color.END+' | Length: '+str(len(tokenx1.replace(p,'')))) - verbout(O, 'Dynamic Part of Token 0x2: '+color.GREY+tokenx2.replace(p,'')+color.END+' | Length: '+str(len(tokenx2.replace(p,'')))) + 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+'!') + 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) else: verbout(R, 'Token distance calculated is '+color.GREEN+'greater than 0.5!') - p = sameSequence(tokenx1, tokenx2) - verbout(C, 'Static Part : '+color.GREY+p+color.END+' | Length: '+str(len(p))) - verbout(O, 'Dynamic Part of Token 0x1: '+color.GREY+tokenx1.replace(p,'')+color.END+' | Length: '+str(len(tokenx1.replace(p,'')))) - verbout(O, 'Dynamic Part of Token 0x2: '+color.GREY+tokenx2.replace(p,'')+color.END+' | Length: '+str(len(tokenx2.replace(p,'')))) - verbout(color.RED,' [-] Post-Analysis reveals that token might be '+color.BG+' NOT VULNERABLE '+color.END+'!') + 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+'!') + print(color.GREY+' [+] Tokens '+color.GREEN+'Cannot be Forged by Bruteforcing/Guessing'+color.END+'!\n') + NovulLogger('Analysis', 'Tokens cannot be Forged by Bruteforcing/Guessing.') time.sleep(1) except KeyboardInterrupt: continue diff --git a/modules/Checkpost.py b/modules/Checkpost.py index 76898ac..c561f36 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -5,7 +5,7 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -15,42 +15,73 @@ from core.colors import * from core.verbout import verbout from urllib.parse import urlencode +from core.logger import VulnLogger +from files.config import POC_GENERATION, GEN_MALICIOUS +from modules.Generator import GenNormalPoC, GenMalicious -def PostBased(url, r1, r2, r3, m_action, result, m_name=''): - 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 +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 result12 = [] # an init + verbout(O, 'Matching results...') for n in checkdiffx1: - if re.match('\+|-',n): # get regex matching stuff + if re.match('\+|-', 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('\+|-', n): # get regex matching stuff result13.append(n) # append to existing list - # This logic is based purely on the assumption on the difference of requests and - # response body. + # Make sure m_action has a / before it. (legitimate action). + if not m_action.startswith('/'): + m_action = '/' + m_action + + # This logic is based purely on the assumption on the difference of various requests + # and response body. # If the number of differences of result12 are less than the number of differences # than result13 then we have the vulnerability. (very basic check) # - # NOTE: The alogrithm has lots of scopes of improvements - if len(result12)<=len(result13): + # 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') time.sleep(0.3) - verbout(O,'PoC of response and request...') + verbout(O, 'PoC of response and request...') if m_name: - print(color.RED+'\n +---------+') - print(color.RED+' | PoC |') - print(color.RED+' +---------+\n') + 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 - print(color.GREEN+' [+] Action : ' +color.END+m_action) # action + if m_action.count('/') > 1: + print(color.GREEN+' [+] Action : ' +color.END+'/'+m_action.rsplit('/', 1)[1]) # action + else: + print(color.GREEN+' [+] Action : ' +color.END+m_action) # action else: # if value m['name'] not there :( - print(color.RED+'\n +---------+') - print(color.RED+' | PoC |') - print(color.RED+' +---------+\n') + print(color.RED+'\n +-----------------+') + print(color.RED+' | Request PoC |') + print(color.RED+' +-----------------+\n') print(color.BLUE+' [+] URL : ' +color.CYAN+url) # the url - print(color.GREEN+' [+] Action : ' +color.END+ m_action) # action - - print(color.ORANGE+' [+] Query : '+color.GREY+ urlencode(result).strip()) - print('') # print out the params + url + if m_action.count('/') > 1: + print(color.GREEN+' [+] Action : ' +color.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()) + # If option --skip-poc hasn't been supplied... + if POC_GENERATION: + # If --malicious has been supplied + if GEN_MALICIOUS: + # Generates a malicious CSRF form + GenMalicious(url, genpoc.__str__()) + else: + # Generates a normal PoC + GenNormalPoC(url, genpoc.__str__()) diff --git a/modules/Cookie.py b/modules/Cookie.py index 0ef4c6d..9aac5a3 100644 --- a/modules/Cookie.py +++ b/modules/Cookie.py @@ -5,7 +5,7 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -17,32 +17,40 @@ from core.request import Get from core.randua import RandomAgent from .Persistence import Persistence +from core.logger import VulnLogger, NovulLogger from urllib.parse import urlencode, unquote, urlsplit resps = [] -def Cookie(url): +def Cookie(url, request): ''' This module is for checking the varied HTTP Cookies and the related security on them to prevent CSRF attacks. ''' - print(color.GREY+' [+] Proceeding for cookie based checks...') + verbout(GR, 'Proceeding for cookie based checks...') SameSite(url) - Persistence(url) + Persistence(url, request) def SameSite(url): ''' This function parses and verifies the cookies with SameSite Flags. ''' - foundx1, foundx2, foundx3 = 0x00, 0x00, 0x00 + verbout(color.RED, '\n +------------------------------------+') + verbout(color.RED, ' | Cross Origin Cookie Validation |') + verbout(color.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...') gen_headers = HEADER_VALUES gen_headers['User-Agent'] = USER_AGENT or RandomAgent() verbout(GR,'Setting Referer header same as host...') + # Setting the netloc as the referer for the first check. gen_headers['Referer'] = urlsplit(url).netloc if COOKIE_VALUE: for cook in COOKIE_VALUE: @@ -75,7 +83,8 @@ def SameSite(url): verbout(color.GREY,' [+] Lets examine how server reacts to a fake external referer...') gen_headers = HEADER_VALUES gen_headers['User-Agent'] = USER_AGENT or RandomAgent() # Setting user-agents - gen_headers['Referer'] = REFERER_URL # Assigning a fake referer + # Assigning a fake referer for the second check, but no cookie. + gen_headers['Referer'] = REFERER_URL getreq = Get(url, headers=gen_headers) head = getreq.headers # Getting headers from requests for h in head: @@ -108,6 +117,7 @@ def SameSite(url): verbout(color.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() + # Assigning a fake referer for third request, this time with cookie ;) gen_headers['Referer'] = REFERER_URL if COOKIE_VALUE: for cook in COOKIE_VALUE: @@ -123,8 +133,8 @@ def SameSite(url): m = cookieval.split(';') verbout(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.lower(), I): + verbout(G,'SameSite Flag '+color.ORANGE+' detected on cookie on Cross Origin Request!') foundx3 = 0x01 q = q.split('=')[1].strip() verbout(C, 'Cookie: '+color.ORANGE+q) @@ -133,16 +143,25 @@ def SameSite(url): foundx3 = 0x02 if foundx1 == 0x01: - verbout(R,'Endpoint '+color.ORANGE+'SameSite Flag Cookie Validation'+color.END+' Present!') + verbout(R,'Endpoint '+color.ORANGE+'SameSite Flag Cookie Validation'+color.END+' is Present!') if (foundx1 == 0x01 and foundx3 == 0x00) and (foundx2 == 0x00 or foundx2 == 0x01): - print(color.GREEN+' [+] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to ANY type of CSRF attacks!') + 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.') + # 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') 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.') else: - verbout(R,'Endpoint '+color.ORANGE+'SameSite Flag Cookie Validation'+color.END+' Not Present!') + 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.GREEN+ ' [+] Possible CSRF Vulnerability Detected : '+color.ORANGE+url+'!') - print(color.ORANGE+' [!] Possible Vulnerability Type: '+color.BY+' No SameSite Flag on Cookies '+color.END) + 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)) diff --git a/modules/Crawler.py b/modules/Crawler.py index 534af57..882f928 100644 --- a/modules/Crawler.py +++ b/modules/Crawler.py @@ -5,7 +5,7 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -#Author: @_tID +#Author: 0xInfection #This module requires XSRFProbe #https://github.com/0xInfection/XSRFProbe @@ -18,6 +18,7 @@ from bs4 import BeautifulSoup from core.request import Get from core.verbout import verbout +from core.logger import ErrorLogger from files.discovered import INTERNAL_URLS class Handler(): # Main Crawler Handler @@ -50,7 +51,7 @@ def noinit(self): return True # +1 return False # -1 - def addToVisit(self,Parser): + def addToVisit(self, Parser): self.toVisit.append(Parser) # Add what we have got def process(self, root): @@ -69,35 +70,36 @@ def process(self, root): 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__()) + verbout(R, 'HTTP Request Error: '+msg.__str__()) + ErrorLogger(url, msg.__str__()) if url in self.toVisit: self.toVisit.remove(url) # Remove non-existent / errored urls - return + return None # 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']): - return + return None # Just in case there is a redirection, we are supposed to follow it :D - verbout(GR,'Making request to new location...') + verbout(GR, 'Making request to new location...') if hasattr(query.headers, 'Location'): url = query.headers['Location'] verbout(O,'Reading response...') response = query.content # Read the response contents try: - verbout(O,'Trying to parse response...') + verbout(O, 'Trying to parse response...') soup = BeautifulSoup(response) # Parser init except HTMLParser.HTMLParseError: - verbout(R,'BeautifulSoup Error: '+url) + verbout(R, 'BeautifulSoup Error: '+url) self.visited.append(url) if url in self.toVisit: self.toVisit.remove(url) - return + return None - for m in soup.findAll('a',href=True): # find out all href^?://* + 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']): @@ -106,17 +108,17 @@ def process(self, root): # If we get a valid link if app!='' and re.search(root, app): # Getting rid of Urls starting with '../../../..' - while re.search(RID_DOUBLE,app): + while re.search(RID_DOUBLE, app): p = re.compile(RID_COMPILE) - app = p.sub('/',app) + app = p.sub('/', app) # Getting rid of Urls starting with './' p = re.compile(RID_SINGLE) - app = p.sub('',app) + app = p.sub('', app) # Add new link to the queue only if its pattern has not been added yet uriPattern=removeIDs(app) # remove IDs if self.notExist(uriPattern) and app != url: - verbout(G,'Added :> ' +color.BLUE+ app) # display what we have got! + verbout(G, 'Added :> ' +color.BLUE+ app) # display what we have got! self.toVisit.append(app) # add up urls to visit self.uriPatterns.append(uriPattern) diff --git a/modules/Debugger.py b/modules/Debugger.py index 444f7f6..2927acc 100644 --- a/modules/Debugger.py +++ b/modules/Debugger.py @@ -5,7 +5,7 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -20,76 +20,117 @@ class Form_Debugger(): def prepareFormInputs(self, form): ''' - This method parses specific form types and generates tokens based + This method parses form types and generates strings based on their input types. ''' verbout(O,'Crafting inputs as form type...') - input = {} + cr_input = {} + totcr = [] - verbout(O,'Processing '+color.BOLD+' max_length: - print(color.GREEN+' [+] CSRF Token Length greater than 256 bytes. '+color.ORANGE+'Token value cannot be guessed/bruteforced...') - print(color.ORANGE+' [+] Endpoint likely '+color.BG+' NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') - print(color.ORANGE+' [!] CSRF Mitigation Method: '+color.BG+' Long Anti-CSRF Tokens '+color.END) - - # Calculate entropy - verbout(O, 'Proceeding to calculate '+color.GREY+'Shannon Entropy'+color.END+' of Token audited...') - entropy = calcEntropy(value) - verbout(GR, 'Calculating Entropy...') - verbout(color.BLUE, ' [+] Entropy Calculated: '+color.CYAN+str(entropy)) - if entropy >= min_entropy: - verbout(color.GREEN,' [+] Anti-CSRF Token Entropy Calculated is '+color.BY+'GREATER than 2.4'+color.END+'... ') - print(color.ORANGE+' [+] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') - print(color.ORANGE+' [!] CSRF Mitigation Method: '+color.BG+' High Entropy Anti-CSRF Tokens '+color.END) - else: - verbout(color.RED,' [-] Anti-CSRF Token Entropy Calculated is '+color.BY+'LESS than 2.4'+color.END+'... ') - print(color.ORANGE+' [-] Endpoint likely '+color.BR+' VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks inspite of CSRF Tokens...') - print(color.ORANGE+' [!] Vulnerability Type: '+color.BR+' Low Entropy Anti-CSRF Tokens '+color.END) + verbout(color.RED, '\n +------------------------------+') + verbout(color.RED, ' | Token Strength Detection |') + verbout(color.RED, ' +------------------------------+\n') + for para in REQUEST_TOKENS: + # Coverting the token to a raw string, cause some special + # chars might fu*k with the Shannon Entropy operation. + value = r'%s' % para + verbout(color.CYAN, ' [!] Testing Anti-CSRF Token: '+color.ORANGE+'%s' % (value)) + # Check length + if len(value) <= min_length: + print(color.RED+' [-] CSRF Token Length less than 5 bytes. '+color.ORANGE+'Token value can be guessed/bruteforced...') + print(color.ORANGE+' [-] Endpoint likely '+color.BR+' VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') + print(color.RED+' [!] Vulnerability Type: '+color.BR+' Very Short/No Anti-CSRF Tokens '+color.END) + VulnLogger(url, 'Very Short Anti-CSRF Tokens.', 'Token: '+value) + if len(value) >= 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.') + found = 0x01 + # Checking entropy + verbout(O, 'Proceeding to calculate '+color.GREY+'Shannon Entropy'+color.END+' of Token audited...') + entropy = calcEntropy(value) + verbout(GR, 'Calculating Entropy...') + verbout(color.BLUE, ' [+] Entropy Calculated: '+color.CYAN+str(entropy)) + if entropy >= min_entropy: + verbout(color.ORANGE,' [+] Anti-CSRF Token Entropy Calculated is '+color.BY+' GREATER than 2.4 '+color.END+'... ') + print(color.GREEN+' [+] Endpoint '+color.BG+' PROBABLY NOT VULNERABLE '+color.END+color.GREEN+' to CSRF Attacks...') + print(color.GREEN+' [!] CSRF Mitigation Method: '+color.BG+' High Entropy Anti-CSRF Tokens '+color.END) + NovulLogger(url, 'High Entropy Anti-CSRF Tokens.') + found = 0x01 + else: + verbout(color.RED,' [-] Anti-CSRF Token Entropy Calculated is '+color.BY+' LESS than 2.4 '+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) + if found == 0x00: if m_name: print(color.RED+'\n +---------+') print(color.RED+' | PoC |') @@ -92,17 +107,17 @@ def Entropy(req, url, m_action, m_name=''): # Print out the params print(color.ORANGE+' [+] Query : '+color.GREY+urllib.parse.urlencode(result)) print('') - return _q, para # Return the query paramter and anti-csrf token + return (_q, para) # Return the query paramter and anti-csrf token def calcEntropy(data): """ This function is used to calculate - Entropy. + Shannon Entropy. """ if not data: return 0 - entropy = 0 # init + entropy = 0 # init for x in range(256): p_x = float(data.count(chr(x)))/len(data) diff --git a/modules/Generator.py b/modules/Generator.py new file mode 100644 index 0000000..9cb3b8c --- /dev/null +++ b/modules/Generator.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +#-:-:-:-:-:-:-::-:-:# +# XSRF Probe # +#-:-:-:-:-:-:-::-:-:# + +# Author: 0xInfection +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe + +from core.colors import * +from ast import literal_eval +from bs4 import BeautifulSoup +from yattag import Doc, indent +from core.verbout import verbout +from files.config import OUTPUT_DIR +from core.prettify import formPrettify +from core.prettify import indentPrettify + +doc, tag, text = Doc().tagtext() + +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) + # 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') + # Try not messing with this part. (1) + 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']) + # Adding the Submit Button + doc.stag('input', value='Submit', type='submit') + doc.stag('br') + # Brand tag :p ...I guess... + with tag('small'): + text('(i) 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('') + # Write out the file af... + fi = open(OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-csrf-poc.html', 'w+', encoding='utf8') + fi.write(content.prettify()) + fi.close() + print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-csrf-poc.html') + +def GenMalicious(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): + """ + 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) + # Main starting which we will use to generate form. + 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"): + 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) + # 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'): + # 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') + formPrettify(indentPrettify(content)) + print('') + # Write out the file af... + fi = open(OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-malicious-poc.html', 'w+', encoding='utf8') + fi.write(content.prettify()) + fi.close() + print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-malicious-poc.html') diff --git a/modules/Origin.py b/modules/Origin.py index 456f56e..b814599 100644 --- a/modules/Origin.py +++ b/modules/Origin.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -15,13 +15,16 @@ from core.verbout import verbout from core.request import Get from core.randua import RandomAgent +from 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') # Make the request normally and get content verbout(O,'Making request on normal basis...') req0x01 = Get(url) @@ -38,7 +41,7 @@ def Origin(url): gen_headers['Cookie'] = cookie # Make the request with different Origin header and get the content - verbout(O,'Making request with tampered headers...') + verbout(O,'Making request with '+color.CYAN+'Tampered Origin Header'+color.END+'...') req0x02 = Get(url, headers=gen_headers) # Comparing the length of the requests' responses. If both content @@ -57,11 +60,13 @@ def Origin(url): 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) + print(color.ORANGE+' [+] Mitigation Method: '+color.BG+' Origin Based Request Validation '+color.END+'\n') + NovulLogger(url, 'Presence of Origin Header based request Validation.') return True else: - verbout(R,'Endpoint '+color.ORANGE+'Origin Validation Not Present'+color.END+'!') + 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+' Origin Based Request Forgery '+color.END) + 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 diff --git a/modules/Parser.py b/modules/Parser.py index db7ac36..8262828 100644 --- a/modules/Parser.py +++ b/modules/Parser.py @@ -5,7 +5,7 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -#Author: @_tID +#Author: 0xInfection #This module requires XSRFProbe #https://github.com/0xInfection/XSRFProbe @@ -61,6 +61,6 @@ def buildAction(url, action): on Current Location and Destination. ''' verbout(O,'Parsing URL parameters...') - if action and not re.match('#', action): # make sure it is not a fragment (eg. http://site.tld/index.php#search) + 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/modules/Persistence.py b/modules/Persistence.py index eea8b98..a1acd3d 100644 --- a/modules/Persistence.py +++ b/modules/Persistence.py @@ -5,7 +5,7 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -16,42 +16,113 @@ from core.verbout import verbout from core.request import Get from core.randua import RandomAgent +from datetime import datetime from core.utils import checkDuplicates +from core.logger import VulnLogger, NovulLogger from urllib.parse import urlencode, unquote, urlsplit - +# Response storing list init resps = [] -def Persistence(url): +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') + # Checking if user has supplied a value. + verbout(GR,'Proceeding to test for '+color.GREY+'Cookie Persistence'+color.END+'...') + time.sleep(0.7) + found = 0x00 + # Now let the real test begin... + # + # [Step 1]: Lets examine now whether cookies set by server are persistent or not. + # For this we'll have to parse the cookies set by the server and check for the + # 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+'...') + gen_headers = HEADER_VALUES + gen_headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36' if COOKIE_VALUE: - verbout(GR,'Proceeding to test for '+color.GREY+'Cookie Persistence'+color.END+'...') - time.sleep(0.7) + for cookie in COOKIE_VALUE: + gen_headers['Cookie'] = cookie + verbout(GR,'Making the request...') + req = Get(url, headers=gen_headers) + 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 + # convert it a human readable format. + print(color.GREEN+' [+] Cookie Expiry Period: '+color.ORANGE+datetime.fromtimestamp(cook.expires).__str__()) + found = 0x01 + VulnLogger(url, 'Persistent Session Cookies Found.', '[i] Cookie: '+req.headers.get('Set-Cookie')) + else: + 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+'...') + # Now its time for POST Based requests. + # + # NOTE: As a standard method, every web application should supply a cookie upon a POST query. + # It might or might not be in case of GET requests. + 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__()) + # 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__()) + found = 0x01 + VulnLogger(url, 'Persistent Session Cookies Found.', '[i] Cookie: '+req.headers.get('Set-Cookie')) + print(color.ORANGE+' [!] Probable Insecure Practice: '+color.BR+' Persistent Session Cookies '+color.END) + else: + NovulLogger(url, 'No Persistent Cookies.') + if found == 0x00: + verbout(R, 'No persistent session cookies identified upon POST Requests!') + print(color.GREEN+' [+] Endpoint might be '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to CSRF attacks!') + print(color.GREEN+' [+] Detected : '+color.BG+' No Persistent Cookies '+color.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 + # well used and common user-agents (as below) and then we observe the variation of set-cookie + # header under different conditions. + # + # 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+'...') 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...') 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 - for cookie in COOKIE_VALUE: - gen_headers['Cookie'] = cookie - verbout(GR,'Making the request...') + if COOKIE_VALUE: + for cookie in COOKIE_VALUE: + gen_headers['Cookie'] = cookie req = Get(url, headers=gen_headers) resps.append(req.headers.get('Set-Cookie')) if checkDuplicates(resps): - verbout(G,'Set-Cookie header does not change with varied User-Agents...') - verbout(R,'Possible persistent session cookies found...') + verbout(G, 'Set-Cookie header does not change with varied User-Agents...') + verbout(color.GREEN, ' [+] Possible persistent session cookies found...') print(color.RED+ ' [+] Possible CSRF Vulnerability Detected : '+color.ORANGE+url+'!') - print(color.ORANGE+' [!] Possible Vulnerability Type: '+color.BR+' Persistent Session Cookies '+color.END) + print(color.ORANGE+' [!] Probable Insecure Practice: '+color.BR+' Persistent Session Cookies '+color.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...') - print(color.GREEN+' [+] Endpoint might be '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to CSRF attacks!') - print(color.GREEN+' [+] Protection Method Detected : '+color.BG+' No Persistent Cookies '+color.END) - else: - verbout(R,'Skipping persistence checks as no cookie value supplied...') + print(color.GREEN+' [+] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to CSRF attacks!') + print(color.GREEN+' [+] Application Practice Method Detected : '+color.BG+' No Persistent Cookies '+color.END) + NovulLogger(url, 'No Persistent Cookies.') diff --git a/modules/Referer.py b/modules/Referer.py index faa5a90..0c1a86a 100644 --- a/modules/Referer.py +++ b/modules/Referer.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -14,13 +14,16 @@ from files.config import * from core.verbout import verbout from core.request import Get +from 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') # Make the request normally and get content verbout(O,'Making request on normal basis...') req0x01 = Get(url) @@ -31,7 +34,6 @@ def Referer(url): # Set a fake Referer along with UA (pretending to be a # legitimate request from a browser) - verbout(GR,'Setting generic headers...') gen_headers['Referer'] = REFERER_URL # We put the cookie in request, if cookie supplied :D @@ -40,7 +42,7 @@ def Referer(url): gen_headers['Cookie'] = cookie # Make the request with different referer header and get the content - verbout(O,'Making request with tampered headers...') + verbout(O,'Making request with '+color.CYAN+'Tampered Referer Header'+color.END+'...') req0x02 = Get(url, headers=gen_headers) # Comparing the length of the requests' responses. If both content @@ -60,10 +62,12 @@ def Referer(url): 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.') return True else: - verbout(R,'Endpoint '+color.ORANGE+'Referer Validation Not Present'+color.END+'!') + 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+' Referer Based Request Forgery '+color.END) + 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 diff --git a/modules/Tamper.py b/modules/Tamper.py index 74a4f16..82f8104 100644 --- a/modules/Tamper.py +++ b/modules/Tamper.py @@ -5,7 +5,7 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe @@ -15,7 +15,9 @@ from files.config import * from core.verbout import verbout from core.utils import replaceStrIndex +from files.paramlist import TOKEN_ERRORS from urllib.parse import urlencode, quote +from core.logger import VulnLogger, NovulLogger def Tamper(url, action, req, body, query, para): ''' @@ -23,6 +25,9 @@ def Tamper(url, action, req, body, query, para): 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') # Null char flags (hex) flagx1 = 0x00 flagx2 = 0x00 @@ -32,12 +37,13 @@ def Tamper(url, action, req, body, query, para): if para == '': return True # Coverting the token to a raw string, cause some special - # chars might fu*k with the Shannon Entropy operation. + # chars might fu*k with the operation. value = r'%s' % para + copy = req # Alright lets start... - # [Step 1]: First we take the token and then replace a char - # at a specific position and test the response body. + # [Step 1]: First we take the token and then replace a particular character + # at a specific position (here at 4th position) and test the response body. # # Required check for checking if string at that position isn't the # same char we are going to replace with. @@ -60,12 +66,16 @@ 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('50'): - verbout(color.RED,' [+] Token tamper from request causes a 50x Internal Error!') - if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): + if ((str(resp.status_code).startswith('2') and not any(search(s, resp.text, I) for s in TOKEN_ERRORS)) + or (len(body) == len(resp.text))): + verbout(color.RED,' [-] Anti-CSRF Token tamper by index replacement returns valid response!') flagx1 = 0x01 + 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.') - # [Step 2]: Second we take the token and then remove a char + # [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+'...') tampvalx2 = replaceStrIndex(value, 3) @@ -80,36 +90,49 @@ 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('50'): - verbout(color.RED,' [+] Token tamper from request causes a 50x Internal Error!') - if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): + if ((str(resp.status_code).startswith('2') and not any(search(s, resp.text, I) for s in TOKEN_ERRORS)) + or (len(body) == len(resp.text))): + verbout(color.RED,' [-] Anti-CSRF Token tamper by index removal returns valid response!') flagx2 = 0x01 + 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.') # [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+'...') + # Removing the anti-csrf token from request del req[query] - verbout(G, 'Removed token from request!') + verbout(color.GREY, ' [+] Removed token parameter from request!') # Lets build up the request... resp = Post(url, action, req) # If there is a 40x (Not Found) or a 50x (Internal Error) error, - # we assume that the tamper did not work :( But if there is a 20x + # we assume that the tamper did not work :(. But if there is a 20x # (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('50'): - verbout(color.RED,' [+] Token removal from request causes a 50x Internal Error!') - if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): + if ((str(resp.status_code).startswith('2') and not any(search(s, resp.text, I) for s in TOKEN_ERRORS)) + or (len(body) == len(resp.text))): + verbout(color.RED,' [-] Anti-CSRF Token removal returns valid response!') flagx3 = 0x01 + 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.') # If any of the forgeries worked... - if flagx1 == 0x01 or flagx2 == 0x01 or flagx3 == 0x01: - verbout(color.GREEN,' [+] The tampered token value works!') - verbout(color.GREEN,' [-] The Tampered Anti-CSRF Token requested does NOT return a 40x or 50x response! ') - print(color.ORANGE+' [-] Endpoint '+color.BR+' CONFIRMED VULNERABLE '+color.END+color.ORANGE+' to Request Forgery Attacks...') - print(color.ORANGE+' [!] Vulnerability Type: '+color.BG+' Non-Unique Anti-CSRF Tokens in Requests '+color.END) + if (flagx1 or flagx2 or 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)) + return True else: print(color.RED+' [-] The Tampered Anti-CSRF Token requested returns a 40x or 50x response... ') - print(color.ORANGE+' [-] 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) + 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.') + return False diff --git a/modules/Token.py b/modules/Token.py index 3e25d90..5f4c344 100644 --- a/modules/Token.py +++ b/modules/Token.py @@ -5,28 +5,32 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe +from files import config from re import search, I from time import sleep -from files.config import * from core.colors import * from core.verbout import verbout from files.discovered import REQUEST_TOKENS from urllib.parse import urlencode, unquote -from files.paramlist import COMMON_CSRF_NAMES +from files.paramlist import COMMON_CSRF_NAMES, COMMON_CSRF_HEADERS -def Token(req): +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 = '' + found = False # First lets have a look at config.py and see if its set - if TOKEN_CHECKS: + if config.TOKEN_CHECKS: verbout(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 @@ -34,19 +38,32 @@ def Token(req): for c in con: for name in COMMON_CSRF_NAMES: # Iterate over the list qu = c.split('=') - if name.lower() in qu[0].lower(): # Search if the token is there in request... - verbout(color.GREEN,' [+] The form was requested with a '+color.ORANGE+'Anti-CSRF Token'+color.GREEN+'...') - verbout(color.GREY,' [+] Token Parameter: '+color.CYAN+qu[0]+'='+qu[1]+' ...') + # 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]) query, param = qu[0], qu[1] - REQUEST_TOKENS.append(param) # We are appending the token to a variable for further analysis + # We are appending the token to a variable for further analysis + REQUEST_TOKENS.append(param) + found = True break # Break execution if a Anti-CSRF token is found - + # If we haven't found the Anti-CSRF token in query, we'll search for it in headers :) + if not found: + for key, value in headers.items(): + 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]) + query, param = key, value + # We are appending the token to a variable for further analysis + REQUEST_TOKENS.append(param) + break # Break execution if a Anti-CSRF token is found except Exception as e: - verbout(R,'Request Parsing Execption!') - verbout(R,'Error: '+e.__str__()) - + verbout(R, 'Request Parsing Exception!') + verbout(R, 'Error: '+e.__str__()) if param: - return query, param - verbout(color.RED,' [-] The form was requested '+color.BR+' Without an Anti-CSRF Token '+color.END+color.RED+'...') + 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) - return '', '' + return (None, None) diff --git a/modules/__init__.py b/modules/__init__.py index 793ff4b..310ac79 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection #This module requires XSRFProbe #https://github.com/0xInfection/XSRFProbe diff --git a/output/example.com/README b/output/example.com/README new file mode 100644 index 0000000..b0833df --- /dev/null +++ b/output/example.com/README @@ -0,0 +1,3 @@ +This is just a sample folder where all stuffs and output will be stored. + +DO NOT DELETE THIS FOLDER!!! diff --git a/output/example.com/errored.log b/output/example.com/errored.log new file mode 100644 index 0000000..3afa5f6 --- /dev/null +++ b/output/example.com/errored.log @@ -0,0 +1,2 @@ +(i) http://example.com/csrf -> No standard form "action". + diff --git a/output/example.com/example.com-csrf-poc.html b/output/example.com/example.com-csrf-poc.html new file mode 100644 index 0000000..0ffbfac --- /dev/null +++ b/output/example.com/example.com-csrf-poc.html @@ -0,0 +1,25 @@ + + + CSRF PoC + + +

+ Your CSRF PoC +

+
+ + + +
+
+ + (i) This form was generated by + + XSRFProbe + + . + + + diff --git a/output/example.com/forms-tested.log b/output/example.com/forms-tested.log new file mode 100644 index 0000000..d774dd2 --- /dev/null +++ b/output/example.com/forms-tested.log @@ -0,0 +1,7 @@ +(i) http://example.com/csrf: + +
+ + +
+ diff --git a/output/example.com/strengths.log b/output/example.com/strengths.log new file mode 100644 index 0000000..1418084 --- /dev/null +++ b/output/example.com/strengths.log @@ -0,0 +1,2 @@ +[+] http://example.com/csrf -> No Persistent Session Cookies. +[+] http://example.com/csrf -> Anti-CSRF token is not a string encoded value. diff --git a/output/example.com/vulnerabilities.log b/output/example.com/vulnerabilities.log new file mode 100644 index 0000000..ade88b3 --- /dev/null +++ b/output/example.com/vulnerabilities.log @@ -0,0 +1,34 @@ +[!] http://example.com/csrf -> No Referer Header based Request Validation presence. + +[i] Response Headers: {'Date': 'Fri, 28 Dec 2018 07:07:43 GMT', 'Set-Cookie': 'TEST_SESSIONID=jhf8avl5; path=/, NB_SRVID=srv140700; path=/', 'Expires': 'Thu, 19 Nov 1981 08:52:00 GMT', 'Cache-Control': 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', 'Pragma': 'no-cache', 'Vary': 'Accept-Encoding', 'Content-Encoding': 'gzip', 'Content-Length': '663', 'Connection': 'close', 'Content-Type': 'text/html'} + + +[!] http://example.com/csrf -> No Origin Header based request validation presence. + +[i] Response Headers: {'Date': 'Fri, 28 Dec 2018 07:07:45 GMT', 'Set-Cookie': 'TEST_SESSIONID=1cgnr23oqil463c6; path=/, NB_SRVID=srv140717; path=/', 'Expires': 'Thu, 19 Nov 1981 08:52:00 GMT', 'Cache-Control': 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', 'Pragma': 'no-cache', 'Vary': 'Accept-Encoding', 'Content-Encoding': 'gzip', 'Content-Length': '663', 'Connection': 'close', 'Content-Type': 'text/html'} + + +[!] http://example.com/csrf -> No Cookie Validation on Cross-Origin Requests. + +[i] Headers: {'Date': 'Fri, 28 Dec 2018 07:07:50 GMT','Set-Cookie': 'TEST_SESSIONID=3f9054b0g2; path=/, NB_SRVID=srv140717; path=/', 'Expires': 'Thu, 19 Nov 1981 08:52:00 GMT', 'Cache-Control': 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', 'Pragma': 'no-cache', 'Vary': 'Accept-Encoding', 'Content-Encoding': 'gzip', 'Content-Length': '663', 'Connection': 'close', 'Content-Type': 'text/html'} + + +[!] http://example.com/csrf -> Form Requested Without Anti-CSRF Token. + +[i] Form Requested:
+ + +
+ +[i] Request Query: {'csrf_it': 'PBgCWG'} + + +[!] http://example.com/csrf -> POST-Based Request Forgery on Forms. + +[i] Form:
+ + +
+ +[i] POST Query: {'csrf_it': 'PBgCWG'} + diff --git a/requirements.txt b/requirements.txt index 4592b91..73bc67f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ requests bs4 -lxml request stringdist +tld +yattag diff --git a/xsrfprobe.py b/xsrfprobe.py index 939e5c5..092d4a1 100644 --- a/xsrfprobe.py +++ b/xsrfprobe.py @@ -5,9 +5,9 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -from core import main # import all stuff -main.Engine() # the true start of the program ;) +from core import main # import stuff +main.Engine() # start the Scanner Engine ;)