From 015f7e555ffc08980c9a80001bf3a57ae79e4f94 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:01:18 +0000 Subject: [PATCH 01/94] Added inputin modifications to ease setup --- core/inputin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/inputin.py b/core/inputin.py index 3436fea..8336c28 100644 --- a/core/inputin.py +++ b/core/inputin.py @@ -11,6 +11,7 @@ import sys import socket +from tld import get_fld from core.colors import * from files.config import SITE_URL @@ -22,7 +23,7 @@ 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...') socket.gethostbyname(web0) # test whether site is up or not @@ -30,7 +31,7 @@ def inputin(): 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 + + if web.split('//')[1] == web0: + return web, '' + return web, web0 From 71dc0c9cceca3cb37fa7a8a3d9976e0db3a91ea2 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:01:59 +0000 Subject: [PATCH 02/94] Added major changes to suit new infrastructure --- core/main.py | 253 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 165 insertions(+), 88 deletions(-) diff --git a/core/main.py b/core/main.py index 69794e4..5551640 100644 --- a/core/main.py +++ b/core/main.py @@ -63,7 +63,7 @@ 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 + web, fld = inputin() # Take the input form1 = form10() # Get the form 1 ready form2 = form20() # Get the form 2 ready @@ -76,109 +76,186 @@ def Engine(): # lets begin it! 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 + 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 + init1 = web # First init + resp1.open(init1) # Makes request as User2 + resp2.open(init1) # Make request as User1 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 - + 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 + 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 + verbout(O,'Using form:\n'+color.CYAN+' %s' % (m)) + try: + if m['action']: + pass + except KeyError: + m['action'] = '/' + url.rsplit('/', 1)[1] + 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: + result, genpoc = form.prepareFormInputs(m) # prepare inputs + r1 = Post(url, action, result).text # make request with token values generated as user1 + result, genpoc = 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... 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... + if m['name']: + query, token = Entropy(result, url, m['action'], 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 + 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, genpoc = 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: try: if m['name']: - query, token = Entropy(result, url, m['action'], m['name']) + PostBased(url, r1, r2, r3, m['action'], result, genpoc, 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 - 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: - try: - if m['name']: - PostBased(url, r1, r2, r3, m['action'], result, m['name']) - except KeyError: - PostBased(url, r1, r2, r3, m['action'], result) + PostBased(url, r1, r2, r3, m['action'], result, genpoc) + 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!') + except HTTPError as msg: # if runtime exception... + verbout(R, 'Exception : '+msg.__str__()) # again exception :( - 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 - actionDone.append(action) # add the stuff done - i+=1 # Increase user iteration + else: + verbout(GR, "Initializing crawling and scanning...") + crawler = Crawler.Handler(init1, resp1) # Init to the Crawler handler - 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 + try: + while crawler.noinit(): # Until 0 urls left + url = next(crawler) # Go for next! - print('\n'+G+"Scan completed!"+'\n') - Analysis() # For Post Scan Analysis + 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 - # 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() + 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 + try: + if m['action']: + pass + except KeyError: + m['action'] = '/' + url.rsplit('/', 1)[1] + 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: + result, genpoc = form.prepareFormInputs(m) # prepare inputs + r1 = Post(url, action, result).text # make request with token values generated as user1 + result, genpoc = 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... + try: + if m['name']: + query, token = Entropy(result, url, m['action'], 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 + 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, genpoc = 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: + try: + if m['name']: + PostBased(url, r1, r2, r3, m['action'], result, genpoc, m['name']) + except KeyError: + PostBased(url, r1, r2, r3, m['action'], result, genpoc) + 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!') + except HTTPError as msg: # if runtime exception... + verbout(color.RED, ' [-] Exception : '+color.END+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 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)) + quit() + + print('\n'+G+"Scan completed!"+'\n') + Analysis() # For Post Scan Analysis except KeyboardInterrupt: # incase user wants to exit ;-; (while crawling) verbout(R, 'User Interrupt!') time.sleep(1.5) From 22db3d48b1a2710d71c2a83d1b23138e559d6203 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:02:38 +0000 Subject: [PATCH 03/94] Added new option --crawl for crawling --- core/options.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/options.py b/core/options.py index d02d253..e8eb559 100644 --- a/core/options.py +++ b/core/options.py @@ -40,9 +40,10 @@ 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('--max-chars', help='Maximum allowed character length for the custom token value to be generated. For example: `--max-chars 5`. Default value is 6.', dest='maxchars', type=int) +optional.add_argument('--crawl', help="Crawl the whole site and simultaneously test all discovered endpoints for CSRF.", dest='crawl', action='store_true') optional.add_argument('--skip-analysis', help='Skip the Post-Scan Analysis of Tokens which were gathered during requests', dest='skipal', 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() @@ -83,7 +84,11 @@ 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 if args.cookie: # Assigning Cookie From 6e1ef79aacd78c3486c2cbfc91cf099363ac5c8a Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:02:58 +0000 Subject: [PATCH 04/94] Added small modifications to request.py --- core/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/request.py b/core/request.py index 7e6e3bf..a046c43 100644 --- a/core/request.py +++ b/core/request.py @@ -45,7 +45,7 @@ def Post(url, action, data): if DISPLAY_HEADERS: pheaders(response.headers) return response # read data content - except requests.exceptions: # if error + except requests.exceptions.HTTPError: # if error verbout(R, "HTTP Error : "+action) return except ValueError: # again if valuerror From 64be57836be7f15aaf4d5df49c595de899a11621 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:03:52 +0000 Subject: [PATCH 05/94] Added new configuration variables to suite --- files/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/config.py b/files/config.py index c62c43a..a48c384 100644 --- a/files/config.py +++ b/files/config.py @@ -18,6 +18,9 @@ # 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 From b1dcf18924ec9cc58d8605c72aa84a84d000dc8d Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:04:11 +0000 Subject: [PATCH 06/94] Added changes in POST Based module --- modules/Checkpost.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/modules/Checkpost.py b/modules/Checkpost.py index 76898ac..af2837f 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -15,8 +15,14 @@ from core.colors import * from core.verbout import verbout from urllib.parse import urlencode +from modules.Generator import GeneratePoC -def PostBased(url, r1, r2, r3, m_action, result, m_name=''): +def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): + ''' + This method is for detecting POST-Based Request Forgeries + on basis of fuzzy string matching and comparison + based on Radcliff-Obershelp Algorithm. + ''' 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 @@ -32,25 +38,28 @@ def PostBased(url, r1, r2, r3, m_action, result, m_name=''): # 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 + # 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) 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 + print(color.GREEN+' [+] Action : ' +color.END+'/'+m_action.rsplit('/', 1)[1]) # 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.GREEN+' [+] Action : ' +color.END+'/'+m_action.rsplit('/', 1)[1]) # action print(color.ORANGE+' [+] Query : '+color.GREY+ urlencode(result).strip()) - print('') # print out the params + url + print(GR, 'Generating PoC Form...' ) + print(color.RED+'\n +--------------+') + print(color.RED+' | Form PoC |') + print(color.RED+' +--------------+\n'+color.CYAN) + GeneratePoC(url, str(genpoc)) From 8462f0afb7df44436e5dc05214ebbdac3b278d64 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:04:31 +0000 Subject: [PATCH 07/94] Init to Crawler.Handler() output --- modules/Crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/Crawler.py b/modules/Crawler.py index 534af57..1da3a62 100644 --- a/modules/Crawler.py +++ b/modules/Crawler.py @@ -106,7 +106,7 @@ 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) # Getting rid of Urls starting with './' From 3d275907a48148ba3dabf7faf405f720709f94eb Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:04:54 +0000 Subject: [PATCH 08/94] Added Debugger mods for generating PoC --- modules/Debugger.py | 92 ++++++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/modules/Debugger.py b/modules/Debugger.py index 444f7f6..d4578de 100644 --- a/modules/Debugger.py +++ b/modules/Debugger.py @@ -20,76 +20,114 @@ 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+' Date: Tue, 11 Dec 2018 11:05:23 +0000 Subject: [PATCH 09/94] Added Token mods for improving accuracy --- modules/Token.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/Token.py b/modules/Token.py index 3e25d90..bc8da35 100644 --- a/modules/Token.py +++ b/modules/Token.py @@ -34,12 +34,18 @@ 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... + # Search if the token is there in request... + if name.lower() in qu[0].lower(): 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]+' ...') 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) break # Break execution if a Anti-CSRF token is found + # Setting this to False since the form was requested with an Anti-CSRF token, + # there is an unique id for every form. There are other checks token reuse and + # other stuff, but we do not need the POST-Based Check for now, for this form. + config.POST_BASED = False # Clean fix ;) except Exception as e: verbout(R,'Request Parsing Execption!') From 107a52dee8cc7c6672a1eb9665ceab9c1c621544 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:06:00 +0000 Subject: [PATCH 10/94] Added minor changes to suit new modules --- xsrfprobe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xsrfprobe.py b/xsrfprobe.py index 939e5c5..b79c0af 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 Engine ;) From 72cda36863a76c2a684464ebea751f007053a629 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:06:23 +0000 Subject: [PATCH 11/94] Parser.py modded for efficient regex parsing --- modules/Parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/Parser.py b/modules/Parser.py index db7ac36..17b48b6 100644 --- a/modules/Parser.py +++ b/modules/Parser.py @@ -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 From 6a94ff9a96b6e3e3d9dc9ba3876433ed4c59e2bf Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:06:41 +0000 Subject: [PATCH 12/94] Added new module for Generating PoCs --- modules/Generator.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 modules/Generator.py diff --git a/modules/Generator.py b/modules/Generator.py new file mode 100644 index 0000000..2dfe59d --- /dev/null +++ b/modules/Generator.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +#-:-:-:-:-:-:-::-:-:# +# XSRF Probe # +#-:-:-:-:-:-:-::-:-:# + +# Author: 0xInfection +# This module requires XSRFProbe +# https://github.com/0xInfection/XSRFProbe + +from json import dumps +from ast import literal_eval +from bs4 import BeautifulSoup, Tag + +def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): + """ + Generate a CSRF PoC using basic form data + """ + content = BeautifulSoup("", "html.parser") + html_tag = content.find("html") + title_tag = content.new_tag('title') + title_tag.string = 'CSRF PoC' + html_tag.append(title_tag) + head_tag = content.new_tag('h2') + head_tag.string = 'Your CSRF PoC' + html_tag.append(head_tag) + form_tag = content.new_tag("form", action=action, method=method, enctype=encoding_type) + html_tag.append(form_tag) + + for field in literal_eval(fields): + label_tag = content.new_tag('label') + label_tag.string = field['label'] + field_tag = content.new_tag("input", type=field['type'], value=field['value']) + field_tag['name'] = field['name'] + form_tag.append(label_tag) + form_tag.append(field_tag) + + submit_tag = content.new_tag("input", type="submit", value='Submit') + form_tag.append(submit_tag) + br_tag = content.new_tag('br') + html_tag.append(br_tag) + footer_tag = content.new_tag('footer') + html_tag.append(footer_tag) + small_tag = content.new_tag('small') + small_tag.string = '(i) This PoC form was generated by XSRFProbe.' + footer_tag.append(small_tag) + m = content.prettify() + for i in m.splitlines(): + print(' '+i) + + +if __name__ == '__main__': + GeneratePoC('http://www.webscantest.com/csrf/csrfpost.php', "[{ 'type':'text', 'name':'property', 'label':'color', 'value':''}, { 'type':'text', 'name':'bullshit', 'label':'pun', 'value':''}]") +# python Generator.py "POST" "application/x-www-form-urlencoded" "http://webscantest.com/csrf/csrfpost.php" "[{ 'type':'text', 'name':'property', 'label':'color', 'value':''}, { 'type':'text', 'name':'bullshit', 'label':'pun', 'value':''}]" + From 68fb94bebb9a10e4a9ae86ead79ae5b960df925e Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 11 Dec 2018 11:07:06 +0000 Subject: [PATCH 13/94] Added small changes in Entropy.py for better error handling --- modules/Entropy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/Entropy.py b/modules/Entropy.py index 60e492e..0808e5c 100644 --- a/modules/Entropy.py +++ b/modules/Entropy.py @@ -69,11 +69,11 @@ def Entropy(req, url, m_action, m_name=''): 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+'... ') + verbout(color.ORANGE,' [+] 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+'... ') + 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) if m_name: @@ -97,7 +97,7 @@ def Entropy(req, url, m_action, m_name=''): def calcEntropy(data): """ This function is used to calculate - Entropy. + Shannon Entropy. """ if not data: return 0 From 688e4ac82c168194d2ace836357e2edb208f3f08 Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 17:40:35 +0530 Subject: [PATCH 14/94] Small update to requirements to fix build error. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 4592b91..f41512c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ bs4 lxml request stringdist +tld From 022f81dc00ce1f9de4935cd6459e073225387043 Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 17:43:32 +0530 Subject: [PATCH 15/94] Improve PEP8 style coding according to Codefactor --- modules/Generator.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/modules/Generator.py b/modules/Generator.py index 2dfe59d..2f37f1a 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -48,9 +48,3 @@ def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www- m = content.prettify() for i in m.splitlines(): print(' '+i) - - -if __name__ == '__main__': - GeneratePoC('http://www.webscantest.com/csrf/csrfpost.php', "[{ 'type':'text', 'name':'property', 'label':'color', 'value':''}, { 'type':'text', 'name':'bullshit', 'label':'pun', 'value':''}]") -# python Generator.py "POST" "application/x-www-form-urlencoded" "http://webscantest.com/csrf/csrfpost.php" "[{ 'type':'text', 'name':'property', 'label':'color', 'value':''}, { 'type':'text', 'name':'bullshit', 'label':'pun', 'value':''}]" - From 442d530bd98c940acf1ae24be18b82a4b2421e15 Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 17:46:59 +0530 Subject: [PATCH 16/94] Added request validation SSL checking. --- core/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/request.py b/core/request.py index a046c43..5d45162 100644 --- a/core/request.py +++ b/core/request.py @@ -69,7 +69,7 @@ def Get(url, headers=headers): return 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) From 7157a98b768dbbe7b7e7b73e2c635b3268b9c9eb Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 17:49:16 +0530 Subject: [PATCH 17/94] Updated Token.py with scanning accuracy improvisations. --- modules/Token.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/Token.py b/modules/Token.py index bc8da35..b67d927 100644 --- a/modules/Token.py +++ b/modules/Token.py @@ -9,9 +9,9 @@ # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe +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 @@ -26,7 +26,7 @@ def Token(req): param = '' # Initializing param query = '' # 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 From 9c7d7bac161f0175351af1bc446dc4fcf9b3ddaf Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 17:55:52 +0530 Subject: [PATCH 18/94] Final update to let builds pass off. --- modules/Token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/Token.py b/modules/Token.py index b67d927..73f1efd 100644 --- a/modules/Token.py +++ b/modules/Token.py @@ -9,7 +9,7 @@ # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -import config +from files import config from re import search, I from time import sleep from core.colors import * From 4bdd73ea660c17addf8d92e0c1cd417443847c2a Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 18:13:51 +0530 Subject: [PATCH 19/94] Update travis config file to suit changes. --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bdaf0a2..8c970ac 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 From 8df62bcaf00df5f4cd6d2ad8eb4d58b7c1a08dbd Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 18:18:37 +0530 Subject: [PATCH 20/94] Update main with a small improvement of verbosity. --- core/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/main.py b/core/main.py index 5551640..6b64096 100644 --- a/core/main.py +++ b/core/main.py @@ -39,6 +39,8 @@ from core.banner import banner, banabout # Imports from files +from files import config +# Necessary evil :( from files.config import * # Imports from modules @@ -90,6 +92,7 @@ def Engine(): # lets begin it! try: if not CRAWL_SITE: url = web + config.DISPLAY_HEADERS = True response = Get(url).text try: verbout(O,'Trying to parse response...') From 326e11135882a0d5d4118b3bb824f9fa215148be Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 18:20:45 +0530 Subject: [PATCH 21/94] Remove trailing whitespace for config travis. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8c970ac..6a258b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ script: # 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 + - 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 From b72fb417c07af2ee620be15fb398f928f38e4e24 Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 18:30:36 +0530 Subject: [PATCH 22/94] Trail to fix bugs (0x01). --- core/inputin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/inputin.py b/core/inputin.py index 8336c28..4d4ccaa 100644 --- a/core/inputin.py +++ b/core/inputin.py @@ -31,7 +31,8 @@ def inputin(): except socket.gaierror: # if site is down print(R+'Site seems to be down...') sys.exit(0) - + if not web0.endswith('/'): + web0 = web0 + '/' if web.split('//')[1] == web0: return web, '' - return web, web0 + return (web, web0) From 4af9577657885930f0d96bb5eba9353575a34e37 Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Tue, 11 Dec 2018 18:43:59 +0530 Subject: [PATCH 23/94] Update Post Based module for more verbosity. --- modules/Checkpost.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/Checkpost.py b/modules/Checkpost.py index af2837f..0d89ee4 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -33,6 +33,9 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): for n in checkdiffx2: if re.match('\+|-',n): # get regex matching stuff result13.append(n) # append to existing list + # 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 requests and # response body. # If the number of differences of result12 are less than the number of differences @@ -50,13 +53,19 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): 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.rsplit('/', 1)[1]) # 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+' | Request PoC |') print(color.RED+' +-----------------+\n') print(color.BLUE+' [+] URL : ' +color.CYAN+url) # the url - print(color.GREEN+' [+] Action : ' +color.END+'/'+m_action.rsplit('/', 1)[1]) # 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 print(color.ORANGE+' [+] Query : '+color.GREY+ urlencode(result).strip()) print(GR, 'Generating PoC Form...' ) print(color.RED+'\n +--------------+') From 1e0de1cff7f12da99c620fb00cffaad871272055 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Wed, 12 Dec 2018 07:33:14 +0000 Subject: [PATCH 24/94] Added support for detecting token encoding --- files/dcodelist.py | 85 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/files/dcodelist.py b/files/dcodelist.py index 4448ca6..772b197 100644 --- a/files/dcodelist.py +++ b/files/dcodelist.py @@ -12,6 +12,75 @@ # This file contains various regex expressions for detecting the # encoding type of strings. +# Token hash encoding detection +hashes = ( + ("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*,' - } From 84c34f61998204e72d761d46de2da9bf29117431 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Wed, 12 Dec 2018 07:33:32 +0000 Subject: [PATCH 25/94] Added main file to detect Encoding type --- modules/Encoding.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/modules/Encoding.py b/modules/Encoding.py index 1a4a228..a7b1a30 100644 --- a/modules/Encoding.py +++ b/modules/Encoding.py @@ -9,25 +9,35 @@ # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -from re import search +from re import finditer from core.colors import * from core.verbout import verbout -from files.dcodelist import token +from files.dcodelist import hashes def Encoding(val): - verbout(G, 'Proceeding to detect encoding of Anti-CSRF Token...') - -def encodeDetect(val): ''' - The main target of this method is to detect if there is - any encoding in the string value passed. + This function is for detecting the encoding type of + Anti-CSRF tokens based on pre-defined + regular expressions. ''' - value = makeAscii() + found = 0x00 + for h in hashes: + txt = hashcheck(h[0], h[1], val) + if txt is not None: + found = 0x01 + print(color.GREEN+' [+] The Token Encoding Detected: '+color.BG+' '+hashtype+' '+color.END) + break # Break the execution if token encoding detected + if found == 0x00: + print(color.RED+' [-] No Token Encoding detected.') + +def hashcheck(hashtype, regexstr, data): + verbout(G, 'Proceeding to detect encoding of Anti-CSRF Token...') + try: + valid_hash = finditer(regexstr, data) + result = [match.group(0) for match in valid_hash] + if result: + return hashtype + except Exception: + pass + return None -def makeAscii(value, encoding='latin-1'): - ''' - The main target of this function is to ensure that 'value' - has all characters in ASCII. - ''' - if isinstance(value, (str, bytearray)): - return value.decode(encoding, 'strict') From e29cfb15d8aafaac98c423b87d57bf087c839025 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 13 Dec 2018 12:29:41 +0000 Subject: [PATCH 26/94] Improved methods for poc generation --- modules/Checkpost.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/Checkpost.py b/modules/Checkpost.py index 0d89ee4..1f70219 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,6 +15,7 @@ from core.colors import * from core.verbout import verbout from urllib.parse import urlencode +from files.config import POC_GENERATION from modules.Generator import GeneratePoC def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): @@ -66,9 +67,10 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): 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+' [+] Query : '+color.GREY+ urlencode(result).strip()) + print(color.ORANGE+' [+] POST Query : '+color.GREY+ urlencode(result).strip()) print(GR, 'Generating PoC Form...' ) print(color.RED+'\n +--------------+') print(color.RED+' | Form PoC |') print(color.RED+' +--------------+\n'+color.CYAN) - GeneratePoC(url, str(genpoc)) + if POC_GENERATION: + GeneratePoC(url, str(genpoc)) From 29d316bf0acabeaeb3c3352e85748d0f2a0d9e05 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 13 Dec 2018 12:30:56 +0000 Subject: [PATCH 27/94] Added more elaborate methods for encoding stuff. --- modules/Encoding.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/modules/Encoding.py b/modules/Encoding.py index a7b1a30..fbf1432 100644 --- a/modules/Encoding.py +++ b/modules/Encoding.py @@ -5,14 +5,15 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe +from time import sleep from re import finditer from core.colors import * from core.verbout import verbout -from files.dcodelist import hashes +from files.dcodelist import HASH_DB def Encoding(val): ''' @@ -20,18 +21,39 @@ def Encoding(val): Anti-CSRF tokens based on pre-defined regular expressions. ''' + if not val: + return None + verbout(GR, 'Proceeding to detect encoding of Anti-CSRF Token...') found = 0x00 - for h in hashes: + # So the idea right here is to detect whether the Anti-CSRF tokens + # are encoded in some form or the other. + # + # Often in my experience with web applications, I have found that + # most of the Anti-CSRF tokens are encoded (mostly MD5 or SHA*). + # In those cases, I have found that the Anti-CSRF tokens follow a + # specific pattern. For example, every request has a specific + # iteration number, if the previous request is 144, and MD5 encrypted + # it turns out to be 0a09c8844ba8f0936c20bd791130d6b6, then it is + # not at all strong, since the next request is probably 145 and can + # be easily forged! Ofc, if there is no salt in the encryption. + # + # This module aims to automate and simplify the task. ;) + for h in HASH_DB: txt = hashcheck(h[0], h[1], val) if txt is not None: found = 0x01 - print(color.GREEN+' [+] The Token Encoding Detected: '+color.BG+' '+hashtype+' '+color.END) + verbout(color.RED, ' [+] Anti-CSRF Token is detected to be String Encoded!') + print(color.GREEN+' [+] Token Encoding Detected: '+color.BG+' '+txt+' '+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+' String Encoded Anti-CSRF Tokens '+color.END) + print(color.RED+' [-] The Tokens might be easily Decrypted and can be Forged!') break # Break the execution if token encoding detected if found == 0x00: - print(color.RED+' [-] No Token Encoding detected.') + print(color.RED+' [-] '+color.BR+' No Token Encoding Detected. '+color.END) + sleep(0.8) + return found def hashcheck(hashtype, regexstr, data): - verbout(G, 'Proceeding to detect encoding of Anti-CSRF Token...') try: valid_hash = finditer(regexstr, data) result = [match.group(0) for match in valid_hash] @@ -41,3 +63,5 @@ def hashcheck(hashtype, regexstr, data): pass return None +#if __name__ == '__main__': +# Encoding('38c4658d5308897a92cef9e113aefc3a') From defd841e41e854f7d722a693d9e60609b839ff40 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 13 Dec 2018 12:32:09 +0000 Subject: [PATCH 28/94] Small update to improve PoC generation (II) --- modules/Generator.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/Generator.py b/modules/Generator.py index 2f37f1a..2c75484 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -9,8 +9,9 @@ # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -from json import dumps +from core.colors import G from ast import literal_eval +from files.config import OUTPUT_DIR from bs4 import BeautifulSoup, Tag def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): @@ -48,3 +49,7 @@ def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www- m = content.prettify() for i in m.splitlines(): print(' '+i) + fi = open(OUTPUT_DIR+'post-csrf-poc.html', 'w+') + fi.write(m) + fi.close() + print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+'post-csrf-poc.html') From a395b232b8830762915f078ea493a2c6352e37d1 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 13 Dec 2018 12:32:32 +0000 Subject: [PATCH 29/94] Other updates to suit build --- core/banner.py | 4 ++-- core/colors.py | 2 +- core/logger.py | 2 +- core/main.py | 9 +++++++-- core/options.py | 28 ++++++++++++++++++++++------ core/randua.py | 2 +- core/request.py | 17 ++++++++++------- core/updater.py | 2 +- core/utils.py | 10 +++++----- core/verbout.py | 2 +- files/__init__.py | 2 +- files/config.py | 6 +++++- files/dcodelist.py | 6 +++--- files/discovered.py | 2 +- files/paramlist.py | 2 +- modules/Analysis.py | 2 +- modules/Cookie.py | 6 +++--- modules/Crawler.py | 2 +- modules/Debugger.py | 18 +++++++++--------- modules/Entropy.py | 8 ++++---- modules/Origin.py | 2 +- modules/Parser.py | 2 +- modules/Persistence.py | 2 +- modules/Referer.py | 2 +- modules/Tamper.py | 10 +++++----- modules/Token.py | 12 ++++++------ modules/__init__.py | 2 +- xsrfprobe.py | 2 +- 28 files changed, 97 insertions(+), 69 deletions(-) diff --git a/core/banner.py b/core/banner.py index afd8ea9..ff842a2 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 @@ -33,7 +33,7 @@ def banner(): 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+'[---]') + 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+' [---] [---]') time.sleep(0.2) 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/logger.py b/core/logger.py index f2cb8ed..fb0b44d 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 diff --git a/core/main.py b/core/main.py index 6b64096..c1432b8 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 @@ -116,7 +116,7 @@ def Engine(): # lets begin it! # 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,'Using form:\n'+color.CYAN+' %s' % (m)) + verbout(O,'Testing form:\n\n'+color.CYAN+' %s' % (m.prettify())) try: if m['action']: pass @@ -127,6 +127,7 @@ def Engine(): # lets begin it! # If form submission is kept to True if FORM_SUBMISSION: try: + # NOTE: Slow connections may cause read timeouts which may result in AttributeError result, genpoc = form.prepareFormInputs(m) # prepare inputs r1 = Post(url, action, result).text # make request with token values generated as user1 result, genpoc = form.prepareFormInputs(m) # prepare the input types @@ -137,6 +138,8 @@ def Engine(): # lets begin it! query, token = Entropy(result, url, m['action'], m['name']) except KeyError: query, token = Entropy(result, url, m['action']) + # Now its time to detect the encoding type (if any) of the Anti-CSRF token. + fnd = Encoding(token) # Go for token parameter tamper checks. if (query and token): Tamper(url, action, result, r2, query, token) @@ -217,6 +220,8 @@ def Engine(): # lets begin it! query, token = Entropy(result, url, m['action'], m['name']) except KeyError: query, token = Entropy(result, url, m['action']) + # Now its time to detect the encoding type (if any) of the Anti-CSRF token. + fnd = Encoding(token) # Go for token parameter tamper checks. if (query and token): Tamper(url, action, result, r2, query, token) diff --git a/core/options.py b/core/options.py index e8eb559..b570372 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 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 @@ -36,12 +36,13 @@ # 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('--max-chars', help='Maximum allowed character length for the custom token value to be generated. For example: `--max-chars 5`. Default value is 6.', dest='maxchars', type=int) optional.add_argument('--crawl', help="Crawl the whole site and simultaneously test all discovered endpoints for CSRF.", dest='crawl', action='store_true') optional.add_argument('--skip-analysis', help='Skip the Post-Scan Analysis of Tokens which were gathered during requests', dest='skipal', 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('--update', help='Update XSRFProbe to latest version on GitHub via git.', dest='update', action='store_true') optional.add_argument('--random-agent', help='Use random user-agents for making requests.', dest='randagent', action='store_true') optional.add_argument('--version', help='Display the version of XSRFProbe and exit.', dest='version', action='store_true') @@ -76,6 +77,10 @@ if args.skipal: config.SCAN_ANALYSIS = False +# Option to skip poc generation +if args.skippoc: + config.POC_GENERATION = False + # Updating main root url if not args.version and not args.update: if args.url: # and not args.help: @@ -131,11 +136,22 @@ 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+config.SITE_URL.split('//')[1].replace('/', '-')): + os.makedirs(args.output) + except FileExistsError: + pass + config.OUTPUT_DIR = args.output+config.SITE_URL.split('//')[1].replace('/', '-') + '/' + else: - config.OUTPUT_DIR = 'files' + config.SITE_URL.split('//')[1] + try: + os.makedirs(config.SITE_URL.split('//')[1].replace('/', '-')) + except FileExistsError: + pass + config.OUTPUT_DIR = config.SITE_URL.split('//')[1].replace('/', '-') + '/' if args.quiet: config.DEBUG = False diff --git a/core/randua.py b/core/randua.py index 591c898..aefe782 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 diff --git a/core/request.py b/core/request.py index 5d45162..77d9039 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 @@ -47,13 +47,16 @@ def Post(url, action, data): return response # read data content except requests.exceptions.HTTPError: # if error verbout(R, "HTTP Error : "+action) - return + return None + except requests.exceptions.ConnectionError: + verbout(R, 'Connection Aborted : '+action) + return None except ValueError: # again if valuerror verbout(R, "Value Error : "+action) - return + return None except Exception as e: verbout(R, "Exception Caught: "+e.__str__()) - return '' # if at all nothing happens :( + return None # if at all nothing happens :( def Get(url, headers=headers): ''' @@ -65,7 +68,7 @@ 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) + verbout(G, 'Found File: '+color.BLUE+url) return try: verbout(GR, 'Processing the '+color.GREY+'GET'+color.END+' Request...') @@ -76,6 +79,6 @@ def Get(url, headers=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') + verbout(R, 'Exception at: '+color.GREY+url) + verbout(R, 'Error: Invalid URL Format') return diff --git a/core/updater.py b/core/updater.py index f41d02a..8a0535d 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 diff --git a/core/utils.py b/core/utils.py index 1b27e93..1fe34ce 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) diff --git a/core/verbout.py b/core/verbout.py index 4d7487d..74ebdfd 100644 --- a/core/verbout.py +++ b/core/verbout.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe 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 a48c384..a610804 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 @@ -129,6 +129,10 @@ # 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 + # 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 772b197..64eadcb 100644 --- a/files/dcodelist.py +++ b/files/dcodelist.py @@ -5,15 +5,15 @@ # 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 -hashes = ( +# 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,}$"), diff --git a/files/discovered.py b/files/discovered.py index 5debcda..f3d2306 100644 --- a/files/discovered.py +++ b/files/discovered.py @@ -5,7 +5,7 @@ # XSRFProbe # #-:-:-:-:-:-:-:-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe diff --git a/files/paramlist.py b/files/paramlist.py index ad86cb8..c8e9769 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 diff --git a/modules/Analysis.py b/modules/Analysis.py index 8d372e0..0afdbc2 100644 --- a/modules/Analysis.py +++ b/modules/Analysis.py @@ -5,7 +5,7 @@ # XSRF Probe # #-:-:-:-:-:-:-::-:-:# -# Author: @_tID +# Author: 0xInfection # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe diff --git a/modules/Cookie.py b/modules/Cookie.py index 0ef4c6d..01b06df 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 @@ -124,7 +124,7 @@ def SameSite(url): verbout(GR,'Examining Cookie...') for q in m: if search('SameSite', q, I): - verbout(G,'SameSite Flag '+color.ORANGE+' detected on cookie!') + 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) @@ -145,4 +145,4 @@ def SameSite(url): verbout(R,'Endpoint '+color.ORANGE+'SameSite Flag 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 SameSite Flag on Cookies On Cross Origin Requests '+color.END) diff --git a/modules/Crawler.py b/modules/Crawler.py index 1da3a62..96e88c3 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 diff --git a/modules/Debugger.py b/modules/Debugger.py index d4578de..05a00a8 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 @@ -27,7 +27,7 @@ def prepareFormInputs(self, form): cr_input = {} totcr = [] - verbout(O,'Processing '+color.BOLD+' < '+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) + 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) + 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.2) + time.sleep(0.1) diff --git a/modules/Generator.py b/modules/Generator.py index 2c75484..97b0c3e 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -9,7 +9,7 @@ # This module requires XSRFProbe # https://github.com/0xInfection/XSRFProbe -from core.colors import G +from core.colors import * from ast import literal_eval from files.config import OUTPUT_DIR from bs4 import BeautifulSoup, Tag @@ -49,6 +49,7 @@ def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www- m = content.prettify() for i in m.splitlines(): print(' '+i) + print('') fi = open(OUTPUT_DIR+'post-csrf-poc.html', 'w+') fi.write(m) fi.close() From 95c6b732a9921d64272df785b504fb35de65ec88 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 14 Dec 2018 07:13:28 +0000 Subject: [PATCH 31/94] Added small modifications to improve logging --- core/logger.py | 17 ++++- core/main.py | 175 +++++++++++++++++++++++++----------------------- core/options.py | 2 +- core/request.py | 29 ++++---- 4 files changed, 124 insertions(+), 99 deletions(-) diff --git a/core/logger.py b/core/logger.py index fb0b44d..20ae402 100644 --- a/core/logger.py +++ b/core/logger.py @@ -19,10 +19,13 @@ 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 +38,11 @@ def pheaders(tup): for key, val in tup.items(): verbout(' ',color.CYAN+key+': '+color.ORANGE+val) verbout('','') + +def LinkLogger(): + from files.discovered import INTERNAL_URLS + logger('internal-links', INTERNAL_URLS) + +def ErrorLogger(url, error): + content = '(i) 'url+' -> '+error + logger('errors', content) diff --git a/core/main.py b/core/main.py index c1432b8..34082b5 100644 --- a/core/main.py +++ b/core/main.py @@ -37,6 +37,7 @@ from core.verbout import verbout from core.forms import form10, form20 from core.banner import banner, banabout +from core.logger import ErrorLogger, LinkLogger # Imports from files from files import config @@ -122,6 +123,7 @@ def Engine(): # lets begin it! 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 @@ -148,6 +150,7 @@ def Engine(): # lets begin it! form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form except IndexError: verbout(R, 'Form Error') + ErrorLogger(url, 'Form Error') continue # making sure program won't end here (dirty fix :( ) verbout(GR, 'Preparing form inputs...') contents2, genpoc = form.prepareFormInputs(form2) # prepare for form 2 as user2 @@ -163,6 +166,7 @@ def Engine(): # lets begin it! print(color.GREEN+' [=] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to POST-Based CSRF Attacks!') except HTTPError as msg: # if runtime exception... verbout(R, 'Exception : '+msg.__str__()) # again exception :( + ErrorLogger(url, msg.__str__()) actionDone.append(action) # add the stuff done i+=1 # Increase user iteration @@ -171,102 +175,107 @@ def Engine(): # lets begin it! verbout(GR, "Initializing crawling and scanning...") crawler = Crawler.Handler(init1, resp1) # Init to the Crawler handler - try: - while crawler.noinit(): # Until 0 urls left - url = next(crawler) # Go for next! + while crawler.noinit(): # Until 0 urls left + url = next(crawler) # Go for next! - print(C+'Testing :> '+color.CYAN+url) # Display what url its crawling + print(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...') + 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 + # 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) + 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 - try: - if m['action']: - pass - except KeyError: - m['action'] = '/' + url.rsplit('/', 1)[1] - 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: + # 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 + 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: + result, genpoc = form.prepareFormInputs(m) # prepare inputs + r1 = Post(url, action, result).text # make request with token values generated as user1 + result, genpoc = 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... + try: + if m['name']: + query, token = Entropy(result, url, m['action'], m['name']) + except KeyError: + query, token = Entropy(result, url, m['action']) + ErrorLogger(url, 'No standard form "name".') + # Now its time to detect the encoding type (if any) of the Anti-CSRF token. + fnd = Encoding(token) + # 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 try: - result, genpoc = form.prepareFormInputs(m) # prepare inputs - r1 = Post(url, action, result).text # make request with token values generated as user1 - result, genpoc = 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 Error') + ErrorLogger(url, 'Form Error') + continue # making sure program won't end here (dirty fix :( ) + verbout(GR, 'Preparing form inputs...') + contents2, genpoc = 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: try: if m['name']: - query, token = Entropy(result, url, m['action'], m['name']) + PostBased(url, r1, r2, r3, m['action'], result, genpoc, m['name']) except KeyError: - query, token = Entropy(result, url, m['action']) - # Now its time to detect the encoding type (if any) of the Anti-CSRF token. - fnd = Encoding(token) - # 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 - 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, genpoc = 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: - try: - if m['name']: - PostBased(url, r1, r2, r3, m['action'], result, genpoc, m['name']) - except KeyError: - PostBased(url, r1, r2, r3, m['action'], result, genpoc) - 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!') - except HTTPError as msg: # if runtime exception... - verbout(color.RED, ' [-] Exception : '+color.END+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 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)) - quit() - + PostBased(url, r1, r2, r3, m['action'], result, genpoc) + 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!') + except HTTPError as msg: # if runtime exception... + verbout(color.RED, ' [-] Exception : '+color.END+msg.__str__()) # again exception :( + ErrorLogger(url, msg.__str__()) + 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.__str__()) + 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.__str__()) + quit() + LinkLogger() # The scanning has finished, so now we can log out all the links ;) print('\n'+G+"Scan completed!"+'\n') Analysis() # For Post Scan Analysis - 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') quit() diff --git a/core/options.py b/core/options.py index b570372..e49eda2 100644 --- a/core/options.py +++ b/core/options.py @@ -132,7 +132,7 @@ 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: diff --git a/core/request.py b/core/request.py index 77d9039..79ccdcb 100644 --- a/core/request.py +++ b/core/request.py @@ -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,24 +38,28 @@ 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.HTTPError: # if error - verbout(R, "HTTP Error : "+action) + 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: - verbout(R, 'Connection Aborted : '+action) + except requests.exceptions.ConnectionError as e: + verbout(R, 'Connection Aborted : '+main_url) + ErrorLogger(main_url, e.__str__()) return None - except ValueError: # again if valuerror - verbout(R, "Value Error : "+action) + 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__()) + ErrorLogger(main_url, e.__str__()) return None # if at all nothing happens :( def Get(url, headers=headers): @@ -69,7 +73,7 @@ def Get(url, headers=headers): if url.split('.')[-1].lower() in (FILE_EXTENSIONS or EXECUTABLES): FILES_EXEC.append(url) verbout(G, 'Found File: '+color.BLUE+url) - return + return None try: verbout(GR, 'Processing the '+color.GREY+'GET'+color.END+' Request...') req = requests.get(url, headers=headers, timeout=TIMEOUT_VALUE, stream=False) @@ -81,4 +85,5 @@ def Get(url, headers=headers): except requests.exceptions.MissingSchema as e: verbout(R, 'Exception at: '+color.GREY+url) verbout(R, 'Error: Invalid URL Format') - return + ErrorLogger(url, e.__str__()) + return None From c74b8e94c6eda1485d113dd7c76bfd1d033c474c Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 14 Dec 2018 07:14:16 +0000 Subject: [PATCH 32/94] Added support for logging --- modules/Crawler.py | 26 ++++++++++++++------------ modules/Generator.py | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/modules/Crawler.py b/modules/Crawler.py index 96e88c3..882f928 100644 --- a/modules/Crawler.py +++ b/modules/Crawler.py @@ -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']): @@ -108,15 +110,15 @@ def process(self, root): # Getting rid of Urls starting with '../../../..' 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/Generator.py b/modules/Generator.py index 97b0c3e..7670b4b 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -50,7 +50,7 @@ def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www- for i in m.splitlines(): print(' '+i) print('') - fi = open(OUTPUT_DIR+'post-csrf-poc.html', 'w+') + fi = open(OUTPUT_DIR+'post-csrf-poc.html', 'w+', encoding='utf8') fi.write(m) fi.close() print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+'post-csrf-poc.html') From ad38dfb66fa8954935e84b7073d56cf06d7acf37 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 14 Dec 2018 07:16:27 +0000 Subject: [PATCH 33/94] Fixes a small bug which fucked up code --- core/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/logger.py b/core/logger.py index 20ae402..78222f2 100644 --- a/core/logger.py +++ b/core/logger.py @@ -44,5 +44,5 @@ def LinkLogger(): logger('internal-links', INTERNAL_URLS) def ErrorLogger(url, error): - content = '(i) 'url+' -> '+error + content = '(i) '+url+' -> '+error.__str__() logger('errors', content) From 8cc4a8ef2382f69c6bae09142588289d08f20dd9 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 14 Dec 2018 07:46:06 +0000 Subject: [PATCH 34/94] Improved verbatim efficiency --- core/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 123 bytes core/__pycache__/banner.cpython-36.pyc | Bin 0 -> 2558 bytes core/__pycache__/colors.cpython-36.pyc | Bin 0 -> 1002 bytes core/__pycache__/forms.cpython-36.pyc | Bin 0 -> 3041 bytes core/__pycache__/inputin.cpython-36.pyc | Bin 0 -> 781 bytes core/__pycache__/logger.cpython-36.pyc | Bin 0 -> 1473 bytes core/__pycache__/main.cpython-36.pyc | Bin 0 -> 6366 bytes core/__pycache__/options.cpython-36.pyc | Bin 0 -> 4286 bytes core/__pycache__/randua.cpython-36.pyc | Bin 0 -> 2485 bytes core/__pycache__/request.cpython-36.pyc | Bin 0 -> 2289 bytes core/__pycache__/updater.cpython-36.pyc | Bin 0 -> 1162 bytes core/__pycache__/utils.cpython-36.pyc | Bin 0 -> 1718 bytes core/__pycache__/verbout.cpython-36.pyc | Bin 0 -> 414 bytes core/logger.py | 19 ++++++++++++++----- core/main.py | 6 ++++-- core/options.py | 14 ++++++-------- 16 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 core/__pycache__/__init__.cpython-36.pyc create mode 100644 core/__pycache__/banner.cpython-36.pyc create mode 100644 core/__pycache__/colors.cpython-36.pyc create mode 100644 core/__pycache__/forms.cpython-36.pyc create mode 100644 core/__pycache__/inputin.cpython-36.pyc create mode 100644 core/__pycache__/logger.cpython-36.pyc create mode 100644 core/__pycache__/main.cpython-36.pyc create mode 100644 core/__pycache__/options.cpython-36.pyc create mode 100644 core/__pycache__/randua.cpython-36.pyc create mode 100644 core/__pycache__/request.cpython-36.pyc create mode 100644 core/__pycache__/updater.cpython-36.pyc create mode 100644 core/__pycache__/utils.cpython-36.pyc create mode 100644 core/__pycache__/verbout.cpython-36.pyc diff --git a/core/__pycache__/__init__.cpython-36.pyc b/core/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ea645e8a936dcf26b3ebba5cfd7c1846abf7d26 GIT binary patch literal 123 zcmXr!<>gY@%oDShfq~&M5W@i@kmUfx#VkM~g&~+hlhJP_LlH(61;iN-HSJPfFEK&M!*UkB`sH%PfhH*DI*J#bJ}1pHiBWY6mi*7>F4Fhe8|d literal 0 HcmV?d00001 diff --git a/core/__pycache__/banner.cpython-36.pyc b/core/__pycache__/banner.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26e666653ed2ba6d5f75036580edf9a36da51c2c GIT binary patch literal 2558 zcmbVOO>Y}T7~a_r+gX3mgccVFFiq1Gqc}!F;($sUH*qVZ%0y0zND0~|o=M`3<2AEu zsU0jX;ZM+i&m|(BN6E4HV3`T~=4zA{qJR!_t_OHehlR2yatHsQ1qfz`Dm_~!dnu`0+L3M>WmO0BEHE?Wf_s>+svvP8r)%aUHR(UA&qX6ls^3qmb;;B7& z%_;D(J!yeQ=DnrOs09+9ud+h-D1Az43)_+9DaLvpE^32RwQ+L~6rnQNLw7*L$)l^c zlO%LcZ7FSJAy9?hg_L*T!Nl}wa}7Sp23ZulP#(2Y4XCur16UI(P1VVN#%f8Akf`N( z@WEhddQ<2sIT%pn9N5!6bV>^jAxr4PSUCrY5_56`1n8Xi&#PA$-9jgfM!LdbzlJCk zlSIw5lL)MlQ(U)Eh@G(*jH1|%2B$oB!XfA5*y#tuK*WV_d+qMSPHgo$n=cJffrixd zdZi_TAZl&$@FWVxt!JS)9E;$9xB7wLt%Lp1h>NxH^SF?h^C+c@Fd1fvxTFTppYVt` zo*EG~@V}+PHULV6*9oey(q961hWMbo%v2U=E$T2}cpFi9iH{iGaL{Y8KzWr)7yKeC zrs|DK4(hALQGtmMUWN)xeAqKoTjIl>q1q;SL2WOIM=ok_(kp6j5(V0;jk>%WJR9n= zI$Km`3unZyoEd)g%up!$QI(S#pv7<3P+D=VE3d-4|DoAzCNiOZ3i!kRe!KU8ZsGe| zl_qV=MY8RAH82xci?&_*# z^#5t78gRMtgUE&drjnIkC~Z%o<3P}_s9f1O=JcD`!YKvDg&|!9Bqf$F zIMo(njt0^3`itYQ49(Q8d%b@UOrj;&h1X#+%=PqUe+lbqYTqZK o4!(vRmr)tuTLW1MlP2)GE3dx7ozs9#hWswlLOfN8l+4onzfv9t`v3p{ literal 0 HcmV?d00001 diff --git a/core/__pycache__/colors.cpython-36.pyc b/core/__pycache__/colors.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..819d29820158c808091d24dd5f40689929877872 GIT binary patch literal 1002 zcmZ{jO>fgc5QcaC6~~E_5(-H4QV>E42tu3Es47Bf8!JMJoXRMNtU*@DrUx3w!A>Q} z75)rAhwENB@fSGpPD0WTfvxxP?Cg4HJUgztuJfZ?c^8|E{bW}z9d;AS&H%|IA2Y#5 z>?4=jxt=x`mQGfiNkf|7m^8m?LX(!zWlk8<7N*P#OFAMqd5ATID{G1d6FOu-CS-}6 zZ6jF{k(Ask0I4dHZzGZ4$De~c085u`7JN#yzwz{(m(Q*fz^Gb9S2Yw>VIeH9n@}t z-;Zl)lFVwYQFcB{rnS#mdNxgy52M;JNeQS;_I&y!HSk6W%I*U;H~w=k*3Bza#_g@} zpdN=0-cx0_FMT_4`0S>y5rMpR`QaAlxEU2)7BFgmvMU z)?M;ZX&*rueTL1`dw5iBr6rj%r~{Vi@UG%G!gio|Tk(^B?WF8i%MN-?Ib{^u=9|1! KUY(Yum$X00?#qP$ literal 0 HcmV?d00001 diff --git a/core/__pycache__/forms.cpython-36.pyc b/core/__pycache__/forms.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7800603dac565e1a2bd34368b856971098e9bcfe GIT binary patch literal 3041 zcmeHJUyIW~5Knuj9LEVh_yLwhg%ff~tJbPX6cnH25cOU}DBJAzx|n3+?&kiXZ+;n{ z{WiY(Eqrojo1|$`^hHEyAd_uoW@mQhH^21ul`Ff~zwEso?0McduXFWb{TOa@4Fvai z@00h&<9)sb&mP}?|N+hf* zBeb8XV_EnZhbFrOu87N%1O-VmW=u>`=SwT{g_q#c`UT5{f2vto3SIk=C}UHR5ipNw zYebVHh=S%1NMuS@tn8}A`$}rTDY4RKBBrnB(vX=H87Hu4)CV-V6$R*>M1hO4NR>pg zC@V|sc?rH&oWa3zc9x0a*v?}*3Ms+JF-5tSJtan7fNF4;k`tCyZUbWnbR|LDvrU7N zpe|8>HEOH1;;~k!5=!{nLZJ~&UVuDRn*a4eVK@xb9-Q637>j#Nv0OF|Ag+sxpX+t) zRBz?@zb{y~Wu}_u5}LN?k}5krYE*S^o~vUb3qW@ll#NZ(mJVpK4Sps8>Pb{(NtDUf zQ)>5?k+~MLm8rsk2(&l`H-L46$tPMcD+nWnI3;L;6Q-)d0xWACYU-C!z!0RehQ~54 z3=<~vtFX(a)T8hcW&?)XMQxPiL^2}$oXHG=|IA`7e1KB$Xx! z2fq$UO*56gYlEU<;@E3aE86`R2M#c;gnah)dvA7d9iD{)jeP6G_Zxv$$_7t`d1qA_ zoEbeUwVH|`Ra!V_b5Ndty^OAd5P04*+aSEF+kL$S&kcQoa;`MD_&(HaU_qg HcCP&ZmI`A?PRb8*EyE4yewcyXM`j;z7z{|;yT%hpuMOIpadkFL7*cqI;1Avm zyJzqFZzrb0EYr1u_6kk@L{LQdid420N8{e+Nc zoqVgk8-9!``2c}X8*J)>k4B*i0W_hGzK+a?V>Hpis7|8$uuYhgQ=s$fCy%GQb literal 0 HcmV?d00001 diff --git a/core/__pycache__/logger.cpython-36.pyc b/core/__pycache__/logger.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06e74784eef78c36df83f95571c838dec9737e24 GIT binary patch literal 1473 zcmZWpUvJws5GSd>mYtwWGkDm`gboYl47Gu64?{2n3u+?`V5psvAZ^gWa78*%EK3SW z$B7|7xv#@M$G#F@_q4CjryVJy3pNV8JMwtsPxm|WhpnxkeDn0(2M3|Q(4$7c{sp>t z34$VuGjypn$?#dNiP2VO&Fo7XBWhFU1G;pmOFhso^=SjNM+4df?NfY;I<3E9HxzZS z!uue+JI3D^bD8MVbORKq3H=3hAwft~p*Eb}Mk^w5MJS;bwSTw%c)hYDsqh+0YwC+1 zH?}?Xd3?WD;Xe9pXJuE`jZ*=xHd3ddO%%4-%1IiF0z1q0_iucLINZj0*_;u1byWF{h~ zp-9Gg4uh`Qerb1MyCqqCr|h|$`~(T{S)LRWRwx`q9ajaf&d*+-pN;w_v9gPj<;s>1 zB~y-^ml;!bmI|qy1y3dGILe>EiTQZOl$R8_WVuvL$Zk28BkfUmqVVib#CIUEANP8^ zC}gkC#8eh#?_Tgp$&2@_mlT}!^nWtmEgzIyo1bY$wm~4Lg`eOraDaD7fcY-WBv{Ww zv!H7V4SjcDNF*`EYl5{!ZY)Yb+CZq))?*+>&%#*GX_{c={r45Ran}ea+7>I|m zgLPg7Wk6jMo>h-sW7qC0^sHdZ;4+J)rlm8J|@-&wsgnSs{uGyF}F2b=0IZIf2 z$7q=5+GQeZ3jdXy-OibS_`KjVApc=Q;pedl)&y^M>~_1sl7!)<_i}L5AH=60@d0-s z7fW&wDyaPi*J)DZI95(lWCd6DaXfgd8s`x#AD%=5WeuXfa)8vCQ1*+rN0D-0#YfTc zpkpZus8`mMJt*sLoEa`_@l@~Xmm3&g!AJxkkQ3m2yn~-%{tQNmp3+Torhyo)z666} zxZG%lSKyLx%__qNMUYQB;(9q0=+;SeHi)C6;pjXbo-Vf`r;O+0>^aoLj&ttrO(yie5}6~@wfe^Z~p^Xa$P$B literal 0 HcmV?d00001 diff --git a/core/__pycache__/main.cpython-36.pyc b/core/__pycache__/main.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59086e2a9b5bb9112ebf1dfe1a049a7db558db47 GIT binary patch literal 6366 zcmbtYNpB;`mClWeD2k#aYFDXJs#Ud6TlH3JYhhK%t&#*v?dp!|hCne>s-$vZMTV+G zBIyf_!Jq**1m9->@e3;Xm3}X)CJosWC?8}_yU>GpqFCvptwX64Lf)OuX zym(u__c9_DdwMcgo(_ES*V~fxd+D{`0O0rW_x~tMk|7zgEs>~~i7dtnQADp2RrDIs zM4uo@^opHw(j+ZQjX8`|yUWRtjLbP`cDIuyS*M5eh;hQsIlZLU$&EWIY-Wkako9`oG0f6oV9glicC4vWLk`S>rz6;7zsmzA?`IVN4k3ekzeAL*ABVCjZ1kU}HuOdzQ`05wrG>>}~I;4n)@x zwN1C`8Rm=Bqr5l zL*}8!6rR#d6|L5mK2cgyOJ<3-)KXXyt;$lhbgc`Jb_yj}hII#88ys3m0Vf2UI*?jv zLuqvxYAb_(H_O&~urkYXfzp<1y@qBajO5Q0Lmib`J;17OU~_@g>J5^$e9+7F3sN2E zB;D9!eJ7fK42rywVEwxTtPteEVX)QL7-B&n2`?h!xE>BOOe_dVB??(1eY#0Q9Cy*8JS@T&?KPlh?&v3bjjZX-JCb8@Zrh6h)cXk^=pksZY< zPJdToSA)DUbmk8>Q@a-ABVKK#H5d#A>BH2CMn4G#4dqbf5WCLX6S?u5lc_Z&9UTtjCwEXs0@{?7q$3Ms&Pmj3Fj?ghiuq5nJyf zW+pmO#;Gtyq;on*T7|{$L6Sy^EVXo5YFTK^rIg9=0tn-b5t9i!3la_DO#H%hx3R59BM_ADFKx3J>u| z#O~kcYvR@Mcg)um{rogvQeqi1vUK~H=b>UZ#vx2&irvCsr)2Iu#g>Nr+zM^ckRx+-LYQw*0S4D>NIx)@b z1|RVpJ*^efKbc0YsGVy6e=*l}_7E!`wPj=GP=U1H2`SeEH(rOXvtf88ris3Iq8gn_ zVUPJf=I`X-uyUfkfONJdb~$ewH`tF*_io~UBd(DjMC)7voz`J#7p1XIcZCi`p#!&a zR#++MQK80+xtPOMW8PR0R9~TkAz^dBxDh6*wppQ}1Obv1YJ9i3!?%5vD#MPtIle<1 z*xd1rbDi%Pv9tB-*lC-^VzD{+g%g1;_Mo@*=FG}-vwFx^VGmvRESHJ>u<0}XWyQ7( zY~rzRe`!^8Iu4=Dj4l@VVEHGmMf3a$-?CCi2!3wrFOO_&cq@CBZLtOyeEl_oZZe$LBcyb|%wQ^-DhG8`Zzav(2#$Psi5ZH7e8) z6#8`Jp6Oz!xfRE3W*%Z)RBn@_I>cPVl$0wYj}> zQ|z~Ov3iNIO7+m6YGzBvRkJQ!j(2lu&9#_?x6tJJRr%ImxWqMdzlw7O23*tbjz4bS z*PG{GH@{=KUSV+P-Vrm4&5_p$glW^A6stsJG@8n`8WVsV^O|=Qwv+)hNzHR8BQ}M6a*lv&NfU51Q$vJ&y{S{twK?vd81#l#Zn0*_kJy z;`w3HZ}`k~XfM{%Ja2Hsk!TNZa%e*BIb(IwRTbypT7{CX=(P2t3Z+R-mEuhS4e8-= zwc9zjQHA4D`<81g*>+gs~PAMZXcEv=Ty+cX1g zs4?@H(JTk$O_F)M^JG0b0+QS*EL;@IWinVUJuH<=<=xHl+M~6N-Id3sl@GQ_qUzZm zCEBBM>0_dnHdaOcQl4NWyRy0Y!CGl|d1l{Sd7{D^8Co!Z^? z8QtAQA*}PO06GLLNj*jpz*(6jU6eHo2X+@;4doS*sz>)3L@mSogiwH4j!6=}ZJPB^ z1-DS+cO{{QI|fGkIf?v;p5uKVoUzBF>NKckE)eyVxkuD}+aL*^Q}a=`KvGe(%yj@v z@S+@d3nUZOrd7{1iB|RLKFLsXf7d{eR~gB8)QVv`&23Wm{CVCikOVggFs^k-x>M(g z=Hs>}l&w&9L_rLbm^!iCzetqd1N`OUEcHA#yK4G}%&X5HLvOfr&z!B|>R{HXSZ=Z22$Llj z7Zm3ESmE#UZ-tVPlXyg{X3%HltfDEJ%%8L}Bo6@w9&HjBPN@j0e!0NM$|OdhP@_JH zHItZ`5LB9+mq+l=%LN6im7FXdg>#q$c1%etS-BrrMIOSK%jXZLN(v6s!0K3c162Kh zGIBq(%mM@5gl1f;H}rtCG&CxFnHG@_a_&@%f}HgrztDaJm`P|} z5cAxUB>b6?X$hX5S*BOfg$nL{-sM30RfY2Jg-=q3wZCs$dnCszTk*88(QBaNv2`n| zTv|S%~D8?eC=^}X0JDd>$R#5sR z19GTkSUw|~eeBpI%VVhMiI1R9y2VJ8GC!V*ui-?Yj!_-Kym*TG6652WIZ;VTMzG@9 z>7Uj2SU_}3clH&C(YCmwi;A770f~LfHvOn{X9Wxg@*BG_<#@&spW~kbQ5mBul#B7j z=(;Ydzo*EkIz$!t6d7IOiNc6`^@wdTzRwed^9&nb?TOM(@@KTsEuSc`XXws_pD1Ib zuR|^Nb9Gz*Qc+`bFbf~mPP~Y45?TLRvkDu-^xcTVeg3iZx6q{JvnPvm$Rbb+2-7qm aMZUy>2c{Ok_K6 z&i&zEmlfqd%E(U+^RMvndMQPr3RO*|5szv^m2{$!lw+!q!k93}taKx-3YnxiZer~^Eq*G?rI^8&}Dop9+=-83km|zpV$=;bG1-yyg6itJ7R?fyTn`UQw=V*qWqFH*H z=6*;)Y8spgI!VvasS(auI!({fnGw!M^gO*lFR~eW>4&lYzRUCqy-Kgq>-5HuN9f0N zmVE@N0s1HOCjImT=N7$9KRdyhqj%_Ci9OggPc@on=SOe~^d7x0bq!=brwjDK3C=_M zh(12SS)@^Kaiq$) zUX}5^MoW^u*t>qL@`#=OfO`Yn|6x~|5n4G?dmkSu7ZkbUnv}fQn>|kO9K*=c%}+4i z1btPEpg+ZU3-lfO64Y%_pMhGG)EuZAIR*5eFuOjS-2vx-E`xVxH;s8u%&_~eoueya zenE*Tc72V2fxbFY%gXmx8~0eccfa>J zyVqMdk{(H`9vmm${q2nc*@r;*uNZ;&2;<}9B%eKaAZA09n~g=b2x?74_!8{uJsHx< zCFME1d-qko zD@%;8fDuX$AJ7u4`cz0Ar}$go7BMP^i!9Z9(JKjBtU=G~*s@-kuG8vKLPYC0>HPz8 z6(J??-zfI^l)_%_W`X~26;-Dy-zs+Et%$xPB4Bj%9x_ZhPX!So1D#$!kV3jarp2pM$y|r~#Ia53 zzfJZ6Q<%SFn1*j45aO8HPGBPnuMOLn+`u0d`r-Xd94jxZNhLSZ80396G^m9Gq?=W{%MGVcu;IG0AKZ>FxSU zK0ELWZr7bBZ#7YWvJA90D1)VPVC~FqBL~L^hQKCv&@uzc!oUBXO|I^DJ%V%}>-|Gq53+k zAj;78aQ=Ht0|sc+oQ^|q&`*F;*p9{rPQV`@35=Ww_gCumjX~~`KDXZ_MiUt(5}Y@h z0PU|@I0RuLq}ryV`-a^iu7k8fU9?2b1kT0o3@M$^1q!_{=`=g z7(TjufBU4SaYWRy`d#Jj7V079jC77Th?c%P>T5naB?O1fqpvNzQ#L$T^kVUUFR`g?W$y}A@83WfJ7{vRiuY`*1~4)=Ic++_G~OKIdJyZ_qZV?uIr@TS=iI(U?p zz`QZpjIWbshdY7mMM>9jqiLPeChU=qpm-(9h)0Te9Yq%x7^C*e_ z6J@0i0pvv~`JF`bkkh?rOkRDBtQ?y0a|sH3eo?rAeZ{6n zbGfo!*(}yqtLx3R%KCDBg-?mq>{hL^*(_qU&d&?RM6Fa@Zx+{!Yv0yZYy5)XO>R_6 zBLF-tn3>vYz0$-pj(-F)%9b{ZZ`PWE$xjK!7&6%K8!7P?YUE1Q>Z{dC^PA$@RwYX7 zE{voQ@wwqP&i-OUbF;W!uD<5ygocUw>g!5%s~+#-X9cQiH6~Un#WIYRST#N^*h$KO( z72*u=@M?}S&+#e>O!kF9^8SLaDRoNCiBB@CX60u(Ijv5sGwMuAjKBWPB&O6vBCTfB zOfsWl#P^Jv#!s{-l~!uVXch-wb{e T>SwTL3dpyT33XP^u&(|e*<30K literal 0 HcmV?d00001 diff --git a/core/__pycache__/randua.cpython-36.pyc b/core/__pycache__/randua.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c2034ae8211e5eaf831d7d2f8d7ddfdb803b872 GIT binary patch literal 2485 zcmcguTXWJt6i$Fr?8R!m;N@kXoMv=3HxfX+wA0qwX`wSMGj$keIw>b@V?%?Rpva34 z`e*z(zVhmmf59hDQqmF-#I_hF%kF0P`}Umu&e>IGXQ#ez&A&}gaoi8C>q%pM5ns22 z1#rOIT;=qiuka9qI3!>Kk}wG=xCT=&4Kpwc*I^EBz)hHkTW}lhz+Fhgy~A`R2KV8? zSFREVeuont{zOc!)N+eX%d%}l)Tl%jcC8a|KDwk-CbC4O0>KVFQy@(}Bao$f-8Og4 z{SC_#H8o3Boh)p;E*CeS5ZgL7$(mU`b_B9~#GINb>NuaOvaC>9R>_WWU@)sQt@VBo zsZQmq;mk{;QfmL7Qm4w8th6GIeIuhcH?5OKlQeVs-oNi*CWCvZWF(c!TDHfbEJD>K z9G$9HV_m;)#wJ!R>l>QSWvQ(8igk#gmlErRI4>KO8iqMlaygaedN*`jK{%-+WG1SdUh=W)#sA4XzWsGpP{<1lf{bwh0YBS zK8FZ@j+I>={syH?; zRQ>xx_{6ZXjIWDV7S7||@m}nUf2-h2LcE=12HqlS?PT@Hv8rZ!uHl+&&p5<@+_Np$ z`^K>eoE8+n9*N9xJaNTzk3FX@HeGg5XU@JURvl)FK2pP=^-t~T?ErMiM|tcs`%B<> zHi@N>V1BEe0>d*st7ftUHiJ*+OsBPEFz#8+frf3qn8B8-V3Fcei5TDai}m$%#w6pv E0naL|9RL6T literal 0 HcmV?d00001 diff --git a/core/__pycache__/request.cpython-36.pyc b/core/__pycache__/request.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbfcb900a3a26e9e22d2e73ffbb21fa781f0506a GIT binary patch literal 2289 zcmbW2&2Jk;6u@V8*I(<;I8NQP2v!h4M#8Ct#G$GXPP|DY(j@q!qzjd`@k|_V*1PWP zxU`m&iz~zhi38$);LedhqX#qxE}Z%=aDg|ocG?OdRm^&3-+a91c{A_#_LF?xG>%ul zd7ee+PxMMLK>i$R^eZ$35$q!;eq#qiU-Pw&>}$TR-nws$5>5ikF~d)eQcg|7(bjcSD#uyb@!!vcQ+7S#cIRPTt(X>%SlFjgGzHG@e#+^Hpn4rGa>tImMsz=)HUm(`QQu+L~51`!pQ z{({&B%>MP^CJRHp*`(1q567ENB6d1v;R)UBhm3A69IKC?!o`4V?}AEn3mTL#@!QzM znpVaJybWBz1@$c70#}NaT5$q)7%K z*Z3;?j#p-yx27Q16yzoEnpXu(e+OK2fczF%y-v*7>hk~8SXNnG2b;QgV*x$vgBTb3 zr4G<_s}_~EyZu!}OQLsIY92kpJXgEpjdNbucGV+)c!lPMd5bUda3iRr?-tSkqOEeYI; zw7M#rifq>8j6R^8Rb}_OY$QIh8|!j5e{ZOgNV|VVM=rZ5XC*@HEof>6v!txJ)FGzX zd$KFzKW^R!N$9(jH$aFiXi(DBZa`dDAhI=x^s)v~4z&W0DdYK5SW~O2t*Mg`SqV&C zC5TAmPT@Kt=mn?4fyk+;#m7+j>%^-Wd=w`xlQ@VR5b6(4>SLF|H%{girxaTu!>AvE zHYM@F#k#)wGREhil0`nwEOEYR$Ij-q4-dcCvllri*;ijtvC?hZtzKi-KIrsXjf3Xl zJz>5aQm88bUO6FkR_d72cSa$Z`1CWGT+~^Wa2lT6;wfo{E;B+-hpMHu^gI6mgX=l> literal 0 HcmV?d00001 diff --git a/core/__pycache__/updater.cpython-36.pyc b/core/__pycache__/updater.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd747cfd99d573a3c84f45ecea870f9a618d6018 GIT binary patch literal 1162 zcmZ`&%~KmU6qokHc8mc-CV^&JMzoVO&J=GZ$%#p57&3)kAOmd@++;e-O2)g$(niu6 z@R*bH&*abPRmb+kCFc&8_Q^JonM^dR_nw~K*Y7>;n7n4^~C9>>2q?sKo}w|tCfK*JBH6;Ql~8l`ivM%xW+@dF_KAv1~7xCI$d8TJNj z{Vx!M45oMi4KYT>quvl_{@GaTFCY_S;Q|_LhN-BXa;BqKOli2@F1r1j6PZXsHdC`z9ITRS<-RP)R@`F*l=Uxs@2?ZVj!ADE zjmS2+%12U7;$EEc_<*zJ3mlze7-|29*RtT0oI+Th9x-C%1l-T$kNrrzBdcq~V$r|# zyMyfTUqy82p8;Z$a48sBS$SWi%#h1|jC=9*29UeD|5K8a6$MfR-vi*}-aVqa*E;fO zEuyR!iGuU~{HO*-bde-X>vqAH`-@lQIDkd&0JzyKpZ^D|anpvmN`=c%zlCR1UalwE;B1G{TnO6S7rH3Z>u${F%q5(<-$kr(hbmlPx73nI`NbQr|*J7e7Wy zN)x?c`pC#UI?<|~kDf>(6^q7iO*QjVTb{^u@Y3G{LAZvi_zqseb&zIo1x|IKPF}<_ zUJa`yuxf;A4w#*C0bwElUE7Lc?+d1l9`$8pH!9~d@||<4Jnc$aaJE*1lP&?NxD4L^ D`d>!p literal 0 HcmV?d00001 diff --git a/core/__pycache__/utils.cpython-36.pyc b/core/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f32c8d60702b387a34a5e0d5b0ff5860e60d6a8 GIT binary patch literal 1718 zcmZ{kU2hvj6o%(x*D;Qh6eG1w#l=!73YKW1hF$Q*mCKLe_i#72T=g$-#e2qyleS`3XJ&Rj_dNTa^X1xF$x8O5 z5`Ag*u~PP;E0g!wpJJ08F(qLf_eay;8wtv*v)m@#agn*aEE4qd!s$Y3mAEqL=7maZ zI@F21`pB?{_Fb2bb{AR~iFM|2lG1W#_IARuL&Ndap0#5`KKT|McRDDG*V?+%A-U`2 zKo>r;`BZzJwj*?LPN?@z>bNvO@o`xE7c;9k=ijYeQHCn*?b>=#yupIJrP;M0TP=gXjnD;$rmr0|C}mSV2# zCfj!Vi+6y$D6*1b5Df8IkDe|7AUTMi1L!aSt)WJ)6|5_!X6RyMw$F(&8cw(IG*Hfp^$^ g>tafKCPa4q*|1dOf&Pl#!mnzI`X`N*db6?dAO9ATC;$Ke literal 0 HcmV?d00001 diff --git a/core/__pycache__/verbout.cpython-36.pyc b/core/__pycache__/verbout.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69009a2f3ed3d3950a9c395e4080f4f70ab01954 GIT binary patch literal 414 zcmYjMu};G<5IwsIDJ2yMA+dS_g`pW(5kk<4tqhc&v+uOu?|t6v-yd;+FW3Opa6w@NQ3@$5VB#__*n&?x4AM^Oy~ET? z{ac8(zDWfUYuE{qKO9}&T_?`M!Q~Fc6@@tMrW1Axr`IkGH#v7yLwr>)^^wJ;SoXLalK7>9Qn '+error.__str__() - logger('errors', content) + con = '(i) '+url+' -> '+error.__str__() + SCAN_ERRORS.append(con) diff --git a/core/main.py b/core/main.py index 34082b5..3408b74 100644 --- a/core/main.py +++ b/core/main.py @@ -37,12 +37,13 @@ from core.verbout import verbout from core.forms import form10, form20 from core.banner import banner, banabout -from core.logger import ErrorLogger, LinkLogger +from core.logger import ErrorLogger, GetLogger # Imports from files from files import config # Necessary evil :( from files.config import * +from files.discovered import FORMS_TESTED # Imports from modules from modules import Debugger @@ -118,6 +119,7 @@ def Engine(): # lets begin it! 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\n'+color.CYAN+' %s' % (m.prettify())) + FORMS_TESTED.append(m.prettify()) try: if m['action']: pass @@ -269,7 +271,7 @@ def Engine(): # lets begin it! verbout(R, 'Error Code : ' +O+ str(e.code)) ErrorLogger(url, e.__str__()) quit() - LinkLogger() # The scanning has finished, so now we can log out all the links ;) + 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 except KeyboardInterrupt as e: # Incase user wants to exit :') (while crawling) diff --git a/core/options.py b/core/options.py index e49eda2..4b84b3e 100644 --- a/core/options.py +++ b/core/options.py @@ -10,7 +10,7 @@ # https://github.com/0xInfection/XSRFProbe # Importing stuff -import argparse, sys +import argparse, sys, tld import urllib.parse, os from files import config from core.colors import R, G @@ -136,22 +136,20 @@ config.USER_AGENT = '' if config.SITE_URL: - if args.output: # If output directory is mentioned... try: - if not os.path.exists(args.output+config.SITE_URL.split('//')[1].replace('/', '-')): - os.makedirs(args.output) + 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+config.SITE_URL.split('//')[1].replace('/', '-') + '/' - + config.OUTPUT_DIR = args.output+tld.get_fld(config.SITE_URL) + '/' else: try: - os.makedirs(config.SITE_URL.split('//')[1].replace('/', '-')) + os.makedirs(tld.get_fld(config.SITE_URL)) except FileExistsError: pass - config.OUTPUT_DIR = config.SITE_URL.split('//')[1].replace('/', '-') + '/' + config.OUTPUT_DIR = tld.get_fld(config.SITE_URL) + '/' if args.quiet: config.DEBUG = False From fe11e7f7985637f900c0e201a18f18085ab6a4a9 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 14 Dec 2018 07:47:07 +0000 Subject: [PATCH 35/94] Added verbatim efficiency for better output --- modules/Generator.py | 4 ++-- modules/Origin.py | 2 +- modules/Referer.py | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/Generator.py b/modules/Generator.py index 7670b4b..1e9666b 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -50,7 +50,7 @@ def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www- for i in m.splitlines(): print(' '+i) print('') - fi = open(OUTPUT_DIR+'post-csrf-poc.html', 'w+', encoding='utf8') + fi = open(OUTPUT_DIR+action+'-csrf-poc.html', 'w+', encoding='utf8') fi.write(m) fi.close() - print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+'post-csrf-poc.html') + print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+action+'-csrf-poc.html') diff --git a/modules/Origin.py b/modules/Origin.py index 61b2018..1ea98f2 100644 --- a/modules/Origin.py +++ b/modules/Origin.py @@ -38,7 +38,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 diff --git a/modules/Referer.py b/modules/Referer.py index a531158..d4958ee 100644 --- a/modules/Referer.py +++ b/modules/Referer.py @@ -31,7 +31,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 +39,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 From b3b31c992fd5756cacfb9052b56ca1c57ede6393 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 14 Dec 2018 07:47:19 +0000 Subject: [PATCH 36/94] Addition of new vars for better logging --- files/discovered.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/files/discovered.py b/files/discovered.py index f3d2306..e1eecde 100644 --- a/files/discovered.py +++ b/files/discovered.py @@ -23,3 +23,9 @@ # Files discovered during crawling FILES_EXEC = [] + +# Forms that were tested +FORMS_TESTED = [] + +# Errors that were encountered +SCAN_ERRORS = [] From 3e0a293c50811c1da680b4c9cb0a46f1fa2ec8c9 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 14 Dec 2018 07:47:32 +0000 Subject: [PATCH 37/94] Cache cleaned --- core/__pycache__/__init__.cpython-36.pyc | Bin 123 -> 0 bytes core/__pycache__/banner.cpython-36.pyc | Bin 2558 -> 0 bytes core/__pycache__/colors.cpython-36.pyc | Bin 1002 -> 0 bytes core/__pycache__/forms.cpython-36.pyc | Bin 3041 -> 0 bytes core/__pycache__/inputin.cpython-36.pyc | Bin 781 -> 0 bytes core/__pycache__/logger.cpython-36.pyc | Bin 1473 -> 0 bytes core/__pycache__/main.cpython-36.pyc | Bin 6366 -> 0 bytes core/__pycache__/options.cpython-36.pyc | Bin 4286 -> 0 bytes core/__pycache__/randua.cpython-36.pyc | Bin 2485 -> 0 bytes core/__pycache__/request.cpython-36.pyc | Bin 2289 -> 0 bytes core/__pycache__/updater.cpython-36.pyc | Bin 1162 -> 0 bytes core/__pycache__/utils.cpython-36.pyc | Bin 1718 -> 0 bytes core/__pycache__/verbout.cpython-36.pyc | Bin 414 -> 0 bytes 13 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 core/__pycache__/__init__.cpython-36.pyc delete mode 100644 core/__pycache__/banner.cpython-36.pyc delete mode 100644 core/__pycache__/colors.cpython-36.pyc delete mode 100644 core/__pycache__/forms.cpython-36.pyc delete mode 100644 core/__pycache__/inputin.cpython-36.pyc delete mode 100644 core/__pycache__/logger.cpython-36.pyc delete mode 100644 core/__pycache__/main.cpython-36.pyc delete mode 100644 core/__pycache__/options.cpython-36.pyc delete mode 100644 core/__pycache__/randua.cpython-36.pyc delete mode 100644 core/__pycache__/request.cpython-36.pyc delete mode 100644 core/__pycache__/updater.cpython-36.pyc delete mode 100644 core/__pycache__/utils.cpython-36.pyc delete mode 100644 core/__pycache__/verbout.cpython-36.pyc diff --git a/core/__pycache__/__init__.cpython-36.pyc b/core/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 0ea645e8a936dcf26b3ebba5cfd7c1846abf7d26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123 zcmXr!<>gY@%oDShfq~&M5W@i@kmUfx#VkM~g&~+hlhJP_LlH(61;iN-HSJPfFEK&M!*UkB`sH%PfhH*DI*J#bJ}1pHiBWY6mi*7>F4Fhe8|d diff --git a/core/__pycache__/banner.cpython-36.pyc b/core/__pycache__/banner.cpython-36.pyc deleted file mode 100644 index 26e666653ed2ba6d5f75036580edf9a36da51c2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2558 zcmbVOO>Y}T7~a_r+gX3mgccVFFiq1Gqc}!F;($sUH*qVZ%0y0zND0~|o=M`3<2AEu zsU0jX;ZM+i&m|(BN6E4HV3`T~=4zA{qJR!_t_OHehlR2yatHsQ1qfz`Dm_~!dnu`0+L3M>WmO0BEHE?Wf_s>+svvP8r)%aUHR(UA&qX6ls^3qmb;;B7& z%_;D(J!yeQ=DnrOs09+9ud+h-D1Az43)_+9DaLvpE^32RwQ+L~6rnQNLw7*L$)l^c zlO%LcZ7FSJAy9?hg_L*T!Nl}wa}7Sp23ZulP#(2Y4XCur16UI(P1VVN#%f8Akf`N( z@WEhddQ<2sIT%pn9N5!6bV>^jAxr4PSUCrY5_56`1n8Xi&#PA$-9jgfM!LdbzlJCk zlSIw5lL)MlQ(U)Eh@G(*jH1|%2B$oB!XfA5*y#tuK*WV_d+qMSPHgo$n=cJffrixd zdZi_TAZl&$@FWVxt!JS)9E;$9xB7wLt%Lp1h>NxH^SF?h^C+c@Fd1fvxTFTppYVt` zo*EG~@V}+PHULV6*9oey(q961hWMbo%v2U=E$T2}cpFi9iH{iGaL{Y8KzWr)7yKeC zrs|DK4(hALQGtmMUWN)xeAqKoTjIl>q1q;SL2WOIM=ok_(kp6j5(V0;jk>%WJR9n= zI$Km`3unZyoEd)g%up!$QI(S#pv7<3P+D=VE3d-4|DoAzCNiOZ3i!kRe!KU8ZsGe| zl_qV=MY8RAH82xci?&_*# z^#5t78gRMtgUE&drjnIkC~Z%o<3P}_s9f1O=JcD`!YKvDg&|!9Bqf$F zIMo(njt0^3`itYQ49(Q8d%b@UOrj;&h1X#+%=PqUe+lbqYTqZK o4!(vRmr)tuTLW1MlP2)GE3dx7ozs9#hWswlLOfN8l+4onzfv9t`v3p{ diff --git a/core/__pycache__/colors.cpython-36.pyc b/core/__pycache__/colors.cpython-36.pyc deleted file mode 100644 index 819d29820158c808091d24dd5f40689929877872..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1002 zcmZ{jO>fgc5QcaC6~~E_5(-H4QV>E42tu3Es47Bf8!JMJoXRMNtU*@DrUx3w!A>Q} z75)rAhwENB@fSGpPD0WTfvxxP?Cg4HJUgztuJfZ?c^8|E{bW}z9d;AS&H%|IA2Y#5 z>?4=jxt=x`mQGfiNkf|7m^8m?LX(!zWlk8<7N*P#OFAMqd5ATID{G1d6FOu-CS-}6 zZ6jF{k(Ask0I4dHZzGZ4$De~c085u`7JN#yzwz{(m(Q*fz^Gb9S2Yw>VIeH9n@}t z-;Zl)lFVwYQFcB{rnS#mdNxgy52M;JNeQS;_I&y!HSk6W%I*U;H~w=k*3Bza#_g@} zpdN=0-cx0_FMT_4`0S>y5rMpR`QaAlxEU2)7BFgmvMU z)?M;ZX&*rueTL1`dw5iBr6rj%r~{Vi@UG%G!gio|Tk(^B?WF8i%MN-?Ib{^u=9|1! KUY(Yum$X00?#qP$ diff --git a/core/__pycache__/forms.cpython-36.pyc b/core/__pycache__/forms.cpython-36.pyc deleted file mode 100644 index 7800603dac565e1a2bd34368b856971098e9bcfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3041 zcmeHJUyIW~5Knuj9LEVh_yLwhg%ff~tJbPX6cnH25cOU}DBJAzx|n3+?&kiXZ+;n{ z{WiY(Eqrojo1|$`^hHEyAd_uoW@mQhH^21ul`Ff~zwEso?0McduXFWb{TOa@4Fvai z@00h&<9)sb&mP}?|N+hf* zBeb8XV_EnZhbFrOu87N%1O-VmW=u>`=SwT{g_q#c`UT5{f2vto3SIk=C}UHR5ipNw zYebVHh=S%1NMuS@tn8}A`$}rTDY4RKBBrnB(vX=H87Hu4)CV-V6$R*>M1hO4NR>pg zC@V|sc?rH&oWa3zc9x0a*v?}*3Ms+JF-5tSJtan7fNF4;k`tCyZUbWnbR|LDvrU7N zpe|8>HEOH1;;~k!5=!{nLZJ~&UVuDRn*a4eVK@xb9-Q637>j#Nv0OF|Ag+sxpX+t) zRBz?@zb{y~Wu}_u5}LN?k}5krYE*S^o~vUb3qW@ll#NZ(mJVpK4Sps8>Pb{(NtDUf zQ)>5?k+~MLm8rsk2(&l`H-L46$tPMcD+nWnI3;L;6Q-)d0xWACYU-C!z!0RehQ~54 z3=<~vtFX(a)T8hcW&?)XMQxPiL^2}$oXHG=|IA`7e1KB$Xx! z2fq$UO*56gYlEU<;@E3aE86`R2M#c;gnah)dvA7d9iD{)jeP6G_Zxv$$_7t`d1qA_ zoEbeUwVH|`Ra!V_b5Ndty^OAd5P04*+aSEF+kL$S&kcQoa;`MD_&(HaU_qg HcCP&ZmI`A?PRb8*EyE4yewcyXM`j;z7z{|;yT%hpuMOIpadkFL7*cqI;1Avm zyJzqFZzrb0EYr1u_6kk@L{LQdid420N8{e+Nc zoqVgk8-9!``2c}X8*J)>k4B*i0W_hGzK+a?V>Hpis7|8$uuYhgQ=s$fCy%GQb diff --git a/core/__pycache__/logger.cpython-36.pyc b/core/__pycache__/logger.cpython-36.pyc deleted file mode 100644 index 06e74784eef78c36df83f95571c838dec9737e24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1473 zcmZWpUvJws5GSd>mYtwWGkDm`gboYl47Gu64?{2n3u+?`V5psvAZ^gWa78*%EK3SW z$B7|7xv#@M$G#F@_q4CjryVJy3pNV8JMwtsPxm|WhpnxkeDn0(2M3|Q(4$7c{sp>t z34$VuGjypn$?#dNiP2VO&Fo7XBWhFU1G;pmOFhso^=SjNM+4df?NfY;I<3E9HxzZS z!uue+JI3D^bD8MVbORKq3H=3hAwft~p*Eb}Mk^w5MJS;bwSTw%c)hYDsqh+0YwC+1 zH?}?Xd3?WD;Xe9pXJuE`jZ*=xHd3ddO%%4-%1IiF0z1q0_iucLINZj0*_;u1byWF{h~ zp-9Gg4uh`Qerb1MyCqqCr|h|$`~(T{S)LRWRwx`q9ajaf&d*+-pN;w_v9gPj<;s>1 zB~y-^ml;!bmI|qy1y3dGILe>EiTQZOl$R8_WVuvL$Zk28BkfUmqVVib#CIUEANP8^ zC}gkC#8eh#?_Tgp$&2@_mlT}!^nWtmEgzIyo1bY$wm~4Lg`eOraDaD7fcY-WBv{Ww zv!H7V4SjcDNF*`EYl5{!ZY)Yb+CZq))?*+>&%#*GX_{c={r45Ran}ea+7>I|m zgLPg7Wk6jMo>h-sW7qC0^sHdZ;4+J)rlm8J|@-&wsgnSs{uGyF}F2b=0IZIf2 z$7q=5+GQeZ3jdXy-OibS_`KjVApc=Q;pedl)&y^M>~_1sl7!)<_i}L5AH=60@d0-s z7fW&wDyaPi*J)DZI95(lWCd6DaXfgd8s`x#AD%=5WeuXfa)8vCQ1*+rN0D-0#YfTc zpkpZus8`mMJt*sLoEa`_@l@~Xmm3&g!AJxkkQ3m2yn~-%{tQNmp3+Torhyo)z666} zxZG%lSKyLx%__qNMUYQB;(9q0=+;SeHi)C6;pjXbo-Vf`r;O+0>^aoLj&ttrO(yie5}6~@wfe^Z~p^Xa$P$B diff --git a/core/__pycache__/main.cpython-36.pyc b/core/__pycache__/main.cpython-36.pyc deleted file mode 100644 index 59086e2a9b5bb9112ebf1dfe1a049a7db558db47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6366 zcmbtYNpB;`mClWeD2k#aYFDXJs#Ud6TlH3JYhhK%t&#*v?dp!|hCne>s-$vZMTV+G zBIyf_!Jq**1m9->@e3;Xm3}X)CJosWC?8}_yU>GpqFCvptwX64Lf)OuX zym(u__c9_DdwMcgo(_ES*V~fxd+D{`0O0rW_x~tMk|7zgEs>~~i7dtnQADp2RrDIs zM4uo@^opHw(j+ZQjX8`|yUWRtjLbP`cDIuyS*M5eh;hQsIlZLU$&EWIY-Wkako9`oG0f6oV9glicC4vWLk`S>rz6;7zsmzA?`IVN4k3ekzeAL*ABVCjZ1kU}HuOdzQ`05wrG>>}~I;4n)@x zwN1C`8Rm=Bqr5l zL*}8!6rR#d6|L5mK2cgyOJ<3-)KXXyt;$lhbgc`Jb_yj}hII#88ys3m0Vf2UI*?jv zLuqvxYAb_(H_O&~urkYXfzp<1y@qBajO5Q0Lmib`J;17OU~_@g>J5^$e9+7F3sN2E zB;D9!eJ7fK42rywVEwxTtPteEVX)QL7-B&n2`?h!xE>BOOe_dVB??(1eY#0Q9Cy*8JS@T&?KPlh?&v3bjjZX-JCb8@Zrh6h)cXk^=pksZY< zPJdToSA)DUbmk8>Q@a-ABVKK#H5d#A>BH2CMn4G#4dqbf5WCLX6S?u5lc_Z&9UTtjCwEXs0@{?7q$3Ms&Pmj3Fj?ghiuq5nJyf zW+pmO#;Gtyq;on*T7|{$L6Sy^EVXo5YFTK^rIg9=0tn-b5t9i!3la_DO#H%hx3R59BM_ADFKx3J>u| z#O~kcYvR@Mcg)um{rogvQeqi1vUK~H=b>UZ#vx2&irvCsr)2Iu#g>Nr+zM^ckRx+-LYQw*0S4D>NIx)@b z1|RVpJ*^efKbc0YsGVy6e=*l}_7E!`wPj=GP=U1H2`SeEH(rOXvtf88ris3Iq8gn_ zVUPJf=I`X-uyUfkfONJdb~$ewH`tF*_io~UBd(DjMC)7voz`J#7p1XIcZCi`p#!&a zR#++MQK80+xtPOMW8PR0R9~TkAz^dBxDh6*wppQ}1Obv1YJ9i3!?%5vD#MPtIle<1 z*xd1rbDi%Pv9tB-*lC-^VzD{+g%g1;_Mo@*=FG}-vwFx^VGmvRESHJ>u<0}XWyQ7( zY~rzRe`!^8Iu4=Dj4l@VVEHGmMf3a$-?CCi2!3wrFOO_&cq@CBZLtOyeEl_oZZe$LBcyb|%wQ^-DhG8`Zzav(2#$Psi5ZH7e8) z6#8`Jp6Oz!xfRE3W*%Z)RBn@_I>cPVl$0wYj}> zQ|z~Ov3iNIO7+m6YGzBvRkJQ!j(2lu&9#_?x6tJJRr%ImxWqMdzlw7O23*tbjz4bS z*PG{GH@{=KUSV+P-Vrm4&5_p$glW^A6stsJG@8n`8WVsV^O|=Qwv+)hNzHR8BQ}M6a*lv&NfU51Q$vJ&y{S{twK?vd81#l#Zn0*_kJy z;`w3HZ}`k~XfM{%Ja2Hsk!TNZa%e*BIb(IwRTbypT7{CX=(P2t3Z+R-mEuhS4e8-= zwc9zjQHA4D`<81g*>+gs~PAMZXcEv=Ty+cX1g zs4?@H(JTk$O_F)M^JG0b0+QS*EL;@IWinVUJuH<=<=xHl+M~6N-Id3sl@GQ_qUzZm zCEBBM>0_dnHdaOcQl4NWyRy0Y!CGl|d1l{Sd7{D^8Co!Z^? z8QtAQA*}PO06GLLNj*jpz*(6jU6eHo2X+@;4doS*sz>)3L@mSogiwH4j!6=}ZJPB^ z1-DS+cO{{QI|fGkIf?v;p5uKVoUzBF>NKckE)eyVxkuD}+aL*^Q}a=`KvGe(%yj@v z@S+@d3nUZOrd7{1iB|RLKFLsXf7d{eR~gB8)QVv`&23Wm{CVCikOVggFs^k-x>M(g z=Hs>}l&w&9L_rLbm^!iCzetqd1N`OUEcHA#yK4G}%&X5HLvOfr&z!B|>R{HXSZ=Z22$Llj z7Zm3ESmE#UZ-tVPlXyg{X3%HltfDEJ%%8L}Bo6@w9&HjBPN@j0e!0NM$|OdhP@_JH zHItZ`5LB9+mq+l=%LN6im7FXdg>#q$c1%etS-BrrMIOSK%jXZLN(v6s!0K3c162Kh zGIBq(%mM@5gl1f;H}rtCG&CxFnHG@_a_&@%f}HgrztDaJm`P|} z5cAxUB>b6?X$hX5S*BOfg$nL{-sM30RfY2Jg-=q3wZCs$dnCszTk*88(QBaNv2`n| zTv|S%~D8?eC=^}X0JDd>$R#5sR z19GTkSUw|~eeBpI%VVhMiI1R9y2VJ8GC!V*ui-?Yj!_-Kym*TG6652WIZ;VTMzG@9 z>7Uj2SU_}3clH&C(YCmwi;A770f~LfHvOn{X9Wxg@*BG_<#@&spW~kbQ5mBul#B7j z=(;Ydzo*EkIz$!t6d7IOiNc6`^@wdTzRwed^9&nb?TOM(@@KTsEuSc`XXws_pD1Ib zuR|^Nb9Gz*Qc+`bFbf~mPP~Y45?TLRvkDu-^xcTVeg3iZx6q{JvnPvm$Rbb+2-7qm aMZUy>2c{Ok_K6 z&i&zEmlfqd%E(U+^RMvndMQPr3RO*|5szv^m2{$!lw+!q!k93}taKx-3YnxiZer~^Eq*G?rI^8&}Dop9+=-83km|zpV$=;bG1-yyg6itJ7R?fyTn`UQw=V*qWqFH*H z=6*;)Y8spgI!VvasS(auI!({fnGw!M^gO*lFR~eW>4&lYzRUCqy-Kgq>-5HuN9f0N zmVE@N0s1HOCjImT=N7$9KRdyhqj%_Ci9OggPc@on=SOe~^d7x0bq!=brwjDK3C=_M zh(12SS)@^Kaiq$) zUX}5^MoW^u*t>qL@`#=OfO`Yn|6x~|5n4G?dmkSu7ZkbUnv}fQn>|kO9K*=c%}+4i z1btPEpg+ZU3-lfO64Y%_pMhGG)EuZAIR*5eFuOjS-2vx-E`xVxH;s8u%&_~eoueya zenE*Tc72V2fxbFY%gXmx8~0eccfa>J zyVqMdk{(H`9vmm${q2nc*@r;*uNZ;&2;<}9B%eKaAZA09n~g=b2x?74_!8{uJsHx< zCFME1d-qko zD@%;8fDuX$AJ7u4`cz0Ar}$go7BMP^i!9Z9(JKjBtU=G~*s@-kuG8vKLPYC0>HPz8 z6(J??-zfI^l)_%_W`X~26;-Dy-zs+Et%$xPB4Bj%9x_ZhPX!So1D#$!kV3jarp2pM$y|r~#Ia53 zzfJZ6Q<%SFn1*j45aO8HPGBPnuMOLn+`u0d`r-Xd94jxZNhLSZ80396G^m9Gq?=W{%MGVcu;IG0AKZ>FxSU zK0ELWZr7bBZ#7YWvJA90D1)VPVC~FqBL~L^hQKCv&@uzc!oUBXO|I^DJ%V%}>-|Gq53+k zAj;78aQ=Ht0|sc+oQ^|q&`*F;*p9{rPQV`@35=Ww_gCumjX~~`KDXZ_MiUt(5}Y@h z0PU|@I0RuLq}ryV`-a^iu7k8fU9?2b1kT0o3@M$^1q!_{=`=g z7(TjufBU4SaYWRy`d#Jj7V079jC77Th?c%P>T5naB?O1fqpvNzQ#L$T^kVUUFR`g?W$y}A@83WfJ7{vRiuY`*1~4)=Ic++_G~OKIdJyZ_qZV?uIr@TS=iI(U?p zz`QZpjIWbshdY7mMM>9jqiLPeChU=qpm-(9h)0Te9Yq%x7^C*e_ z6J@0i0pvv~`JF`bkkh?rOkRDBtQ?y0a|sH3eo?rAeZ{6n zbGfo!*(}yqtLx3R%KCDBg-?mq>{hL^*(_qU&d&?RM6Fa@Zx+{!Yv0yZYy5)XO>R_6 zBLF-tn3>vYz0$-pj(-F)%9b{ZZ`PWE$xjK!7&6%K8!7P?YUE1Q>Z{dC^PA$@RwYX7 zE{voQ@wwqP&i-OUbF;W!uD<5ygocUw>g!5%s~+#-X9cQiH6~Un#WIYRST#N^*h$KO( z72*u=@M?}S&+#e>O!kF9^8SLaDRoNCiBB@CX60u(Ijv5sGwMuAjKBWPB&O6vBCTfB zOfsWl#P^Jv#!s{-l~!uVXch-wb{e T>SwTL3dpyT33XP^u&(|e*<30K diff --git a/core/__pycache__/randua.cpython-36.pyc b/core/__pycache__/randua.cpython-36.pyc deleted file mode 100644 index 3c2034ae8211e5eaf831d7d2f8d7ddfdb803b872..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2485 zcmcguTXWJt6i$Fr?8R!m;N@kXoMv=3HxfX+wA0qwX`wSMGj$keIw>b@V?%?Rpva34 z`e*z(zVhmmf59hDQqmF-#I_hF%kF0P`}Umu&e>IGXQ#ez&A&}gaoi8C>q%pM5ns22 z1#rOIT;=qiuka9qI3!>Kk}wG=xCT=&4Kpwc*I^EBz)hHkTW}lhz+Fhgy~A`R2KV8? zSFREVeuont{zOc!)N+eX%d%}l)Tl%jcC8a|KDwk-CbC4O0>KVFQy@(}Bao$f-8Og4 z{SC_#H8o3Boh)p;E*CeS5ZgL7$(mU`b_B9~#GINb>NuaOvaC>9R>_WWU@)sQt@VBo zsZQmq;mk{;QfmL7Qm4w8th6GIeIuhcH?5OKlQeVs-oNi*CWCvZWF(c!TDHfbEJD>K z9G$9HV_m;)#wJ!R>l>QSWvQ(8igk#gmlErRI4>KO8iqMlaygaedN*`jK{%-+WG1SdUh=W)#sA4XzWsGpP{<1lf{bwh0YBS zK8FZ@j+I>={syH?; zRQ>xx_{6ZXjIWDV7S7||@m}nUf2-h2LcE=12HqlS?PT@Hv8rZ!uHl+&&p5<@+_Np$ z`^K>eoE8+n9*N9xJaNTzk3FX@HeGg5XU@JURvl)FK2pP=^-t~T?ErMiM|tcs`%B<> zHi@N>V1BEe0>d*st7ftUHiJ*+OsBPEFz#8+frf3qn8B8-V3Fcei5TDai}m$%#w6pv E0naL|9RL6T diff --git a/core/__pycache__/request.cpython-36.pyc b/core/__pycache__/request.cpython-36.pyc deleted file mode 100644 index bbfcb900a3a26e9e22d2e73ffbb21fa781f0506a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2289 zcmbW2&2Jk;6u@V8*I(<;I8NQP2v!h4M#8Ct#G$GXPP|DY(j@q!qzjd`@k|_V*1PWP zxU`m&iz~zhi38$);LedhqX#qxE}Z%=aDg|ocG?OdRm^&3-+a91c{A_#_LF?xG>%ul zd7ee+PxMMLK>i$R^eZ$35$q!;eq#qiU-Pw&>}$TR-nws$5>5ikF~d)eQcg|7(bjcSD#uyb@!!vcQ+7S#cIRPTt(X>%SlFjgGzHG@e#+^Hpn4rGa>tImMsz=)HUm(`QQu+L~51`!pQ z{({&B%>MP^CJRHp*`(1q567ENB6d1v;R)UBhm3A69IKC?!o`4V?}AEn3mTL#@!QzM znpVaJybWBz1@$c70#}NaT5$q)7%K z*Z3;?j#p-yx27Q16yzoEnpXu(e+OK2fczF%y-v*7>hk~8SXNnG2b;QgV*x$vgBTb3 zr4G<_s}_~EyZu!}OQLsIY92kpJXgEpjdNbucGV+)c!lPMd5bUda3iRr?-tSkqOEeYI; zw7M#rifq>8j6R^8Rb}_OY$QIh8|!j5e{ZOgNV|VVM=rZ5XC*@HEof>6v!txJ)FGzX zd$KFzKW^R!N$9(jH$aFiXi(DBZa`dDAhI=x^s)v~4z&W0DdYK5SW~O2t*Mg`SqV&C zC5TAmPT@Kt=mn?4fyk+;#m7+j>%^-Wd=w`xlQ@VR5b6(4>SLF|H%{girxaTu!>AvE zHYM@F#k#)wGREhil0`nwEOEYR$Ij-q4-dcCvllri*;ijtvC?hZtzKi-KIrsXjf3Xl zJz>5aQm88bUO6FkR_d72cSa$Z`1CWGT+~^Wa2lT6;wfo{E;B+-hpMHu^gI6mgX=l> diff --git a/core/__pycache__/updater.cpython-36.pyc b/core/__pycache__/updater.cpython-36.pyc deleted file mode 100644 index fd747cfd99d573a3c84f45ecea870f9a618d6018..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1162 zcmZ`&%~KmU6qokHc8mc-CV^&JMzoVO&J=GZ$%#p57&3)kAOmd@++;e-O2)g$(niu6 z@R*bH&*abPRmb+kCFc&8_Q^JonM^dR_nw~K*Y7>;n7n4^~C9>>2q?sKo}w|tCfK*JBH6;Ql~8l`ivM%xW+@dF_KAv1~7xCI$d8TJNj z{Vx!M45oMi4KYT>quvl_{@GaTFCY_S;Q|_LhN-BXa;BqKOli2@F1r1j6PZXsHdC`z9ITRS<-RP)R@`F*l=Uxs@2?ZVj!ADE zjmS2+%12U7;$EEc_<*zJ3mlze7-|29*RtT0oI+Th9x-C%1l-T$kNrrzBdcq~V$r|# zyMyfTUqy82p8;Z$a48sBS$SWi%#h1|jC=9*29UeD|5K8a6$MfR-vi*}-aVqa*E;fO zEuyR!iGuU~{HO*-bde-X>vqAH`-@lQIDkd&0JzyKpZ^D|anpvmN`=c%zlCR1UalwE;B1G{TnO6S7rH3Z>u${F%q5(<-$kr(hbmlPx73nI`NbQr|*J7e7Wy zN)x?c`pC#UI?<|~kDf>(6^q7iO*QjVTb{^u@Y3G{LAZvi_zqseb&zIo1x|IKPF}<_ zUJa`yuxf;A4w#*C0bwElUE7Lc?+d1l9`$8pH!9~d@||<4Jnc$aaJE*1lP&?NxD4L^ D`d>!p diff --git a/core/__pycache__/utils.cpython-36.pyc b/core/__pycache__/utils.cpython-36.pyc deleted file mode 100644 index 1f32c8d60702b387a34a5e0d5b0ff5860e60d6a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1718 zcmZ{kU2hvj6o%(x*D;Qh6eG1w#l=!73YKW1hF$Q*mCKLe_i#72T=g$-#e2qyleS`3XJ&Rj_dNTa^X1xF$x8O5 z5`Ag*u~PP;E0g!wpJJ08F(qLf_eay;8wtv*v)m@#agn*aEE4qd!s$Y3mAEqL=7maZ zI@F21`pB?{_Fb2bb{AR~iFM|2lG1W#_IARuL&Ndap0#5`KKT|McRDDG*V?+%A-U`2 zKo>r;`BZzJwj*?LPN?@z>bNvO@o`xE7c;9k=ijYeQHCn*?b>=#yupIJrP;M0TP=gXjnD;$rmr0|C}mSV2# zCfj!Vi+6y$D6*1b5Df8IkDe|7AUTMi1L!aSt)WJ)6|5_!X6RyMw$F(&8cw(IG*Hfp^$^ g>tafKCPa4q*|1dOf&Pl#!mnzI`X`N*db6?dAO9ATC;$Ke diff --git a/core/__pycache__/verbout.cpython-36.pyc b/core/__pycache__/verbout.cpython-36.pyc deleted file mode 100644 index 69009a2f3ed3d3950a9c395e4080f4f70ab01954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 414 zcmYjMu};G<5IwsIDJ2yMA+dS_g`pW(5kk<4tqhc&v+uOu?|t6v-yd;+FW3Opa6w@NQ3@$5VB#__*n&?x4AM^Oy~ET? z{ac8(zDWfUYuE{qKO9}&T_?`M!Q~Fc6@@tMrW1Axr`IkGH#v7yLwr>)^^wJ;SoXLalK7>9Qn Date: Fri, 14 Dec 2018 08:08:49 +0000 Subject: [PATCH 38/94] Trail to fix build errors --- core/logger.py | 2 +- modules/Generator.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/logger.py b/core/logger.py index 9cfac50..7f7fc7b 100644 --- a/core/logger.py +++ b/core/logger.py @@ -13,6 +13,7 @@ from core.colors import * from files.config import * from core.verbout import verbout +from files.discovered import INTERNAL_URLS, FILES_EXEC, SCAN_ERRORS, FORMS_TESTED, REQUEST_TOKENS def logger(filename, content): ''' @@ -40,7 +41,6 @@ def pheaders(tup): verbout('','') def GetLogger(): - from files.discovered import INTERNAL_URLS, FILES_EXEC, SCAN_ERRORS, FORMS_TESTED, REQUEST_TOKENS if INTERNAL_URLS: logger('internal-links', INTERNAL_URLS) if SCAN_ERRORS: diff --git a/modules/Generator.py b/modules/Generator.py index 1e9666b..ac4be2e 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -50,7 +50,7 @@ def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www- for i in m.splitlines(): print(' '+i) print('') - fi = open(OUTPUT_DIR+action+'-csrf-poc.html', 'w+', encoding='utf8') + fi = open(OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-csrf-poc.html', 'w+', encoding='utf8') fi.write(m) fi.close() - print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+action+'-csrf-poc.html') + print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-csrf-poc.html') From 8c53c832bddab5a39337290a31beacb950355fb5 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 15 Dec 2018 03:20:02 +0000 Subject: [PATCH 39/94] Added detailed error logging system --- core/logger.py | 9 ++++++++- core/main.py | 11 ++++++++++- core/options.py | 1 + core/verbout.py | 6 +++++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/core/logger.py b/core/logger.py index 7f7fc7b..efb47ad 100644 --- a/core/logger.py +++ b/core/logger.py @@ -13,7 +13,8 @@ from core.colors import * from files.config import * from core.verbout import verbout -from files.discovered import INTERNAL_URLS, FILES_EXEC, SCAN_ERRORS, FORMS_TESTED, REQUEST_TOKENS +from files.discovered import INTERNAL_URLS, FILES_EXEC, SCAN_ERRORS +from files.discovered import VULN_LIST, FORMS_TESTED, REQUEST_TOKENS def logger(filename, content): ''' @@ -51,7 +52,13 @@ def GetLogger(): logger('anti-csrf-tokens', REQUEST_TOKENS) if FORMS_TESTED: logger('forms-tested', FORMS_TESTED) + if VULN_LIST: + logger('vulnerabilities', VULN_LIST) def ErrorLogger(url, error): con = '(i) '+url+' -> '+error.__str__() SCAN_ERRORS.append(con) + +def VulnLogger(url, vuln): + tent = '[!] '+url+' -> '+vuln + VULN_LIST.append(tent) diff --git a/core/main.py b/core/main.py index 3408b74..accacce 100644 --- a/core/main.py +++ b/core/main.py @@ -91,7 +91,11 @@ def Engine(): # lets begin it! resp1.open(init1) # Makes request as User2 resp2.open(init1) # Make request as User1 + # 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: + # Implementing the first mode. [NO CRAWL] if not CRAWL_SITE: url = web config.DISPLAY_HEADERS = True @@ -144,6 +148,8 @@ def Engine(): # lets begin it! query, token = Entropy(result, url, m['action']) # Now its time to detect the encoding type (if any) of the Anti-CSRF token. fnd = Encoding(token) + if fnd == 0x01: + VulnLogger(url, 'Token is a string encoded value which can be probably decrypted.') # Go for token parameter tamper checks. if (query and token): Tamper(url, action, result, r2, query, token) @@ -174,6 +180,7 @@ def Engine(): # lets begin it! 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 @@ -211,7 +218,7 @@ def Engine(): # lets begin it! 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 + 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: @@ -229,6 +236,8 @@ def Engine(): # lets begin it! ErrorLogger(url, 'No standard form "name".') # Now its time to detect the encoding type (if any) of the Anti-CSRF token. fnd = Encoding(token) + if fnd == 0x01: + VulnLogger(url, 'String encoded token value. Token might be decrypted.') # Go for token parameter tamper checks. if (query and token): Tamper(url, action, result, r2, query, token) diff --git a/core/options.py b/core/options.py index 4b84b3e..9fab02d 100644 --- a/core/options.py +++ b/core/options.py @@ -32,6 +32,7 @@ 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') diff --git a/core/verbout.py b/core/verbout.py index 74ebdfd..57fb174 100644 --- a/core/verbout.py +++ b/core/verbout.py @@ -12,7 +12,7 @@ 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] From 314d8aa4cae8b23f8b2eb00aed8be9b5ee952399 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 15 Dec 2018 03:20:15 +0000 Subject: [PATCH 40/94] Added vuln database option --- files/config.py | 3 +++ files/discovered.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/files/config.py b/files/config.py index a610804..b9bf422 100644 --- a/files/config.py +++ b/files/config.py @@ -25,6 +25,9 @@ # 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 diff --git a/files/discovered.py b/files/discovered.py index e1eecde..4e0dd96 100644 --- a/files/discovered.py +++ b/files/discovered.py @@ -12,6 +12,9 @@ # INFO: This file is for storing the various important parts of # requests discovered during making of various requests. +# Vulnerabilities which were noticed +VULN_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 From 734d90c395d1a75aeda94bdfb620eda6aed002f5 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 15 Dec 2018 03:20:46 +0000 Subject: [PATCH 41/94] Added modules with detailed logging system --- modules/Checkpost.py | 2 ++ modules/Cookie.py | 2 ++ modules/Entropy.py | 4 ++++ modules/Origin.py | 2 ++ modules/Referer.py | 2 ++ modules/Tamper.py | 4 ++++ 6 files changed, 16 insertions(+) diff --git a/modules/Checkpost.py b/modules/Checkpost.py index 1f70219..2ff63f4 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -15,6 +15,7 @@ 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 from modules.Generator import GeneratePoC @@ -46,6 +47,7 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): 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.') time.sleep(0.3) verbout(O, 'PoC of response and request...') if m_name: diff --git a/modules/Cookie.py b/modules/Cookie.py index 01b06df..c45bb16 100644 --- a/modules/Cookie.py +++ b/modules/Cookie.py @@ -17,6 +17,7 @@ from core.request import Get from core.randua import RandomAgent from .Persistence import Persistence +from core.logger import VulnLogger from urllib.parse import urlencode, unquote, urlsplit resps = [] @@ -146,3 +147,4 @@ def SameSite(url): 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 On Cross Origin Requests '+color.END) + VulnLogger(url, 'No SameSite Flag Set on Cookies on Cross-Origin Requests.') diff --git a/modules/Entropy.py b/modules/Entropy.py index a0696c7..a63e36b 100644 --- a/modules/Entropy.py +++ b/modules/Entropy.py @@ -15,6 +15,7 @@ from core.colors import * from .Token import Token from core.verbout import verbout +from core.logger import VulnLogger def Entropy(req, url, m_action, m_name=''): """ @@ -48,6 +49,7 @@ def Entropy(req, url, m_action, m_name=''): # Check for common CSRF token names _q, para = Token(req) if (para and _q) == None: + VulnLogger(url, 'Form Requested Without Anti-CSRF Token.') return '', '' # Coverting the token to a raw string, cause some special # chars might fu*k with the Shannon Entropy operation. @@ -58,6 +60,7 @@ def Entropy(req, url, m_action, m_name=''): 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.ORANGE+' [!] Vulnerability Type: '+color.BG+' Very Short/No Anti-CSRF Tokens '+color.END) + VulnLogger(url, 'Very Short/No Anti-CSRF Tokens.') if len(value) > 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...') @@ -76,6 +79,7 @@ def Entropy(req, url, m_action, m_name=''): 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) + VulnLogger(url, 'Low Entropy Anti-CSRF Tokens.') if m_name: print(color.RED+'\n +---------+') print(color.RED+' | PoC |') diff --git a/modules/Origin.py b/modules/Origin.py index 1ea98f2..e77cc9e 100644 --- a/modules/Origin.py +++ b/modules/Origin.py @@ -15,6 +15,7 @@ from core.verbout import verbout from core.request import Get from core.randua import RandomAgent +from core.logger import VulnLogger def Origin(url): """ @@ -64,4 +65,5 @@ def Origin(url): 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) + VulnLogger(url, 'No Origin Header based request validation.') return False diff --git a/modules/Referer.py b/modules/Referer.py index d4958ee..02c3b0d 100644 --- a/modules/Referer.py +++ b/modules/Referer.py @@ -14,6 +14,7 @@ from files.config import * from core.verbout import verbout from core.request import Get +from core.logger import VulnLogger def Referer(url): """ @@ -65,4 +66,5 @@ def Referer(url): 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) + VulnLogger(url, 'No Referer Header based request validation.') return False diff --git a/modules/Tamper.py b/modules/Tamper.py index 0c4d1fe..2e1598b 100644 --- a/modules/Tamper.py +++ b/modules/Tamper.py @@ -14,6 +14,7 @@ from core.request import Post from files.config import * from core.verbout import verbout +from core.logger import VulnLogger from core.utils import replaceStrIndex from urllib.parse import urlencode, quote @@ -64,6 +65,7 @@ def Tamper(url, action, req, body, query, para): 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)): flagx1 = 0x01 + VulnLogger(url, 'Anti-CSRF Token tamper by index replacement returns valid response.') # [Step 2]: Second we take the token and then remove a char # at a specific position and test the response body. @@ -84,6 +86,7 @@ def Tamper(url, action, req, body, query, para): 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)): flagx2 = 0x01 + VulnLogger(url, 'Anti-CSRF Token tamper by index removal returns valid response.') # [Step 3]: Third we take the token and then remove the whole # anticsrf token and test the response body. @@ -102,6 +105,7 @@ def Tamper(url, action, req, body, query, para): 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)): flagx3 = 0x01 + VulnLogger(url, 'Anti-CSRF Token on removal returns valid response.') # If any of the forgeries worked... if (flagx1 or flagx2 or flagx3) == 0x01: From d2b31ba4db1a5503592b207b59d28b1554e08b2c Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 15 Dec 2018 03:53:47 +0000 Subject: [PATCH 42/94] Press F to pay Respect --- core/main.py | 14 ++++++-------- modules/Analysis.py | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/core/main.py b/core/main.py index accacce..db46447 100644 --- a/core/main.py +++ b/core/main.py @@ -40,8 +40,6 @@ from core.logger import ErrorLogger, GetLogger # Imports from files -from files import config -# Necessary evil :( from files.config import * from files.discovered import FORMS_TESTED @@ -98,7 +96,6 @@ def Engine(): # lets begin it! # Implementing the first mode. [NO CRAWL] if not CRAWL_SITE: url = web - config.DISPLAY_HEADERS = True response = Get(url).text try: verbout(O,'Trying to parse response...') @@ -123,7 +120,7 @@ def Engine(): # lets begin it! 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\n'+color.CYAN+' %s' % (m.prettify())) - FORMS_TESTED.append(m.prettify()) + FORMS_TESTED.append('(i) '+url+':\n\n'+m.prettify()+'\n') try: if m['action']: pass @@ -163,7 +160,7 @@ def Engine(): # lets begin it! verbout(GR, 'Preparing form inputs...') contents2, genpoc = 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: + if POST_BASED and not query and not token: try: if m['name']: PostBased(url, r1, r2, r3, m['action'], result, genpoc, m['name']) @@ -212,6 +209,7 @@ def Engine(): # lets begin it! # 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 @@ -246,12 +244,12 @@ def Engine(): # lets begin it! form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form except IndexError: verbout(R, 'Form Error') - ErrorLogger(url, 'Form 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 2 as user2 r3 = Post(url,action,contents2).text # make request as user3 with user2's form - if POST_BASED: + if POST_BASED and not query and not token: try: if m['name']: PostBased(url, r1, r2, r3, m['action'], result, genpoc, m['name']) @@ -280,7 +278,7 @@ def Engine(): # lets begin it! verbout(R, 'Error Code : ' +O+ str(e.code)) ErrorLogger(url, e.__str__()) quit() - GetLogger() # The scanning has finished, so now we can log out all the links ;) + 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 except KeyboardInterrupt as e: # Incase user wants to exit :') (while crawling) diff --git a/modules/Analysis.py b/modules/Analysis.py index 0afdbc2..e1ccd09 100644 --- a/modules/Analysis.py +++ b/modules/Analysis.py @@ -67,7 +67,7 @@ def Analysis(): verbout(O, 'Dynamic Part of Token 0x2: '+color.GREY+tokenx2.replace(p,'')+color.END+' | Length: '+str(len(tokenx2.replace(p,'')))) 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+'!') elif n < 0.5 or m < len(tokenx1)/2: @@ -86,10 +86,10 @@ def Analysis(): 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(color.GREEN,' [+] Post-Analysis reveals that token might be '+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+'!') time.sleep(1) except KeyboardInterrupt: continue - print(C+'Post-Scan Analysis Completed!') + print('\n'+C+'Post-Scan Analysis Completed!') From d7cf6a023bb5ecc1016065498fb593363b05e10b Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 04:45:59 +0000 Subject: [PATCH 43/94] Updated randua module with Opera User agents --- core/randua.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/core/randua.py b/core/randua.py index aefe782..581c881 100644 --- a/core/randua.py +++ b/core/randua.py @@ -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(): From 543d64d949b8964f62db2869605d23587155cec1 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 04:46:49 +0000 Subject: [PATCH 44/94] Updated Version number with stable bookmark --- files/VersionNum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/VersionNum b/files/VersionNum index 46b105a..a9842d6 100644 --- a/files/VersionNum +++ b/files/VersionNum @@ -1 +1 @@ -v2.0.0 +v2.0.0 (stable) From 398ed11584313a371763240392c4dda1cf986deb Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 05:59:55 +0000 Subject: [PATCH 45/94] Added checks for strength based xsrf --- core/logger.py | 8 +++++++- core/main.py | 24 +++++++++++++++++------- core/options.py | 9 ++++++++- core/utils.py | 4 ---- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/core/logger.py b/core/logger.py index efb47ad..e592c54 100644 --- a/core/logger.py +++ b/core/logger.py @@ -14,7 +14,7 @@ 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 +from files.discovered import VULN_LIST, FORMS_TESTED, REQUEST_TOKENS, STRENGTH_LIST def logger(filename, content): ''' @@ -54,6 +54,8 @@ def GetLogger(): 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__() @@ -62,3 +64,7 @@ def ErrorLogger(url, error): def VulnLogger(url, vuln): tent = '[!] '+url+' -> '+vuln 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 db46447..c1d3bde 100644 --- a/core/main.py +++ b/core/main.py @@ -38,6 +38,7 @@ from core.forms import form10, form20 from core.banner import banner, banabout from core.logger import ErrorLogger, GetLogger +from core.logger import VulnLogger, NovulLogger # Imports from files from files.config import * @@ -147,6 +148,8 @@ def Engine(): # lets begin it! fnd = Encoding(token) if fnd == 0x01: VulnLogger(url, 'Token is a string encoded value which can be probably decrypted.') + else: + NovulLogger(url, 'Anti-CSRF token is not a string encoded value.') # Go for token parameter tamper checks. if (query and token): Tamper(url, action, result, r2, query, token) @@ -155,7 +158,7 @@ def Engine(): # lets begin it! form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form except IndexError: verbout(R, 'Form Error') - ErrorLogger(url, 'Form 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 2 as user2 @@ -168,10 +171,11 @@ def Engine(): # lets begin it! PostBased(url, r1, r2, r3, m['action'], result, genpoc) 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!') + 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.__str__()) + ErrorLogger(url, msg) actionDone.append(action) # add the stuff done i+=1 # Increase user iteration @@ -236,6 +240,8 @@ def Engine(): # lets begin it! fnd = Encoding(token) if fnd == 0x01: VulnLogger(url, 'String encoded token value. Token might be decrypted.') + else: + NovulLogger(url, 'Anti-CSRF token is not a string encoded value.') # Go for token parameter tamper checks. if (query and token): Tamper(url, action, result, r2, query, token) @@ -244,7 +250,7 @@ def Engine(): # lets begin it! form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form except IndexError: verbout(R, 'Form Error') - ErrorLogger(url, '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 2 as user2 @@ -258,16 +264,17 @@ def Engine(): # lets begin it! 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.__str__()) + 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.__str__()) + 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 @@ -276,7 +283,7 @@ def Engine(): # lets begin it! if str(e.code) == '403': verbout(R, 'HTTP Authentication Error!') verbout(R, 'Error Code : ' +O+ str(e.code)) - ErrorLogger(url, e.__str__()) + ErrorLogger(url, e) quit() GetLogger() # The scanning has finished, so now we can log out all the links ;) print('\n'+G+"Scan completed!"+'\n') @@ -288,3 +295,6 @@ def Engine(): # lets begin it! print(R+'Aborted!') # say goodbye ErrorLogger('KeyBoard Interrupt', 'Aborted') quit() + except Exception as e: + verbout(R, e.__str__()) + ErrorLogger(url, e) diff --git a/core/options.py b/core/options.py index 9fab02d..aafe18a 100644 --- a/core/options.py +++ b/core/options.py @@ -44,6 +44,7 @@ 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('--skip-analysis', help='Skip the Post-Scan Analysis of Tokens which were gathered during requests', dest='skipal', 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('--version', help='Display the version of XSRFProbe and exit.', dest='version', action='store_true') @@ -95,6 +96,8 @@ # 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 @@ -107,6 +110,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 @@ -121,7 +128,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 diff --git a/core/utils.py b/core/utils.py index 1fe34ce..5f49e4c 100644 --- a/core/utils.py +++ b/core/utils.py @@ -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 From 52cb406e0dd85f3edac8f24713c2b35877351a5a Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 06:00:11 +0000 Subject: [PATCH 46/94] Included the --display param --- files/config.py | 2 ++ files/discovered.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/files/config.py b/files/config.py index b9bf422..e1ffa2a 100644 --- a/files/config.py +++ b/files/config.py @@ -14,6 +14,8 @@ # 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 # Site Url to be scanned (Required) SITE_URL = '' diff --git a/files/discovered.py b/files/discovered.py index 4e0dd96..d6b47f6 100644 --- a/files/discovered.py +++ b/files/discovered.py @@ -15,6 +15,9 @@ # 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 From 1485ca9e30341127e812012b3edfb1122b995ce5 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 06:00:30 +0000 Subject: [PATCH 47/94] Added support for strength based logging --- modules/Analysis.py | 6 +++++- modules/Cookie.py | 22 ++++++++++++++++++---- modules/Entropy.py | 14 ++++++++------ modules/Origin.py | 5 +++-- modules/Persistence.py | 13 +++++++++++-- modules/Referer.py | 5 +++-- modules/Tamper.py | 7 ++++++- modules/Token.py | 2 +- 8 files changed, 55 insertions(+), 19 deletions(-) diff --git a/modules/Analysis.py b/modules/Analysis.py index e1ccd09..ef6d392 100644 --- a/modules/Analysis.py +++ b/modules/Analysis.py @@ -15,6 +15,7 @@ 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(): ''' @@ -54,7 +55,7 @@ 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 @@ -70,6 +71,7 @@ def Analysis(): 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+'!') + VulnLogger('Analysis', 'Tokens can easily be Forged by Bruteforcing/Guessing.') 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) @@ -80,6 +82,7 @@ def Analysis(): 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+'!') + VulnLogger('Analysis', 'Tokens can easily be Forged by Bruteforcing/Guessing.') else: verbout(R, 'Token distance calculated is '+color.GREEN+'greater than 0.5!') p = sameSequence(tokenx1, tokenx2) @@ -89,6 +92,7 @@ def Analysis(): verbout(color.GREEN,' [+] Post-Analysis reveals that token might be '+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+'!') + NovulLogger('Analysis', 'Tokens cannot be Forged by Bruteforcing/Guessing.') time.sleep(1) except KeyboardInterrupt: continue diff --git a/modules/Cookie.py b/modules/Cookie.py index c45bb16..69ed972 100644 --- a/modules/Cookie.py +++ b/modules/Cookie.py @@ -17,7 +17,7 @@ from core.request import Get from core.randua import RandomAgent from .Persistence import Persistence -from core.logger import VulnLogger +from core.logger import VulnLogger, NovulLogger from urllib.parse import urlencode, unquote, urlsplit resps = [] @@ -37,13 +37,17 @@ def SameSite(url): This function parses and verifies the cookies with SameSite Flags. ''' - foundx1, foundx2, foundx3 = 0x00, 0x00, 0x00 + # 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: @@ -76,7 +80,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: @@ -109,6 +114,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: @@ -137,11 +143,19 @@ def SameSite(url): verbout(R,'Endpoint '+color.ORANGE+'SameSite Flag Cookie Validation'+color.END+' 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,'Heuristic(s) reveal endpoint might be '+color.BY+' VULNERABLE '+color.END+' to CSRFs...') diff --git a/modules/Entropy.py b/modules/Entropy.py index a63e36b..b64cfcf 100644 --- a/modules/Entropy.py +++ b/modules/Entropy.py @@ -15,7 +15,7 @@ from core.colors import * from .Token import Token from core.verbout import verbout -from core.logger import VulnLogger +from core.logger import VulnLogger, NovulLogger def Entropy(req, url, m_action, m_name=''): """ @@ -61,10 +61,11 @@ def Entropy(req, url, m_action, m_name=''): print(color.ORANGE+' [-] Endpoint likely '+color.BR+' VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') print(color.ORANGE+' [!] Vulnerability Type: '+color.BG+' Very Short/No Anti-CSRF Tokens '+color.END) VulnLogger(url, 'Very Short/No Anti-CSRF Tokens.') - if len(value) > 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...') + 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.ORANGE+' to CSRF Attacks...') print(color.ORANGE+' [!] CSRF Mitigation Method: '+color.BG+' Long Anti-CSRF Tokens '+color.END) + NovulLogger(url, 'Long Anti-CSRF tokens with Good Strength.') # Checking entropy verbout(O, 'Proceeding to calculate '+color.GREY+'Shannon Entropy'+color.END+' of Token audited...') @@ -73,11 +74,12 @@ def Entropy(req, url, m_action, m_name=''): 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.ORANGE+' [+] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') + print(color.GREEN+' [+] 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) + NovulLogger(url, 'High Entropy Anti-CSRF Tokens.') 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.RED+' [-] 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) VulnLogger(url, 'Low Entropy Anti-CSRF Tokens.') if m_name: diff --git a/modules/Origin.py b/modules/Origin.py index e77cc9e..2a4df07 100644 --- a/modules/Origin.py +++ b/modules/Origin.py @@ -15,7 +15,7 @@ from core.verbout import verbout from core.request import Get from core.randua import RandomAgent -from core.logger import VulnLogger +from core.logger import VulnLogger, NovulLogger def Origin(url): """ @@ -59,11 +59,12 @@ def Origin(url): 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) + 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,'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) - VulnLogger(url, 'No Origin Header based request validation.') + VulnLogger(url, 'No Origin Header based request validation presence.') return False diff --git a/modules/Persistence.py b/modules/Persistence.py index a8f8db0..3d26a9f 100644 --- a/modules/Persistence.py +++ b/modules/Persistence.py @@ -17,14 +17,21 @@ from core.request import Get from core.randua import RandomAgent 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): + ''' + The main idea behind this is to check for Cookie + Persistence (the cookie supplied by user). + ''' + # Checking if user has supplied a value. if COOKIE_VALUE: verbout(GR,'Proceeding to test for '+color.GREY+'Cookie Persistence'+color.END+'...') time.sleep(0.7) + # So the idea is to 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', @@ -47,11 +54,13 @@ def Persistence(url): verbout(G,'Set-Cookie header does not change with varied User-Agents...') verbout(R,'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 Practise: '+color.BR+' Persistent Session Cookies '+color.END) + VulnLogger(url, 'Persistent Session Cookies.') 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) + NovulLogger(url, 'No Persistent Cookies.') else: verbout(R,'Skipping persistence checks as no cookie value supplied...') diff --git a/modules/Referer.py b/modules/Referer.py index 02c3b0d..c2a29c8 100644 --- a/modules/Referer.py +++ b/modules/Referer.py @@ -14,7 +14,7 @@ from files.config import * from core.verbout import verbout from core.request import Get -from core.logger import VulnLogger +from core.logger import VulnLogger, NovulLogger def Referer(url): """ @@ -60,11 +60,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,'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) - VulnLogger(url, 'No Referer Header based request validation.') + VulnLogger(url, 'No Referer Header based Request Validation presence.') return False diff --git a/modules/Tamper.py b/modules/Tamper.py index 2e1598b..fe77f2b 100644 --- a/modules/Tamper.py +++ b/modules/Tamper.py @@ -14,9 +14,9 @@ from core.request import Post from files.config import * from core.verbout import verbout -from core.logger import VulnLogger from core.utils import replaceStrIndex from urllib.parse import urlencode, quote +from core.logger import VulnLogger, NovulLogger def Tamper(url, action, req, body, query, para): ''' @@ -63,6 +63,7 @@ def Tamper(url, action, req, body, query, para): # 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!') + NovulLogger(url, 'Anti-CSRF Token tamper by index replacement does not return valid response.') if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): flagx1 = 0x01 VulnLogger(url, 'Anti-CSRF Token tamper by index replacement returns valid response.') @@ -84,6 +85,7 @@ def Tamper(url, action, req, body, query, para): # 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!') + NovulLogger(url, 'Anti-CSRF Token tamper by index removal does not return valid response.') if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): flagx2 = 0x01 VulnLogger(url, 'Anti-CSRF Token tamper by index removal returns valid response.') @@ -103,6 +105,7 @@ def Tamper(url, action, req, body, query, para): # 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!') + NovulLogger(url, 'Anti-CSRF Token on removal does not return valid response.') if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): flagx3 = 0x01 VulnLogger(url, 'Anti-CSRF Token on removal returns valid response.') @@ -113,7 +116,9 @@ def Tamper(url, action, req, body, query, para): 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) + VulnLogger(url, 'Anti-CSRF Tokens are not Unique. Token Reuse detected.') else: print(color.RED+' [-] The Tampered Anti-CSRF Token requested returns a 40x or 50x response... ') print(color.GREEN+' [-] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') print(color.ORANGE+' [!] CSRF Mitigation Method: '+color.BG+' Unique Anti-CSRF Tokens '+color.END) + NovulLogger(url, 'Unique Anti-CSRF Tokens. No token reuse.') diff --git a/modules/Token.py b/modules/Token.py index 35016bb..38bd56d 100644 --- a/modules/Token.py +++ b/modules/Token.py @@ -53,6 +53,6 @@ def Token(req): if param: return (query, param) - verbout(color.RED,' [-] The form was requested '+color.BR+' Without an Anti-CSRF Token '+color.END+color.RED+'...') + 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 (None, None) From 13fa6f09bcbfb0d353242f287c7f240afad4fc60 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 06:18:56 +0000 Subject: [PATCH 48/94] Small changes to trail build --- core/inputin.py | 3 +-- core/options.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/inputin.py b/core/inputin.py index 4d4ccaa..82fd76b 100644 --- a/core/inputin.py +++ b/core/inputin.py @@ -9,8 +9,7 @@ #This module requires XSRF-Probe #https://github.com/0xInfection/XSRF-Probe -import sys -import socket +import sys, socket from tld import get_fld from core.colors import * from files.config import SITE_URL diff --git a/core/options.py b/core/options.py index aafe18a..9b4cb6d 100644 --- a/core/options.py +++ b/core/options.py @@ -39,10 +39,10 @@ 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, 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('--crawl', help="Crawl the whole site and simultaneously test all discovered endpoints for CSRF.", dest='crawl', action='store_true') -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('--no-analysis', help='Skip the Post-Scan Analysis of Tokens which were gathered during requests', dest='skipal', 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') From ef3e06d1606266e37d0d0d1a126f1396c2782972 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 06:19:06 +0000 Subject: [PATCH 49/94] Another set of changes to prevent errors --- files/config.py | 2 +- files/discovered.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files/config.py b/files/config.py index e1ffa2a..68bd101 100644 --- a/files/config.py +++ b/files/config.py @@ -14,7 +14,7 @@ # 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 DISPLAY_HEADERS, EXECUTABLES, FILE_EXTENSIONS, POC_GENERATION, OUTPUT_DIR global CRAWL_SITE, TOKEN_CHECKS, DELAY_VALUE, SCAN_ANALYSIS, EXCLUDE_DIRS # Site Url to be scanned (Required) diff --git a/files/discovered.py b/files/discovered.py index d6b47f6..63bf204 100644 --- a/files/discovered.py +++ b/files/discovered.py @@ -27,7 +27,7 @@ # List of all Urls that we found INTERNAL_URLS = [] -# Files discovered during crawling +# Files/executables discovered during crawling FILES_EXEC = [] # Forms that were tested From 443a75084f58bc1af86776ff3e177461c4a75b29 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 07:26:49 +0000 Subject: [PATCH 50/94] Added method to beautify forms --- files/VersionNum | 2 +- modules/Generator.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/files/VersionNum b/files/VersionNum index a9842d6..437382d 100644 --- a/files/VersionNum +++ b/files/VersionNum @@ -1 +1 @@ -v2.0.0 (stable) +v2 (stable) diff --git a/modules/Generator.py b/modules/Generator.py index ac4be2e..8e8e602 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -13,6 +13,7 @@ from ast import literal_eval from files.config import OUTPUT_DIR from bs4 import BeautifulSoup, Tag +from core.prettify import formPrettify def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): """ @@ -46,11 +47,9 @@ def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www- small_tag = content.new_tag('small') small_tag.string = '(i) This PoC form was generated by XSRFProbe.' footer_tag.append(small_tag) - m = content.prettify() - for i in m.splitlines(): - print(' '+i) + formPrettify(content.prettify()) print('') fi = open(OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-csrf-poc.html', 'w+', encoding='utf8') - fi.write(m) + fi.write(content.prettify()) fi.close() print(G+'PoC successfully saved under '+color.ORANGE+OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-csrf-poc.html') From 52083ed116e1eebb3d14a5b4514608ec4681cfd0 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 07:27:09 +0000 Subject: [PATCH 51/94] Included new module for prettifying forms --- core/prettify.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 core/prettify.py diff --git a/core/prettify.py b/core/prettify.py new file mode 100644 index 0000000..6ae74bc --- /dev/null +++ b/core/prettify.py @@ -0,0 +1,50 @@ +#!/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() + try: + 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) + except MemoryError: + pass + for h in highlighted: + print(' '+h) From 5edef7fe2db6695bb8413b37b038f31e4db7fd01 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Tue, 18 Dec 2018 07:27:22 +0000 Subject: [PATCH 52/94] Other small stuffs as required --- core/banner.py | 2 +- core/main.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/banner.py b/core/banner.py index 60db0a2..2b42e7c 100644 --- a/core/banner.py +++ b/core/banner.py @@ -43,5 +43,5 @@ def banabout(): # some fancy banner stuff :p time.sleep(0.1) print(color.BLUE+' [---] [---]') time.sleep(0.1) - print(color.BLUE+' [---] '+color.ORANGE+' ~ Version '+color.RED+open('files/VersionNum').read().strip()+color.ORANGE+' ~ '+color.BLUE+' [---]\n') + 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/main.py b/core/main.py index c1d3bde..b137d4c 100644 --- a/core/main.py +++ b/core/main.py @@ -35,6 +35,7 @@ from core.inputin import inputin from core.request import Get, Post from core.verbout import verbout +from core.prettify import formPrettify from core.forms import form10, form20 from core.banner import banner, banabout from core.logger import ErrorLogger, GetLogger @@ -120,7 +121,9 @@ def Engine(): # lets begin it! # 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\n'+color.CYAN+' %s' % (m.prettify())) + 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']: From b7839ca050263f99a75457dd189d3a2d1d37e2d9 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Wed, 19 Dec 2018 12:55:45 +0000 Subject: [PATCH 53/94] Added method for generating malicious csrf poc --- core/options.py | 5 +++++ core/prettify.py | 51 +++++++++++++++++++++++------------------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/core/options.py b/core/options.py index 9b4cb6d..f12c86e 100644 --- a/core/options.py +++ b/core/options.py @@ -43,6 +43,7 @@ optional.add_argument('--max-chars', help='Maximum allowed character length for the custom token value to be generated. For example: `--max-chars 5`. Default value is 6.', dest='maxchars', type=int) optional.add_argument('--crawl', help="Crawl the whole site and simultaneously test all discovered endpoints for CSRF.", dest='crawl', action='store_true') optional.add_argument('--no-analysis', help='Skip the Post-Scan Analysis of Tokens which were gathered during requests', dest='skipal', action='store_true') +optional.add_argument('--malicious', help='Generate a malicious CSRF Form which can be used in real-world exploits.', dest='malicious', action='store_true') optional.add_argument('--skip-poc', help='Skip the PoC Form Generation of POST-Based Cross Site Request Forgeries.', dest='skippoc', action='store_true') optional.add_argument('--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') @@ -83,6 +84,10 @@ 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: diff --git a/core/prettify.py b/core/prettify.py index 6ae74bc..35cf8b3 100644 --- a/core/prettify.py +++ b/core/prettify.py @@ -19,32 +19,29 @@ def formPrettify(response): ''' highlighted = [] response = response.splitlines() - try: - 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) - except MemoryError: - pass + 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) From 545108a9d4932cad2ba062f61aac379f737cc3f3 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Wed, 19 Dec 2018 12:56:35 +0000 Subject: [PATCH 54/94] Added new method as GEN_MALICIOUS for generating malicious csrf forms --- modules/Checkpost.py | 27 ++++++++++--------- modules/Generator.py | 62 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/modules/Checkpost.py b/modules/Checkpost.py index 2ff63f4..00cf400 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -16,28 +16,29 @@ from core.verbout import verbout from urllib.parse import urlencode from core.logger import VulnLogger -from files.config import POC_GENERATION -from modules.Generator import GeneratePoC +from files.config import POC_GENERATION, GEN_MALICIOUS +from modules.Generator import GenNormalPoC, GenMalicious def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): ''' This method is for detecting POST-Based Request Forgeries on basis of fuzzy string matching and comparison - based on Radcliff-Obershelp Algorithm. + based on Ratcliff-Obershelp Algorithm. ''' - 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 + 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 for n in checkdiffx1: - if re.match('\+|-',n): # get regex matching stuff + if re.match('\+|-', n): # get regex matching stuff 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 # 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 requests and # response body. # If the number of differences of result12 are less than the number of differences @@ -70,9 +71,11 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): else: print(color.GREEN+' [+] Action : ' +color.END+m_action) # action print(color.ORANGE+' [+] POST Query : '+color.GREY+ urlencode(result).strip()) - print(GR, 'Generating PoC Form...' ) - print(color.RED+'\n +--------------+') - print(color.RED+' | Form PoC |') - print(color.RED+' +--------------+\n'+color.CYAN) + # If option --skip-poc hasn't been supplied... if POC_GENERATION: - GeneratePoC(url, str(genpoc)) + if GEN_MALICIOUS: + # Generates a malicious CSRF form + GenMalicious(url, str(genpoc)) + else: + # Generates a normal PoC + GenNormalPoC(url, str(genpoc)) diff --git a/modules/Generator.py b/modules/Generator.py index 8e8e602..cf22199 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -15,21 +15,30 @@ from bs4 import BeautifulSoup, Tag from core.prettify import formPrettify -def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): +def GenNormalPoC(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): """ - Generate a CSRF PoC using basic form data + Generate a normal CSRF PoC using basic form data """ + print(GR, 'Generating normal PoC Form...' ) + print(color.RED+'\n +--------------+') + print(color.RED+' | Form PoC |') + print(color.RED+' +--------------+\n'+color.CYAN) + # Main starting which we will use to generate form. + # Small hack to make beautifulsoup work in the reverse way ;) content = BeautifulSoup("", "html.parser") html_tag = content.find("html") title_tag = content.new_tag('title') + # Adding the title_tag title_tag.string = 'CSRF PoC' html_tag.append(title_tag) head_tag = content.new_tag('h2') + # Adding a

tag for heading head_tag.string = 'Your CSRF PoC' html_tag.append(head_tag) + # Init to the form and stuff form_tag = content.new_tag("form", action=action, method=method, enctype=encoding_type) html_tag.append(form_tag) - + # We generate the form by taking input and stuff. for field in literal_eval(fields): label_tag = content.new_tag('label') label_tag.string = field['label'] @@ -37,7 +46,7 @@ def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www- field_tag['name'] = field['name'] form_tag.append(label_tag) form_tag.append(field_tag) - + # The form submit button and tag submit_tag = content.new_tag("input", type="submit", value='Submit') form_tag.append(submit_tag) br_tag = content.new_tag('br') @@ -45,11 +54,56 @@ def GeneratePoC(action, fields, method='POST', encoding_type='application/x-www- footer_tag = content.new_tag('footer') html_tag.append(footer_tag) small_tag = content.new_tag('small') + # Fancy brand tag ;p small_tag.string = '(i) This PoC form was generated by XSRFProbe.' footer_tag.append(small_tag) formPrettify(content.prettify()) print('') + # Write out the file af o ]}) 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...' ) + print(color.RED+'\n +-------------------+') + print(color.RED+' | Malicious PoC |') + print(color.RED+' +-------------------+\n'+color.CYAN) + content = BeautifulSoup("", "html.parser") + html_tag = content.find("html") + title_tag = content.new_tag('title') + title_tag.string = 'Get Money ($$$)' + html_tag.append(title_tag) + head_tag = content.new_tag('p') + # Doesn't look clickbait at all :p + # TODO: WIll improve this is future. + head_tag.string = 'Click below to get $100 bonus.' + html_tag.append(head_tag) + form_tag = content.new_tag("form", action=action, method=method, enctype=encoding_type) + html_tag.append(form_tag) + # This is where we get the forms' field values + # Since it is a malicious form, all the form field values must be hidden ;) + for field in literal_eval(fields): + if not field['value']: + # So that is why we ask user for input ;) + q = input(color.ORANGE+' [+] Enter field value for "'+color.GREY+field['label']+'" :> '+color.CYAN) + field_tag = content.new_tag("input", type='hidden', value=q) + else: + field_tag = content.new_tag("input", type='hidden', value=field['value']) + field_tag['name'] = field['name'] + form_tag.append(field_tag) + + submit_tag = content.new_tag("input", type="submit", value='Get It') + form_tag.append(submit_tag) + # Print out the form pretty well formatted + formPrettify(content.prettify()) + print('') + # Print out the directory where it is saved + 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('/','-')+'-malicious-poc.html') From d0213d34112f71757519250db81947ffbf66f0d3 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Wed, 19 Dec 2018 12:57:03 +0000 Subject: [PATCH 55/94] Other stuff to complete addition --- files/config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/files/config.py b/files/config.py index 68bd101..039b9b7 100644 --- a/files/config.py +++ b/files/config.py @@ -15,7 +15,7 @@ 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 +global CRAWL_SITE, TOKEN_CHECKS, DELAY_VALUE, SCAN_ANALYSIS, EXCLUDE_DIRS, GEN_MALICIOUS # Site Url to be scanned (Required) SITE_URL = '' @@ -138,6 +138,10 @@ # 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',] From ae1d8a0dfe07c980c9744d9052e3ba749412ec50 Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Thu, 20 Dec 2018 00:04:25 +0530 Subject: [PATCH 56/94] Small update to requirements.txt avoided unnecessary usage --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f41512c..ba29c0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ requests bs4 -lxml request stringdist tld From 2baa0a4f422dd57ddfce018a3cea05912c712e2f Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Thu, 20 Dec 2018 00:08:00 +0530 Subject: [PATCH 57/94] Removed a unnecessary library from dependencies --- core/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/main.py b/core/main.py index b137d4c..7a75a6d 100644 --- a/core/main.py +++ b/core/main.py @@ -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.') From 0019516c10713fdb28ccdb469a11cafbb6bdc2ed Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 22 Dec 2018 12:39:06 +0000 Subject: [PATCH 58/94] Added more verbose logging system --- modules/Analysis.py | 6 +++--- modules/Checkpost.py | 4 ++-- modules/Cookie.py | 6 +++--- modules/Encoding.py | 2 +- modules/Entropy.py | 10 ++++++---- modules/Origin.py | 2 +- modules/Persistence.py | 2 +- modules/Referer.py | 2 +- modules/Tamper.py | 11 ++++++----- 9 files changed, 24 insertions(+), 21 deletions(-) diff --git a/modules/Analysis.py b/modules/Analysis.py index ef6d392..68f6253 100644 --- a/modules/Analysis.py +++ b/modules/Analysis.py @@ -46,7 +46,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)))) @@ -71,7 +71,7 @@ def Analysis(): 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+'!') - VulnLogger('Analysis', 'Tokens can easily be Forged by Bruteforcing/Guessing.') + 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) @@ -82,7 +82,7 @@ def Analysis(): 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+'!') - VulnLogger('Analysis', 'Tokens can easily be Forged by Bruteforcing/Guessing.') + 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) diff --git a/modules/Checkpost.py b/modules/Checkpost.py index 00cf400..ba3fcac 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -19,7 +19,7 @@ from files.config import POC_GENERATION, GEN_MALICIOUS from modules.Generator import GenNormalPoC, GenMalicious -def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): +def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): ''' This method is for detecting POST-Based Request Forgeries on basis of fuzzy string matching and comparison @@ -48,7 +48,7 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, m_name=''): 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.') + VulnLogger(url, 'POST-Based Request Forgery on Forms.', '[i] Form: '+form'\n[i] POST Query: '+str(result)+'\n') time.sleep(0.3) verbout(O, 'PoC of response and request...') if m_name: diff --git a/modules/Cookie.py b/modules/Cookie.py index 69ed972..66ad653 100644 --- a/modules/Cookie.py +++ b/modules/Cookie.py @@ -157,8 +157,8 @@ def SameSite(url): 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 On Cross Origin Requests '+color.END) - VulnLogger(url, 'No SameSite Flag Set on Cookies on Cross-Origin Requests.') + 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/Encoding.py b/modules/Encoding.py index fbf1432..0f304c3 100644 --- a/modules/Encoding.py +++ b/modules/Encoding.py @@ -51,7 +51,7 @@ def Encoding(val): if found == 0x00: print(color.RED+' [-] '+color.BR+' No Token Encoding Detected. '+color.END) sleep(0.8) - return found + return (found, txt) def hashcheck(hashtype, regexstr, data): try: diff --git a/modules/Entropy.py b/modules/Entropy.py index b64cfcf..a61c3c8 100644 --- a/modules/Entropy.py +++ b/modules/Entropy.py @@ -17,7 +17,7 @@ from core.verbout import verbout from core.logger import VulnLogger, NovulLogger -def Entropy(req, url, m_action, m_name=''): +def Entropy(req, url, form, m_action, m_name=''): """ This function has the work of comparing and calculating Shannon Entropy and related @@ -49,7 +49,9 @@ def Entropy(req, url, m_action, m_name=''): # Check for common CSRF token names _q, para = Token(req) if (para and _q) == None: - VulnLogger(url, 'Form Requested Without Anti-CSRF Token.') + VulnLogger(url, + 'Form Requested Without Anti-CSRF Token.', + '[i] Form Requested: '+form+'\n[i] Request Query: '+req) return '', '' # Coverting the token to a raw string, cause some special # chars might fu*k with the Shannon Entropy operation. @@ -60,7 +62,7 @@ def Entropy(req, url, m_action, m_name=''): 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.ORANGE+' [!] Vulnerability Type: '+color.BG+' Very Short/No Anti-CSRF Tokens '+color.END) - VulnLogger(url, 'Very Short/No Anti-CSRF Tokens.') + 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.ORANGE+' to CSRF Attacks...') @@ -81,7 +83,7 @@ def Entropy(req, url, m_action, m_name=''): 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.ORANGE+' to CSRF Attacks inspite of CSRF Tokens...') print(color.ORANGE+' [!] Vulnerability Type: '+color.BR+' Low Entropy Anti-CSRF Tokens '+color.END) - VulnLogger(url, 'Low Entropy Anti-CSRF Tokens.') + VulnLogger(url, 'Low Entropy Anti-CSRF Tokens.', 'Token: '+value) if m_name: print(color.RED+'\n +---------+') print(color.RED+' | PoC |') diff --git a/modules/Origin.py b/modules/Origin.py index 2a4df07..6048666 100644 --- a/modules/Origin.py +++ b/modules/Origin.py @@ -66,5 +66,5 @@ def Origin(url): 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) - VulnLogger(url, 'No Origin Header based request validation presence.') + VulnLogger(url, 'No Origin Header based request validation presence.', '[i] Response Headers: '+str(req0x02.headers)) return False diff --git a/modules/Persistence.py b/modules/Persistence.py index 3d26a9f..ca90af3 100644 --- a/modules/Persistence.py +++ b/modules/Persistence.py @@ -55,7 +55,7 @@ def Persistence(url): verbout(R,'Possible persistent session cookies found...') print(color.RED+ ' [+] Possible CSRF Vulnerability Detected : '+color.ORANGE+url+'!') print(color.ORANGE+' [!] Probable Insecure Practise: '+color.BR+' Persistent Session Cookies '+color.END) - VulnLogger(url, 'Persistent Session Cookies.') + 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...') diff --git a/modules/Referer.py b/modules/Referer.py index c2a29c8..0adec2a 100644 --- a/modules/Referer.py +++ b/modules/Referer.py @@ -67,5 +67,5 @@ def Referer(url): 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) - VulnLogger(url, 'No Referer Header based Request Validation presence.') + 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 fe77f2b..25959c5 100644 --- a/modules/Tamper.py +++ b/modules/Tamper.py @@ -35,7 +35,7 @@ def Tamper(url, action, req, body, query, para): # Coverting the token to a raw string, cause some special # 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. @@ -66,7 +66,7 @@ def Tamper(url, action, req, body, query, para): NovulLogger(url, 'Anti-CSRF Token tamper by index replacement does not return valid response.') if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): flagx1 = 0x01 - VulnLogger(url, 'Anti-CSRF Token tamper by index replacement returns valid response.') + VulnLogger(url, 'Anti-CSRF Token tamper by index replacement returns valid response.', '[i] POST Query: '+req) # [Step 2]: Second we take the token and then remove a char # at a specific position and test the response body. @@ -88,11 +88,12 @@ def Tamper(url, action, req, body, query, para): NovulLogger(url, 'Anti-CSRF Token tamper by index removal does not return valid response.') if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): flagx2 = 0x01 - VulnLogger(url, 'Anti-CSRF Token tamper by index removal returns valid response.') + VulnLogger(url, 'Anti-CSRF Token tamper by index removal returns valid response.', '[i] POST Query: '+req) # [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!') # Lets build up the request... @@ -108,7 +109,7 @@ def Tamper(url, action, req, body, query, para): NovulLogger(url, 'Anti-CSRF Token on removal does not return valid response.') if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): flagx3 = 0x01 - VulnLogger(url, 'Anti-CSRF Token on removal returns valid response.') + VulnLogger(url, 'Anti-CSRF Token on removal returns valid response.', '[i] POST Query: '+req) # If any of the forgeries worked... if (flagx1 or flagx2 or flagx3) == 0x01: @@ -116,7 +117,7 @@ def Tamper(url, action, req, body, query, para): 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) - VulnLogger(url, 'Anti-CSRF Tokens are not Unique. Token Reuse detected.') + VulnLogger(url, 'Anti-CSRF Tokens are not Unique. Token Reuse detected.', '[i] Request: '+copy) else: print(color.RED+' [-] The Tampered Anti-CSRF Token requested returns a 40x or 50x response... ') print(color.GREEN+' [-] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') From e6f4092753ecdd01f0d17ec368f328b2375ed0ce Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 22 Dec 2018 12:39:23 +0000 Subject: [PATCH 59/94] Other impotent stuffs reqd --- files/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 126 bytes files/__pycache__/config.cpython-36.pyc | Bin 0 -> 1220 bytes files/dcodelist.py | 68 +++++++++++----------- 3 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 files/__pycache__/__init__.cpython-36.pyc create mode 100644 files/__pycache__/config.cpython-36.pyc diff --git a/files/__pycache__/__init__.cpython-36.pyc b/files/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebf515fa26b3a088c9934723cf8357b702025331 GIT binary patch literal 126 zcmXr!<>gYVmWa8@z`*brh~a<{$Z`PUViq8g!Vt`$$>_I|p$H_5Abx4;7v<-d=)0sA zXP4v`=vNdMr4&M4u=4F<|$LkeT-r}&y%}*)KNwotRQOpD+7`Or1 CmK;_9 literal 0 HcmV?d00001 diff --git a/files/__pycache__/config.cpython-36.pyc b/files/__pycache__/config.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d653722827ad6fc57e1cf61f54939cc661c71332 GIT binary patch literal 1220 zcmZ8h+j5&`5XJ)8iey<<6320RqNgNwY>@4!IjAQr{k9n+@Bm7je>%e;7RfjgP=hU1 zH+_-5KwqFQ(!2k*SAK=|B3&fs(8BEgyWf7h?8h+dmdnLA|E&G==L*OD#eHy9G5!%P z`2!trE(de&9OT>_@RY|`0Kv`oId>lNFb@k*fE!T!E$=Qs$t}R5djq*ev=Z7PS{ZV5 z3CeB-%5)i)Xcc>J;@n#}>o(R`(C%Qo3Kdv}D%^xya2rCz?bk9d=2;E0X&3t*nmf{30wWDy9ScQt;>wtx372b=D1HU1*EtQB*rCLLL8H5mx5>dCsy0qO8?ehTqFwI&TqNEdBY-*;sBh{pO z?a9uQ53pUt9@lH%NITp0o&Vr&L?Po*7UjnSEPKf%6(I7)`0e;z zWV$Kk{jpE^VRDhVfbwqqn(^Hr%-E-l55gXf4Y4pxJjzGOnDNmCwq168mK)*FVG`p; ziLbBUV+BjKiPJGPlw~R}H7CnizD4#r2edRj9F4pn8mo3JVmitJ(QU_6^p??P#rF_f zY#PRqN}Lyp)*-BXNR$@RrTj~nTDwzsPEv)zG1c!NtvZu~Lae5uJBqGouPoJKOUFjjc|VYq zF?QODrZ!chW6{dKsu72r*hJ4(vuKH&kY>kL_B3Ms&Yj=K4`k~8ejuMmQ7X6m9&dqH1{e_@4hhY?&1{O|F= 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 (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-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 (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 (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}$"), + ("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}$"), + ("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}$"), + ("NTLM", r"^[0-9A-Fa-f]{32}$"), ) # Get rid of Double ../../ From 9dc9368b6a250b3ada56791e1a577d28ea1b365e Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 22 Dec 2018 12:40:08 +0000 Subject: [PATCH 60/94] Added more verbose logging system --- core/logger.py | 4 ++-- core/main.py | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/core/logger.py b/core/logger.py index e592c54..79495f7 100644 --- a/core/logger.py +++ b/core/logger.py @@ -61,8 +61,8 @@ def ErrorLogger(url, error): con = '(i) '+url+' -> '+error.__str__() SCAN_ERRORS.append(con) -def VulnLogger(url, vuln): - tent = '[!] '+url+' -> '+vuln +def VulnLogger(url, vuln, content=''): + tent = '[!] '+url+' -> '+vuln+'\n\n'+content VULN_LIST.append(tent) def NovulLogger(url, strength): diff --git a/core/main.py b/core/main.py index b137d4c..ed61d30 100644 --- a/core/main.py +++ b/core/main.py @@ -144,13 +144,13 @@ def Engine(): # lets begin it! # Go for token based entropy checks... try: if m['name']: - query, token = Entropy(result, url, m['action'], m['name']) + query, token = Entropy(result, url, m.prettify(), m['action'], m['name']) except KeyError: - query, token = Entropy(result, url, m['action']) + query, token = Entropy(result, url, m.prettify(), m['action']) # Now its time to detect the encoding type (if any) of the Anti-CSRF token. - fnd = Encoding(token) - if fnd == 0x01: - VulnLogger(url, 'Token is a string encoded value which can be probably decrypted.') + 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. @@ -169,9 +169,9 @@ def Engine(): # lets begin it! if POST_BASED and not query and not token: try: if m['name']: - PostBased(url, r1, r2, r3, m['action'], result, genpoc, m['name']) + PostBased(url, r1, r2, r3, m['action'], result, genpoc, m.prettify(), m['name']) except KeyError: - PostBased(url, r1, r2, r3, m['action'], result, genpoc) + PostBased(url, r1, r2, r3, 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!') @@ -235,14 +235,14 @@ def Engine(): # lets begin it! # Go for token based entropy checks... try: if m['name']: - query, token = Entropy(result, url, m['action'], m['name']) + query, token = Entropy(result, url, m.prettify(), m['action'], m['name']) except KeyError: - query, token = Entropy(result, url, m['action']) + query, token = Entropy(result, url, 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 = Encoding(token) - if fnd == 0x01: - VulnLogger(url, 'String encoded token value. Token might be decrypted.') + 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. @@ -261,9 +261,9 @@ def Engine(): # lets begin it! if POST_BASED and not query and not token: try: if m['name']: - PostBased(url, r1, r2, r3, m['action'], result, genpoc, m['name']) + PostBased(url, r1, r2, r3, m['action'], result, genpoc, m.prettify(), m['name']) except KeyError: - PostBased(url, r1, r2, r3, m['action'], result, genpoc) + PostBased(url, r1, r2, r3, 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!') From e2207d8486c7bf8e01f36ab405d51e06aa51a69a Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 22 Dec 2018 13:02:20 +0000 Subject: [PATCH 61/94] Trail to fix bugs --- modules/Checkpost.py | 6 +++--- modules/Encoding.py | 2 -- modules/Entropy.py | 2 +- modules/Tamper.py | 8 ++++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/modules/Checkpost.py b/modules/Checkpost.py index ba3fcac..afe0c79 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -48,7 +48,7 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): 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'\n[i] POST Query: '+str(result)+'\n') + 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...') if m_name: @@ -75,7 +75,7 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): if POC_GENERATION: if GEN_MALICIOUS: # Generates a malicious CSRF form - GenMalicious(url, str(genpoc)) + GenMalicious(url, genpoc.__str__()) else: # Generates a normal PoC - GenNormalPoC(url, str(genpoc)) + GenNormalPoC(url, genpoc.__str__()) diff --git a/modules/Encoding.py b/modules/Encoding.py index 0f304c3..9e472e6 100644 --- a/modules/Encoding.py +++ b/modules/Encoding.py @@ -21,8 +21,6 @@ def Encoding(val): Anti-CSRF tokens based on pre-defined regular expressions. ''' - if not val: - return None verbout(GR, 'Proceeding to detect encoding of Anti-CSRF Token...') found = 0x00 # So the idea right here is to detect whether the Anti-CSRF tokens diff --git a/modules/Entropy.py b/modules/Entropy.py index a61c3c8..3ebd068 100644 --- a/modules/Entropy.py +++ b/modules/Entropy.py @@ -51,7 +51,7 @@ def Entropy(req, url, form, m_action, m_name=''): if (para and _q) == None: VulnLogger(url, 'Form Requested Without Anti-CSRF Token.', - '[i] Form Requested: '+form+'\n[i] Request Query: '+req) + '[i] Form Requested: '+form+'\n[i] Request Query: '+req.__str__()) return '', '' # Coverting the token to a raw string, cause some special # chars might fu*k with the Shannon Entropy operation. diff --git a/modules/Tamper.py b/modules/Tamper.py index 25959c5..c221548 100644 --- a/modules/Tamper.py +++ b/modules/Tamper.py @@ -66,7 +66,7 @@ def Tamper(url, action, req, body, query, para): NovulLogger(url, 'Anti-CSRF Token tamper by index replacement does not return valid response.') if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): flagx1 = 0x01 - VulnLogger(url, 'Anti-CSRF Token tamper by index replacement returns valid response.', '[i] POST Query: '+req) + VulnLogger(url, 'Anti-CSRF Token tamper by index replacement returns valid response.', '[i] POST Query: '+str(req)) # [Step 2]: Second we take the token and then remove a char # at a specific position and test the response body. @@ -88,7 +88,7 @@ def Tamper(url, action, req, body, query, para): NovulLogger(url, 'Anti-CSRF Token tamper by index removal does not return valid response.') if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): flagx2 = 0x01 - VulnLogger(url, 'Anti-CSRF Token tamper by index removal returns valid response.', '[i] POST Query: '+req) + VulnLogger(url, 'Anti-CSRF Token tamper by index removal returns valid response.', '[i] POST Query: '+str(req)) # [Step 3]: Third we take the token and then remove the whole # anticsrf token and test the response body. @@ -109,7 +109,7 @@ def Tamper(url, action, req, body, query, para): NovulLogger(url, 'Anti-CSRF Token on removal does not return valid response.') if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) and (len(body) == len(resp.text)): flagx3 = 0x01 - VulnLogger(url, 'Anti-CSRF Token on removal returns valid response.', '[i] POST Query: '+req) + VulnLogger(url, 'Anti-CSRF Token on removal returns valid response.', '[i] POST Query: '+str(req)) # If any of the forgeries worked... if (flagx1 or flagx2 or flagx3) == 0x01: @@ -117,7 +117,7 @@ def Tamper(url, action, req, body, query, para): 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) - VulnLogger(url, 'Anti-CSRF Tokens are not Unique. Token Reuse detected.', '[i] Request: '+copy) + VulnLogger(url, 'Anti-CSRF Tokens are not Unique. Token Reuse detected.', '[i] Request: '+str(copy)) else: print(color.RED+' [-] The Tampered Anti-CSRF Token requested returns a 40x or 50x response... ') print(color.GREEN+' [-] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') From 84b6bdd4227c76ca62dafd621d5fad6740fa3b3f Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 22 Dec 2018 13:02:51 +0000 Subject: [PATCH 62/94] Verbose logger bug fixed --- core/logger.py | 2 +- core/main.py | 6 +++--- files/__pycache__/__init__.cpython-36.pyc | Bin 126 -> 0 bytes files/__pycache__/config.cpython-36.pyc | Bin 1220 -> 0 bytes 4 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 files/__pycache__/__init__.cpython-36.pyc delete mode 100644 files/__pycache__/config.cpython-36.pyc diff --git a/core/logger.py b/core/logger.py index 79495f7..9ac1458 100644 --- a/core/logger.py +++ b/core/logger.py @@ -62,7 +62,7 @@ def ErrorLogger(url, error): SCAN_ERRORS.append(con) def VulnLogger(url, vuln, content=''): - tent = '[!] '+url+' -> '+vuln+'\n\n'+content + tent = '[!] '+url+' -> '+vuln+'\n\n'+str(content)+'\n\n' VULN_LIST.append(tent) def NovulLogger(url, strength): diff --git a/core/main.py b/core/main.py index ed61d30..8565199 100644 --- a/core/main.py +++ b/core/main.py @@ -298,6 +298,6 @@ def Engine(): # lets begin it! print(R+'Aborted!') # say goodbye ErrorLogger('KeyBoard Interrupt', 'Aborted') quit() - except Exception as e: - verbout(R, e.__str__()) - ErrorLogger(url, e) +# except Exception as e: + # verbout(R, e.__str__()) + # ErrorLogger(url, e) diff --git a/files/__pycache__/__init__.cpython-36.pyc b/files/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index ebf515fa26b3a088c9934723cf8357b702025331..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 126 zcmXr!<>gYVmWa8@z`*brh~a<{$Z`PUViq8g!Vt`$$>_I|p$H_5Abx4;7v<-d=)0sA zXP4v`=vNdMr4&M4u=4F<|$LkeT-r}&y%}*)KNwotRQOpD+7`Or1 CmK;_9 diff --git a/files/__pycache__/config.cpython-36.pyc b/files/__pycache__/config.cpython-36.pyc deleted file mode 100644 index d653722827ad6fc57e1cf61f54939cc661c71332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1220 zcmZ8h+j5&`5XJ)8iey<<6320RqNgNwY>@4!IjAQr{k9n+@Bm7je>%e;7RfjgP=hU1 zH+_-5KwqFQ(!2k*SAK=|B3&fs(8BEgyWf7h?8h+dmdnLA|E&G==L*OD#eHy9G5!%P z`2!trE(de&9OT>_@RY|`0Kv`oId>lNFb@k*fE!T!E$=Qs$t}R5djq*ev=Z7PS{ZV5 z3CeB-%5)i)Xcc>J;@n#}>o(R`(C%Qo3Kdv}D%^xya2rCz?bk9d=2;E0X&3t*nmf{30wWDy9ScQt;>wtx372b=D1HU1*EtQB*rCLLL8H5mx5>dCsy0qO8?ehTqFwI&TqNEdBY-*;sBh{pO z?a9uQ53pUt9@lH%NITp0o&Vr&L?Po*7UjnSEPKf%6(I7)`0e;z zWV$Kk{jpE^VRDhVfbwqqn(^Hr%-E-l55gXf4Y4pxJjzGOnDNmCwq168mK)*FVG`p; ziLbBUV+BjKiPJGPlw~R}H7CnizD4#r2edRj9F4pn8mo3JVmitJ(QU_6^p??P#rF_f zY#PRqN}Lyp)*-BXNR$@RrTj~nTDwzsPEv)zG1c!NtvZu~Lae5uJBqGouPoJKOUFjjc|VYq zF?QODrZ!chW6{dKsu72r*hJ4(vuKH&kY>kL_B3Ms&Yj=K4`k~8ejuMmQ7X6m9&dqH1{e_@4hhY?&1{O|F Date: Wed, 26 Dec 2018 01:21:08 +0530 Subject: [PATCH 63/94] Update paramlist.py with a more anti csrf token --- files/paramlist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/files/paramlist.py b/files/paramlist.py index c8e9769..f3b0b4c 100644 --- a/files/paramlist.py +++ b/files/paramlist.py @@ -19,7 +19,8 @@ 'CSRFName', # OWASP CSRF_Guard 'CSRFToken', # OWASP CSRF_Guard 'anticsrf', # AntiCsrfParam.java - '__RequestVerificationToken', # AntiCsrfParam.java + '__RequestVerificationToken', # ASP.NET TokenParam + 'wpnonce', # WordPress 'authenticity_token', # Ruby on Rails 'csrf_param', # Ruby on Rails 'TransientKey', # VanillaForums From 564066a8c464452984c120e97047bab526d72f75 Mon Sep 17 00:00:00 2001 From: Infected Drake Date: Wed, 26 Dec 2018 01:29:04 +0530 Subject: [PATCH 64/94] Update token.py with more elaborate stuff. --- modules/Token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/Token.py b/modules/Token.py index 38bd56d..c1e3c61 100644 --- a/modules/Token.py +++ b/modules/Token.py @@ -36,7 +36,7 @@ def Token(req): qu = c.split('=') # Search if the token is there in request... if name.lower() in qu[0].lower(): - verbout(color.GREEN,' [+] The form was requested with a '+color.ORANGE+'Anti-CSRF Token'+color.GREEN+'...') + verbout(color.GREEN,' [+] The form was requested with an '+color.BG+' Anti-CSRF Token '+color.GREEN+'!') verbout(color.GREY,' [+] Token Parameter: '+color.CYAN+qu[0]+'='+qu[1]+' ...') query, param = qu[0], qu[1] # We are appending the token to a variable for further analysis From 4ec4b6b152d1f47ea32c2dbea6ce87e9d8968443 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Wed, 26 Dec 2018 05:46:58 +0000 Subject: [PATCH 65/94] Added method for improving verbosity --- core/prettify.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/prettify.py b/core/prettify.py index 35cf8b3..a4339de 100644 --- a/core/prettify.py +++ b/core/prettify.py @@ -45,3 +45,30 @@ def formPrettify(response): 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 From c50855b801f34dc0c2d6cbac2f321a0645b00599 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Wed, 26 Dec 2018 05:47:26 +0000 Subject: [PATCH 66/94] Improved malicious form generator logic --- modules/Generator.py | 137 ++++++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 75 deletions(-) diff --git a/modules/Generator.py b/modules/Generator.py index cf22199..5edd40d 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -11,55 +11,48 @@ from core.colors import * from ast import literal_eval +from bs4 import BeautifulSoup +from yattag import Doc, indent from files.config import OUTPUT_DIR -from bs4 import BeautifulSoup, Tag from core.prettify import formPrettify +from core.prettify import indentPrettify -def GenNormalPoC(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): +doc, tag, text = Doc().tagtext() + +def GenNormalPoC(action, fields, method='POST', encoding_type='text/plain'): """ Generate a normal CSRF PoC using basic form data - """ + """ print(GR, 'Generating normal PoC Form...' ) - print(color.RED+'\n +--------------+') - print(color.RED+' | Form PoC |') - print(color.RED+' +--------------+\n'+color.CYAN) + print(color.RED+'\n +---------------------+') + print(color.RED+' | Normal Form PoC |') + print(color.RED+' +---------------------+\n'+color.CYAN) # Main starting which we will use to generate form. - # Small hack to make beautifulsoup work in the reverse way ;) - content = BeautifulSoup("", "html.parser") - html_tag = content.find("html") - title_tag = content.new_tag('title') - # Adding the title_tag - title_tag.string = 'CSRF PoC' - html_tag.append(title_tag) - head_tag = content.new_tag('h2') - # Adding a

tag for heading - head_tag.string = 'Your CSRF PoC' - html_tag.append(head_tag) - # Init to the form and stuff - form_tag = content.new_tag("form", action=action, method=method, enctype=encoding_type) - html_tag.append(form_tag) - # We generate the form by taking input and stuff. - for field in literal_eval(fields): - label_tag = content.new_tag('label') - label_tag.string = field['label'] - field_tag = content.new_tag("input", type=field['type'], value=field['value']) - field_tag['name'] = field['name'] - form_tag.append(label_tag) - form_tag.append(field_tag) - # The form submit button and tag - submit_tag = content.new_tag("input", type="submit", value='Submit') - form_tag.append(submit_tag) - br_tag = content.new_tag('br') - html_tag.append(br_tag) - footer_tag = content.new_tag('footer') - html_tag.append(footer_tag) - small_tag = content.new_tag('small') - # Fancy brand tag ;p - small_tag.string = '(i) This PoC form was generated by XSRFProbe.' - footer_tag.append(small_tag) - formPrettify(content.prettify()) + 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, indent=4)) print('') - # Write out the file af o ]}) + # 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() @@ -68,42 +61,36 @@ def GenNormalPoC(action, fields, method='POST', encoding_type='application/x-www 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...' ) - print(color.RED+'\n +-------------------+') - print(color.RED+' | Malicious PoC |') - print(color.RED+' +-------------------+\n'+color.CYAN) - content = BeautifulSoup("", "html.parser") - html_tag = content.find("html") - title_tag = content.new_tag('title') - title_tag.string = 'Get Money ($$$)' - html_tag.append(title_tag) - head_tag = content.new_tag('p') - # Doesn't look clickbait at all :p - # TODO: WIll improve this is future. - head_tag.string = 'Click below to get $100 bonus.' - html_tag.append(head_tag) - form_tag = content.new_tag("form", action=action, method=method, enctype=encoding_type) - html_tag.append(form_tag) - # This is where we get the forms' field values - # Since it is a malicious form, all the form field values must be hidden ;) - for field in literal_eval(fields): - if not field['value']: - # So that is why we ask user for input ;) - q = input(color.ORANGE+' [+] Enter field value for "'+color.GREY+field['label']+'" :> '+color.CYAN) - field_tag = content.new_tag("input", type='hidden', value=q) - else: - field_tag = content.new_tag("input", type='hidden', value=field['value']) - field_tag['name'] = field['name'] - form_tag.append(field_tag) - - submit_tag = content.new_tag("input", type="submit", value='Get It') - form_tag.append(submit_tag) - # Print out the form pretty well formatted - formPrettify(content.prettify()) + print(color.RED+'\n +------------------------+') + print(color.RED+' | Malicious Form PoC |') + print(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, indent=4)) print('') - # Print out the directory where it is saved - fi = open(OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-csrf-poc.html', 'w+', encoding='utf8') + # 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') From d1a7db2d3851d5e59825da75cd86c0727eedd3d5 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 27 Dec 2018 13:10:29 +0000 Subject: [PATCH 67/94] Improved main workflow --- core/main.py | 54 ++++++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/core/main.py b/core/main.py index a06f95f..3453668 100644 --- a/core/main.py +++ b/core/main.py @@ -70,23 +70,18 @@ def Engine(): # lets begin it! web, fld = inputin() # Take the input form1 = form10() # Get the form 1 ready form2 = form20() # 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 Flag ori_detect = 0x00 # Null Char Flags form = Debugger.Form_Debugger() # init to the form parser+token generator - - bs1 = BeautifulSoup(form1).findAll('form',action=True)[0] # make sure the stuff works properly - bs2 = BeautifulSoup(form2).findAll('form',action=True)[0] # same as above - + bs1 = BeautifulSoup(form1).findAll('form', action=True)[0] # make sure the stuff works properly + bs2 = BeautifulSoup(form2).findAll('form', action=True)[0] # same as above init1 = web # First init resp1.open(init1) # Makes request as User2 resp2.open(init1) # Make request as User1 @@ -111,13 +106,10 @@ def Engine(): # lets begin it! 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 @@ -138,15 +130,18 @@ def Engine(): # lets begin it! try: # NOTE: Slow connections may cause read timeouts which may result in AttributeError result, genpoc = form.prepareFormInputs(m) # prepare inputs - r1 = Post(url, action, result).text # make request with token values generated as user1 + r1 = Post(url, action, result) # make request with token values generated as user1 result, genpoc = form.prepareFormInputs(m) # prepare the input types - r2 = Post(url, action, result).text # again make request with token values generated as user2 + 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, m.prettify(), m['action'], m['name']) + query, token = Entropy(result, url, r1.headers, m.prettify(), m['action'], m['name']) except KeyError: - query, token = Entropy(result, url, m.prettify(), m['action']) + query, token = Entropy(result, url, r1.headers, m.prettify(), m['action']) # Now its time to detect the encoding type (if any) of the Anti-CSRF token. fnd, detct = Encoding(token) if fnd == 0x01 and detct: @@ -155,23 +150,23 @@ def Engine(): # lets begin it! NovulLogger(url, 'Anti-CSRF token is not a string encoded value.') # Go for token parameter tamper checks. if (query and token): - Tamper(url, action, result, r2, query, token) + 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 Error') ErrorLogger(url, 'Form Index Error.') - continue # making sure program won't end here (dirty fix :( ) + continue # Making sure program won't end here (dirty fix :( ) verbout(GR, 'Preparing form inputs...') contents2, genpoc = form.prepareFormInputs(form2) # prepare for form 2 as user2 - r3 = Post(url,action,contents2).text # make request as user3 with user2's form + r3 = Post(url, action, contents2) # make request as user3 with user2's form if POST_BASED and not query and not token: try: if m['name']: - PostBased(url, r1, r2, r3, m['action'], result, genpoc, m.prettify(), m['name']) + PostBased(url, r1.text, r2.text, r3.text, m['action'], result, genpoc, m.prettify(), m['name']) except KeyError: - PostBased(url, r1, r2, r3, m['action'], result, genpoc, m.prettify()) + PostBased(url, r1.text, r2.text, r3.text, m['action'], result, genpoc, m.prettify()) else: print(color.GREEN+' [+] The form was requested with a Anti-CSRF token.') print(color.GREEN+' [+] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to POST-Based CSRF Attacks!') @@ -179,20 +174,15 @@ def Engine(): # lets begin it! 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: @@ -204,15 +194,12 @@ def Engine(): # lets begin it! 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 @@ -229,9 +216,9 @@ def Engine(): # lets begin it! if FORM_SUBMISSION: try: result, genpoc = form.prepareFormInputs(m) # prepare inputs - r1 = Post(url, action, result).text # make request with token values generated as user1 + r1 = Post(url, action, result) # make request with token values generated as user1 result, genpoc = form.prepareFormInputs(m) # prepare the input types - r2 = Post(url, action, result).text # again make request with token values generated as user2 + r2 = Post(url, action, result) # again make request with token values generated as user2 # Go for token based entropy checks... try: if m['name']: @@ -247,7 +234,7 @@ def Engine(): # lets begin it! NovulLogger(url, 'Anti-CSRF token is not a string encoded value.') # Go for token parameter tamper checks. if (query and token): - Tamper(url, action, result, r2, query, token) + 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 @@ -257,13 +244,13 @@ def Engine(): # lets begin it! continue # making sure program won't end here (dirty fix :( ) verbout(GR, 'Preparing form inputs...') contents2, genpoc = form.prepareFormInputs(form2) # prepare for form 2 as user2 - r3 = Post(url,action,contents2).text # make request as user3 with user2's form + r3 = Post(url, action, contents2) # make request as user3 with user2's form if POST_BASED and not query and not token: try: if m['name']: - PostBased(url, r1, r2, r3, m['action'], result, genpoc, m.prettify(), m['name']) + PostBased(url, r1.text, r2.text, r3.text, m['action'], result, genpoc, m.prettify(), m['name']) except KeyError: - PostBased(url, r1, r2, r3, m['action'], result, genpoc, m.prettify()) + PostBased(url, r1.text, r2.text, r3.text, m['action'], result, genpoc, m.prettify()) else: print(color.GREEN+' [+] The form was requested with a Anti-CSRF token.') print(color.GREEN+' [+] Endpoint '+color.BG+' NOT VULNERABLE '+color.END+color.GREEN+' to P0ST-Based CSRF Attacks!') @@ -297,6 +284,7 @@ def Engine(): # lets begin it! 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__()) From b7620e6366c63757e4ff25a300b4f57411891b68 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 27 Dec 2018 13:11:04 +0000 Subject: [PATCH 68/94] Added some exception handling stuff --- core/request.py | 12 ++++++++++++ core/updater.py | 3 +++ 2 files changed, 15 insertions(+) diff --git a/core/request.py b/core/request.py index 79ccdcb..7500dfb 100644 --- a/core/request.py +++ b/core/request.py @@ -87,3 +87,15 @@ def Get(url, headers=headers): 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 8a0535d..f54e09b 100644 --- a/core/updater.py +++ b/core/updater.py @@ -14,6 +14,9 @@ from requests import get def updater(): + ''' + Function to update XSRFProbe seamlessly. + ''' print(GR+'Checking for updates...') vno = get('https://raw.githubusercontent.com/0xInfection/XSRFProbe/master/files/VersionNum').text print(GR+'Version on GitHub: '+color.CYAN+vno.strip()) From 794cb6ec87f458bf67d473585737d95ad7776c21 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 27 Dec 2018 13:11:32 +0000 Subject: [PATCH 69/94] Added more of anti-csrf tokens --- files/paramlist.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/files/paramlist.py b/files/paramlist.py index f3b0b4c..5f8b9f5 100644 --- a/files/paramlist.py +++ b/files/paramlist.py @@ -20,17 +20,20 @@ 'CSRFToken', # OWASP CSRF_Guard 'anticsrf', # AntiCsrfParam.java '__RequestVerificationToken', # ASP.NET TokenParam - 'wpnonce', # WordPress + '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 '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. @@ -42,13 +45,11 @@ 'auth_token', 'auth', 'anti_csrf', - 'auth_value', + 'debug_token', 'csrf_value', '_debugval', 'csrf_token', - 'VerificationToken', '__authvalue', - 'authenticity_value', '__token', '__auth', 'secret', @@ -67,6 +68,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', From 468ce3bf000a8dec972923d12cc39e4a6c89983b Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 27 Dec 2018 13:11:52 +0000 Subject: [PATCH 70/94] Updated logic about post based csrf --- modules/Checkpost.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/Checkpost.py b/modules/Checkpost.py index afe0c79..ab82c92 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -29,7 +29,7 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): checkdiffx2 = difflib.ndiff(r1.splitlines(1), r3.splitlines(1)) # check the diff noted result12 = [] # an init 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: @@ -39,13 +39,13 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): if not m_action.startswith('/'): m_action = '/' + m_action - # This logic is based purely on the assumption on the difference of requests and - # response body. + # 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 algorithm has lots of scopes of improvement... - if len(result12)<=len(result13): + 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') @@ -73,6 +73,7 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): 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__()) From 02106e7b1803d880f83c54944ae9cdf81b2cee50 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 27 Dec 2018 13:12:28 +0000 Subject: [PATCH 71/94] Persistence checks highly improvised --- modules/Cookie.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/Cookie.py b/modules/Cookie.py index 66ad653..c574118 100644 --- a/modules/Cookie.py +++ b/modules/Cookie.py @@ -22,7 +22,7 @@ resps = [] -def Cookie(url): +def Cookie(url, request): ''' This module is for checking the varied HTTP Cookies and the related security on them to @@ -30,7 +30,7 @@ def Cookie(url): ''' print(color.GREY+' [+] Proceeding for cookie based checks...') SameSite(url) - Persistence(url) + Persistence(url, request) def SameSite(url): ''' @@ -130,7 +130,7 @@ def SameSite(url): m = cookieval.split(';') verbout(GR,'Examining Cookie...') for q in m: - if search('SameSite', q, I): + 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() @@ -140,7 +140,7 @@ 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 to ANY type of CSRF attacks! '+color.END) From e18240eb2e7bee6a3de0b94f946f512e600a7fb8 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 27 Dec 2018 13:13:10 +0000 Subject: [PATCH 72/94] Resolved entropy issues with tokens --- modules/Entropy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/Entropy.py b/modules/Entropy.py index 3ebd068..0c42aea 100644 --- a/modules/Entropy.py +++ b/modules/Entropy.py @@ -17,7 +17,7 @@ from core.verbout import verbout from core.logger import VulnLogger, NovulLogger -def Entropy(req, url, form, m_action, m_name=''): +def Entropy(req, url, headers, form, m_action, m_name=''): """ This function has the work of comparing and calculating Shannon Entropy and related @@ -47,7 +47,7 @@ def Entropy(req, url, form, m_action, m_name=''): min_entropy = 2.4 # Check for common CSRF token names - _q, para = Token(req) + _q, para = Token(req, headers) if (para and _q) == None: VulnLogger(url, 'Form Requested Without Anti-CSRF Token.', From d8664d72c429a992b72f839a0ab5d93fc610f2d3 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 27 Dec 2018 13:13:48 +0000 Subject: [PATCH 73/94] Added staged persistence checks - Huge improvement --- modules/Persistence.py | 71 +++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/modules/Persistence.py b/modules/Persistence.py index ca90af3..8e6c185 100644 --- a/modules/Persistence.py +++ b/modules/Persistence.py @@ -16,45 +16,90 @@ 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 = [] +found = 0x00 -def Persistence(url): +def Persistence(url, postq): ''' The main idea behind this is to check for Cookie - Persistence (the cookie supplied by user). + Persistence. ''' # Checking if user has supplied a value. + verbout(GR,'Proceeding to test for '+color.GREY+'Cookie Persistence'+color.END+'...') + time.sleep(0.7) + + # 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['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) - # So the idea is to + 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 + verbout(C, 'Proceeding to test cookie persistence on '+color.CYAN+'POST Requests'+color.END+'...') + # Now its time for POST Based 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 + + # [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+' [!] Probable Insecure Practise: '+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...') @@ -62,5 +107,3 @@ def Persistence(url): 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) NovulLogger(url, 'No Persistent Cookies.') - else: - verbout(R,'Skipping persistence checks as no cookie value supplied...') From 55e463f85d41be20e449fcc72a0ae9fd2f560480 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 27 Dec 2018 13:14:02 +0000 Subject: [PATCH 74/94] Added tokenizer based checks --- modules/Token.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/modules/Token.py b/modules/Token.py index c1e3c61..0ce003e 100644 --- a/modules/Token.py +++ b/modules/Token.py @@ -16,15 +16,16 @@ 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. ''' param = '' # Initializing param query = '' + found = False # First lets have a look at config.py and see if its set if config.TOKEN_CHECKS: verbout(O,'Parsing request for detecting anti-csrf tokens...') @@ -36,21 +37,28 @@ def Token(req): qu = c.split('=') # Search if the token is there in request... if name.lower() in qu[0].lower(): - verbout(color.GREEN,' [+] The form was requested with an '+color.BG+' Anti-CSRF Token '+color.GREEN+'!') - verbout(color.GREY,' [+] Token Parameter: '+color.CYAN+qu[0]+'='+qu[1]+' ...') + 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]+'='+qu[1]+' ...') query, param = qu[0], qu[1] # 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 - # Setting this to False since the form was requested with an Anti-CSRF token, - # there is an unique id for every form. There are other checks like token reuse and - # other stuff, but we do not need the POST-Based Check for now, for this form. - config.POST_BASED = False # Clean hack ;) - + # 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]+'='+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 Exception!') - verbout(R,'Error: '+e.__str__()) - + verbout(R, 'Request Parsing Exception!') + verbout(R, 'Error: '+e.__str__()) if param: return (query, param) verbout(color.ORANGE,' [-] The form was requested '+color.RED+' Without an Anti-CSRF Token '+color.END+color.ORANGE+'...') From 0725623ea42df4a275e92841c2452e0bb379100f Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Thu, 27 Dec 2018 13:14:30 +0000 Subject: [PATCH 75/94] Updated a requirement dependency --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index ba29c0e..73bc67f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ bs4 request stringdist tld +yattag From 566a020999b628003489606645a946818d81fca9 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 07:51:51 +0000 Subject: [PATCH 76/94] Improved parsing logic and detection --- modules/Token.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/Token.py b/modules/Token.py index 0ce003e..332520c 100644 --- a/modules/Token.py +++ b/modules/Token.py @@ -23,6 +23,9 @@ def Token(req, headers): This method checks for whether Anti-CSRF Tokens are present in the request. ''' + print(color.RED+'\n +---------------------------+') + print(color.RED+' | Anti-CSRF Token Check |') + print(color.RED+' +---------------------------+\n') param = '' # Initializing param query = '' found = False @@ -38,7 +41,7 @@ def Token(req, headers): # 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]+'='+qu[1]+' ...') + verbout(color.GREY, ' [+] Token Parameter: '+color.CYAN+qu[0]+'='+color.ORANGE+qu[1]) query, param = qu[0], qu[1] # We are appending the token to a variable for further analysis REQUEST_TOKENS.append(param) @@ -51,7 +54,7 @@ def Token(req, headers): # 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]+'='+qu[1]+' ...') + 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) From fde6e9d4f46f4853f0fcffe72d3b938e410a3596 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 07:52:14 +0000 Subject: [PATCH 77/94] Added stuff reqd --- modules/Tamper.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/Tamper.py b/modules/Tamper.py index c221548..bd4b57c 100644 --- a/modules/Tamper.py +++ b/modules/Tamper.py @@ -24,6 +24,9 @@ def Tamper(url, action, req, body, query, para): found and check the content length for related vulnerabilities. ''' + print(color.RED+'\n +---------------------------------------+') + print(color.RED+' | Anti-CSRF Token Tamper Validation |') + print(color.RED+' +---------------------------------------+\n') # Null char flags (hex) flagx1 = 0x00 flagx2 = 0x00 @@ -95,7 +98,7 @@ def Tamper(url, action, req, body, query, para): 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.GREEN, ' [+] Removed token parameter from request!') # Lets build up the request... resp = Post(url, action, req) From 181873b1c3c1816c94107b44d0743c21fbde5f40 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 07:52:30 +0000 Subject: [PATCH 78/94] Added more checks to suit build --- modules/Checkpost.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/Checkpost.py b/modules/Checkpost.py index ab82c92..772ff28 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -25,9 +25,14 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): on basis of fuzzy string matching and comparison based on Ratcliff-Obershelp Algorithm. ''' + print(color.RED+'\n +------------------------------+') + print(color.RED+' | POST-Based Forgery Check |') + print(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 only +/- result12.append(n) # append to existing list From 5c3eb69dbc9b9397283a885877573e0a29496a18 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 07:53:04 +0000 Subject: [PATCH 79/94] Updated modules with documentation --- modules/Entropy.py | 70 +++++++++++++++++++++++--------------------- modules/Generator.py | 6 ++-- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/modules/Entropy.py b/modules/Entropy.py index 0c42aea..1aafbbf 100644 --- a/modules/Entropy.py +++ b/modules/Entropy.py @@ -15,6 +15,7 @@ from core.colors import * from .Token import Token from core.verbout import verbout +from files.discovered import REQUEST_TOKENS from core.logger import VulnLogger, NovulLogger def Entropy(req, url, headers, form, m_action, m_name=''): @@ -24,7 +25,7 @@ def Entropy(req, url, headers, form, m_action, m_name=''): POST Based requests' security. """ - + found = 0x00 # The minimum length of a csrf token should be 5 bytes. min_length = 5 @@ -49,41 +50,44 @@ def Entropy(req, url, headers, form, m_action, m_name=''): # Check for common CSRF token names _q, para = Token(req, headers) if (para and _q) == None: - VulnLogger(url, + VulnLogger (url, 'Form Requested Without Anti-CSRF Token.', '[i] Form Requested: '+form+'\n[i] Request Query: '+req.__str__()) return '', '' - # Coverting the token to a raw string, cause some special - # chars might fu*k with the Shannon Entropy operation. - value = r'%s' % para - - # 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.ORANGE+' [!] Vulnerability Type: '+color.BG+' 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.ORANGE+' to CSRF Attacks...') - print(color.ORANGE+' [!] CSRF Mitigation Method: '+color.BG+' Long Anti-CSRF Tokens '+color.END) - NovulLogger(url, 'Long Anti-CSRF tokens with Good Strength.') - - # 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+' NOT VULNERABLE '+color.END+color.ORANGE+' to CSRF Attacks...') - print(color.ORANGE+' [!] CSRF Mitigation Method: '+color.BG+' High Entropy Anti-CSRF Tokens '+color.END) - NovulLogger(url, 'High Entropy Anti-CSRF Tokens.') - 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.ORANGE+' to CSRF Attacks inspite of CSRF Tokens...') - print(color.ORANGE+' [!] Vulnerability Type: '+color.BR+' Low Entropy Anti-CSRF Tokens '+color.END) - VulnLogger(url, 'Low Entropy Anti-CSRF Tokens.', 'Token: '+value) + 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: %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+' 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 |') diff --git a/modules/Generator.py b/modules/Generator.py index 5edd40d..91e9afc 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -19,7 +19,7 @@ doc, tag, text = Doc().tagtext() -def GenNormalPoC(action, fields, method='POST', encoding_type='text/plain'): +def GenNormalPoC(action, fields, method='POST', encoding_type='application/x-www-form-urlencoded'): """ Generate a normal CSRF PoC using basic form data """ @@ -50,7 +50,7 @@ def GenNormalPoC(action, fields, method='POST', encoding_type='text/plain'): text('XSRFProbe') text('.') content = BeautifulSoup(doc.getvalue(), 'html.parser') - formPrettify(indentPrettify(content, indent=4)) + formPrettify(indentPrettify(content)) print('') # Write out the file af... fi = open(OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-csrf-poc.html', 'w+', encoding='utf8') @@ -87,7 +87,7 @@ def GenMalicious(action, fields, method='POST', encoding_type='application/x-www # Brand tag :p ...I guess... doc.asis('') content = BeautifulSoup(doc.getvalue(), 'html.parser') - formPrettify(indentPrettify(content, indent=4)) + formPrettify(indentPrettify(content)) print('') # Write out the file af... fi = open(OUTPUT_DIR+action.split('//')[1].replace('/','-')+'-malicious-poc.html', 'w+', encoding='utf8') From 67594b692836c52845c8665629b45ccb33e81d6a Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 07:53:24 +0000 Subject: [PATCH 80/94] Heavy code refactos --- modules/Debugger.py | 31 +++++++++++++++++-------------- modules/Encoding.py | 23 ++++++++++++++--------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/modules/Debugger.py b/modules/Debugger.py index 05a00a8..2927acc 100644 --- a/modules/Debugger.py +++ b/modules/Debugger.py @@ -55,19 +55,22 @@ def prepareFormInputs(self, form): cr1['value'] = '' totcr.append(cr1) - verbout(GR, 'Processing'+color.BOLD+' Date: Fri, 28 Dec 2018 07:54:30 +0000 Subject: [PATCH 82/94] Added options to put output directory --- core/inputin.py | 27 ++++++++++++++++++++++++--- core/options.py | 4 ++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/core/inputin.py b/core/inputin.py index 82fd76b..579d6ad 100644 --- a/core/inputin.py +++ b/core/inputin.py @@ -9,7 +9,7 @@ #This module requires XSRF-Probe #https://github.com/0xInfection/XSRF-Probe -import sys, socket +import socket, requests from tld import get_fld from core.colors import * from files.config import SITE_URL @@ -24,12 +24,33 @@ def inputin(): 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) + quit() + try: + print(O+'Testing '+color.GREY+web+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: diff --git a/core/options.py b/core/options.py index f12c86e..0ec3f2a 100644 --- a/core/options.py +++ b/core/options.py @@ -159,10 +159,10 @@ config.OUTPUT_DIR = args.output+tld.get_fld(config.SITE_URL) + '/' else: try: - os.makedirs(tld.get_fld(config.SITE_URL)) + os.makedirs('output/'+tld.get_fld(config.SITE_URL)) except FileExistsError: pass - config.OUTPUT_DIR = tld.get_fld(config.SITE_URL) + '/' + config.OUTPUT_DIR = 'output/'+tld.get_fld(config.SITE_URL) + '/' if args.quiet: config.DEBUG = False From 962737ac81c672044d52595c8ef87e2d316eb6e1 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 07:54:45 +0000 Subject: [PATCH 83/94] improved build logic detection --- modules/Cookie.py | 5 ++++- modules/Origin.py | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/Cookie.py b/modules/Cookie.py index c574118..fdb5254 100644 --- a/modules/Cookie.py +++ b/modules/Cookie.py @@ -28,7 +28,7 @@ def Cookie(url, request): 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, request) @@ -37,6 +37,9 @@ def SameSite(url): This function parses and verifies the cookies with SameSite Flags. ''' + print(color.RED+'\n +------------------------------------+') + print(color.RED+' | Cross Origin Cookie Validation |') + print(color.RED+' +------------------------------------+\n') # Some Flags we'd need later... foundx1 = 0x00 foundx2 = 0x00 diff --git a/modules/Origin.py b/modules/Origin.py index 6048666..ae81a3e 100644 --- a/modules/Origin.py +++ b/modules/Origin.py @@ -22,7 +22,9 @@ def Origin(url): Check if the remote web application verifies the Origin before processing the HTTP request. """ - + print(color.RED+'\n +-------------------------------------+') + print(color.RED+' | Origin Based Request Validation |') + print(color.RED+' +-------------------------------------+\n') # Make the request normally and get content verbout(O,'Making request on normal basis...') req0x01 = Get(url) @@ -62,9 +64,9 @@ def Origin(url): 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) VulnLogger(url, 'No Origin Header based request validation presence.', '[i] Response Headers: '+str(req0x02.headers)) return False From afb61027c4c5059ec6387fbb98d6d113574ddc12 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 07:55:23 +0000 Subject: [PATCH 84/94] A separate directory for outputting neatly --- output/example.com/README | 3 ++ output/example.com/errored.log | 2 ++ output/example.com/example.com-csrf-poc.html | 25 ++++++++++++++ output/example.com/forms-tested.log | 7 ++++ output/example.com/strengths.log | 2 ++ output/example.com/vulnerabilities.log | 34 ++++++++++++++++++++ 6 files changed, 73 insertions(+) create mode 100644 output/example.com/README create mode 100644 output/example.com/errored.log create mode 100644 output/example.com/example.com-csrf-poc.html create mode 100644 output/example.com/forms-tested.log create mode 100644 output/example.com/strengths.log create mode 100644 output/example.com/vulnerabilities.log 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'} + From 8eee69f413d750b393e46f6901a2a2a9befb3ee4 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 07:56:02 +0000 Subject: [PATCH 85/94] Improved the logic in detection rates and Analysis --- core/main.py | 25 +++++++++++++++---------- modules/Analysis.py | 41 ++++++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/core/main.py b/core/main.py index 3453668..905f323 100644 --- a/core/main.py +++ b/core/main.py @@ -129,9 +129,14 @@ def Engine(): # lets begin it! if FORM_SUBMISSION: try: # NOTE: Slow connections may cause read timeouts which may result in AttributeError - result, genpoc = form.prepareFormInputs(m) # prepare inputs + # 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 the input types + 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: @@ -155,12 +160,12 @@ def Engine(): # lets begin it! try: form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form except IndexError: - verbout(R, 'Form Error') + 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 2 as user2 - r3 = Post(url, action, contents2) # make request as user3 with user2's form + 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 and not token: try: if m['name']: @@ -215,9 +220,9 @@ def Engine(): # lets begin it! # If form submission is kept to True if FORM_SUBMISSION: try: - result, genpoc = form.prepareFormInputs(m) # prepare inputs + 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 the input types + 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 token based entropy checks... try: @@ -239,12 +244,12 @@ def Engine(): # lets begin it! try: form2 = Debugger.getAllForms(BeautifulSoup(o2))[i] # user2 gets his form except IndexError: - verbout(R, 'Form Error') + 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 2 as user2 - r3 = Post(url, action, contents2) # make request as user3 with user2's form + 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 and not token: try: if m['name']: diff --git a/modules/Analysis.py b/modules/Analysis.py index 68f6253..61fa16a 100644 --- a/modules/Analysis.py +++ b/modules/Analysis.py @@ -12,6 +12,7 @@ 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 @@ -37,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(C, 'First Token: '+color.ORANGE+tokenx1) + verbout(color.GREEN, ' [+] Shannon Entropy: %s' % (calcEntropy(tokenx1))) + verbout(C, 'Second Token: '+color.ORANGE+tokenx2) + verbout(color.GREEN, ' [+] Shannon Entropy: %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)+'%') @@ -60,40 +63,40 @@ def Analysis(): # # The main idea behind this is to detect the static and dynamic part via DL Algorithm # as discussed above by calculating edit distance. + p = sameSequence(tokenx1, tokenx2) + tokenx01 = tokenx1.replace(p,'') + tokenx02 = tokenx2.replace(p,'') if n == 0.5 or m == len(tokenx1)/2: verbout(GR, 'The tokens are composed of 2 parts (one static and other dynamic)... ') - 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.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.GREEN,' [+] 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 - print('\n'+C+'Post-Scan Analysis Completed!') + print(C+'Post-Scan Analysis Completed!') From 662c5cfa9b3c329c52e34f4b14b636d905214470 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 08:13:44 +0000 Subject: [PATCH 86/94] Trail to fix build error --- core/main.py | 8 ++++---- modules/Analysis.py | 6 +++--- modules/Checkpost.py | 6 +++--- modules/Cookie.py | 6 +++--- modules/Encoding.py | 6 +++--- modules/Entropy.py | 3 +++ modules/Generator.py | 12 ++++++------ modules/Origin.py | 6 +++--- modules/Persistence.py | 6 +++--- modules/Referer.py | 6 +++--- modules/Tamper.py | 6 +++--- modules/Token.py | 6 +++--- 12 files changed, 40 insertions(+), 37 deletions(-) diff --git a/core/main.py b/core/main.py index 905f323..cdb660e 100644 --- a/core/main.py +++ b/core/main.py @@ -203,8 +203,6 @@ def Engine(): # lets begin it! 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 @@ -224,12 +222,14 @@ def Engine(): # lets begin it! r1 = Post(url, action, result) # make request with token values generated as user1 result, genpoc = form.prepareFormInputs(m) # prepare inputs as user 2 r2 = Post(url, action, result) # again make request with token values generated as user2 + if COOKIE_BASED: + Cookie(url, r1) # Go for token based entropy checks... try: if m['name']: - query, token = Entropy(result, url, m.prettify(), m['action'], m['name']) + query, token = Entropy(result, url, r1.headers, m.prettify(), m['action'], m['name']) except KeyError: - query, token = Entropy(result, url, m.prettify(), m['action']) + query, token = Entropy(result, url, r1.headers, m.prettify(), m['action']) ErrorLogger(url, 'No standard form "name".') # Now its time to detect the encoding type (if any) of the Anti-CSRF token. fnd, detct = Encoding(token) diff --git a/modules/Analysis.py b/modules/Analysis.py index 61fa16a..144bcdb 100644 --- a/modules/Analysis.py +++ b/modules/Analysis.py @@ -27,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 diff --git a/modules/Checkpost.py b/modules/Checkpost.py index 772ff28..c561f36 100644 --- a/modules/Checkpost.py +++ b/modules/Checkpost.py @@ -25,9 +25,9 @@ def PostBased(url, r1, r2, r3, m_action, result, genpoc, form, m_name=''): on basis of fuzzy string matching and comparison based on Ratcliff-Obershelp Algorithm. ''' - print(color.RED+'\n +------------------------------+') - print(color.RED+' | POST-Based Forgery Check |') - print(color.RED+' +------------------------------+\n') + 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 diff --git a/modules/Cookie.py b/modules/Cookie.py index fdb5254..9aac5a3 100644 --- a/modules/Cookie.py +++ b/modules/Cookie.py @@ -37,9 +37,9 @@ def SameSite(url): This function parses and verifies the cookies with SameSite Flags. ''' - print(color.RED+'\n +------------------------------------+') - print(color.RED+' | Cross Origin Cookie Validation |') - print(color.RED+' +------------------------------------+\n') + 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 diff --git a/modules/Encoding.py b/modules/Encoding.py index 43263c4..ebc8c07 100644 --- a/modules/Encoding.py +++ b/modules/Encoding.py @@ -24,9 +24,9 @@ def Encoding(val): found = 0x00 if not val: return(found, None) - print(color.RED+'\n +------------------------------+') - print(color.RED+' | Token Encoding Detection |') - print(color.RED+' +------------------------------+\n') + verbout(color.RED, '\n +------------------------------+') + verbout(color.RED, ' | Token Encoding Detection |') + verbout(color.RED, ' +------------------------------+\n') verbout(GR, 'Proceeding to detect encoding of Anti-CSRF Token...') # So the idea right here is to detect whether the Anti-CSRF tokens # are encoded in some form or the other. diff --git a/modules/Entropy.py b/modules/Entropy.py index 1aafbbf..11ac2ce 100644 --- a/modules/Entropy.py +++ b/modules/Entropy.py @@ -54,6 +54,9 @@ def Entropy(req, url, headers, form, m_action, m_name=''): 'Form Requested Without Anti-CSRF Token.', '[i] Form Requested: '+form+'\n[i] Request Query: '+req.__str__()) return '', '' + 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. diff --git a/modules/Generator.py b/modules/Generator.py index 91e9afc..d02e380 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -24,9 +24,9 @@ def GenNormalPoC(action, fields, method='POST', encoding_type='application/x-www Generate a normal CSRF PoC using basic form data """ print(GR, 'Generating normal PoC Form...' ) - print(color.RED+'\n +---------------------+') - print(color.RED+' | Normal Form PoC |') - print(color.RED+' +---------------------+\n'+color.CYAN) + 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'): @@ -63,9 +63,9 @@ def GenMalicious(action, fields, method='POST', encoding_type='application/x-www Generate a malicious CSRF PoC using basic form data """ print(GR, 'Generating malicious PoC Form...' ) - print(color.RED+'\n +------------------------+') - print(color.RED+' | Malicious Form PoC |') - print(color.RED+' +------------------------+\n'+color.CYAN) + 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'): diff --git a/modules/Origin.py b/modules/Origin.py index ae81a3e..6292e14 100644 --- a/modules/Origin.py +++ b/modules/Origin.py @@ -22,9 +22,9 @@ def Origin(url): Check if the remote web application verifies the Origin before processing the HTTP request. """ - print(color.RED+'\n +-------------------------------------+') - print(color.RED+' | Origin Based Request Validation |') - print(color.RED+' +-------------------------------------+\n') + 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) diff --git a/modules/Persistence.py b/modules/Persistence.py index 38faa46..a1acd3d 100644 --- a/modules/Persistence.py +++ b/modules/Persistence.py @@ -28,9 +28,9 @@ def Persistence(url, postq): The main idea behind this is to check for Cookie Persistence. ''' - print(color.RED+'\n +-----------------------------------+') - print(color.RED+' | Cookie Persistence Validation |') - print(color.RED+' +-----------------------------------+\n') + 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) diff --git a/modules/Referer.py b/modules/Referer.py index 8b8e0de..0c1a86a 100644 --- a/modules/Referer.py +++ b/modules/Referer.py @@ -21,9 +21,9 @@ def Referer(url): Check if the remote web application verifies the Referer before processing the HTTP request. """ - print(color.RED+'\n +--------------------------------------+') - print(color.RED+' | Referer Based Request Validation |') - print(color.RED+' +--------------------------------------+\n') + 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) diff --git a/modules/Tamper.py b/modules/Tamper.py index bd4b57c..2d9ce56 100644 --- a/modules/Tamper.py +++ b/modules/Tamper.py @@ -24,9 +24,9 @@ def Tamper(url, action, req, body, query, para): found and check the content length for related vulnerabilities. ''' - print(color.RED+'\n +---------------------------------------+') - print(color.RED+' | Anti-CSRF Token Tamper Validation |') - print(color.RED+' +---------------------------------------+\n') + verbout(color.RED, '\n +---------------------------------------+') + verbout(color.RED, ' | Anti-CSRF Token Tamper Validation |') + verbout(color.RED, ' +---------------------------------------+\n') # Null char flags (hex) flagx1 = 0x00 flagx2 = 0x00 diff --git a/modules/Token.py b/modules/Token.py index 332520c..5f4c344 100644 --- a/modules/Token.py +++ b/modules/Token.py @@ -23,9 +23,9 @@ def Token(req, headers): This method checks for whether Anti-CSRF Tokens are present in the request. ''' - print(color.RED+'\n +---------------------------+') - print(color.RED+' | Anti-CSRF Token Check |') - print(color.RED+' +---------------------------+\n') + verbout(color.RED, '\n +---------------------------+') + verbout(color.RED, ' | Anti-CSRF Token Check |') + verbout(color.RED, ' +---------------------------+\n') param = '' # Initializing param query = '' found = False From fe0e0dea1d4a02e50ff9b1036db7d5daafea6ac9 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Fri, 28 Dec 2018 13:07:07 +0000 Subject: [PATCH 87/94] Trail to fix errors (0x2) --- core/inputin.py | 2 +- modules/Analysis.py | 8 ++++---- modules/Generator.py | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/inputin.py b/core/inputin.py index 579d6ad..9dd2ff5 100644 --- a/core/inputin.py +++ b/core/inputin.py @@ -31,7 +31,7 @@ def inputin(): print(R+'Site seems to be down...') quit() try: - print(O+'Testing '+color.GREY+web+color.END+' endpoint status...') + 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: diff --git a/modules/Analysis.py b/modules/Analysis.py index 144bcdb..b8376a4 100644 --- a/modules/Analysis.py +++ b/modules/Analysis.py @@ -38,10 +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+tokenx1) - verbout(color.GREEN, ' [+] Shannon Entropy: %s' % (calcEntropy(tokenx1))) - verbout(C, 'Second Token: '+color.ORANGE+tokenx2) - verbout(color.GREEN, ' [+] Shannon Entropy: %s' % (calcEntropy(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)+'%') diff --git a/modules/Generator.py b/modules/Generator.py index d02e380..9cb3b8c 100644 --- a/modules/Generator.py +++ b/modules/Generator.py @@ -13,6 +13,7 @@ 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 From 4c7a48e93aaddd324b46fd883515ecdb46fc668a Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 29 Dec 2018 06:57:15 +0000 Subject: [PATCH 88/94] Added small code refactors to code --- core/forms.py | 62 +++++++++++++++++++++++++-------------------------- core/main.py | 14 ++++++------ 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/core/forms.py b/core/forms.py index b3a7646..516ec08 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/main.py b/core/main.py index cdb660e..b2e24d1 100644 --- a/core/main.py +++ b/core/main.py @@ -36,8 +36,8 @@ from core.request import Get, Post from core.verbout import verbout from core.prettify import formPrettify -from core.forms import form10, form20 from core.banner import banner, banabout +from core.forms import testFormx1, testFormx2 from core.logger import ErrorLogger, GetLogger from core.logger import VulnLogger, NovulLogger @@ -68,8 +68,8 @@ def Engine(): # lets begin it! banner() # Print the banner banabout() # The second banner web, fld = inputin() # Take the input - form1 = form10() # Get the form 1 ready - form2 = form20() # Get the form 2 ready + 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 @@ -155,7 +155,7 @@ def Engine(): # lets begin it! NovulLogger(url, 'Anti-CSRF token is not a string encoded value.') # Go for token parameter tamper checks. if (query and token): - Tamper(url, action, result, r2.text, query, 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 @@ -166,7 +166,7 @@ def Engine(): # lets begin it! 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 and not token: + if (POST_BASED) and ((not query) or (txor)): try: if m['name']: PostBased(url, r1.text, r2.text, r3.text, m['action'], result, genpoc, m.prettify(), m['name']) @@ -239,7 +239,7 @@ def Engine(): # lets begin it! NovulLogger(url, 'Anti-CSRF token is not a string encoded value.') # Go for token parameter tamper checks. if (query and token): - Tamper(url, action, result, r2.text, query, 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 @@ -250,7 +250,7 @@ def Engine(): # lets begin it! 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 and not token: + 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']) From db587e9c02be152cf93c1a0a0e101adc6c7b3d03 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 29 Dec 2018 06:57:49 +0000 Subject: [PATCH 89/94] Improved entropy logic checks --- modules/Entropy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/Entropy.py b/modules/Entropy.py index 11ac2ce..1e4418d 100644 --- a/modules/Entropy.py +++ b/modules/Entropy.py @@ -61,7 +61,7 @@ def Entropy(req, url, headers, form, m_action, m_name=''): # 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: %s' % (value)) + 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...') @@ -81,7 +81,7 @@ def Entropy(req, url, headers, form, m_action, m_name=''): 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+' NOT VULNERABLE '+color.END+color.GREEN+' to CSRF Attacks...') + 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 @@ -107,7 +107,7 @@ def Entropy(req, url, headers, form, 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): """ From 02c2a8e7cf7dd4d1927292386f5e17caac263b75 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 29 Dec 2018 06:58:04 +0000 Subject: [PATCH 90/94] Added improved origin checks --- modules/Origin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/Origin.py b/modules/Origin.py index 6292e14..b814599 100644 --- a/modules/Origin.py +++ b/modules/Origin.py @@ -60,13 +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.RED+'Origin Validation Not Present'+color.END+'!') verbout(R,'Heuristics reveal endpoint might be '+color.BY+' VULNERABLE '+color.END+' to Origin Based CSRFs...') print(color.CYAN+ ' [+] Possible CSRF Vulnerability Detected : '+color.GREY+url+'!') - print(color.ORANGE+' [!] Possible Vulnerability Type: '+color.BY+' No Origin Based Request Validation '+color.END) + 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 From 23315b0fe9701fe4bcf0c59c6fc5f6ccfead8f1a Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 29 Dec 2018 06:58:38 +0000 Subject: [PATCH 91/94] Improvised Tamper.py to provide better feedback --- modules/Tamper.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/modules/Tamper.py b/modules/Tamper.py index 2d9ce56..f02df6f 100644 --- a/modules/Tamper.py +++ b/modules/Tamper.py @@ -39,9 +39,10 @@ def Tamper(url, action, req, body, query, para): # 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. @@ -65,13 +66,14 @@ def Tamper(url, action, req, body, query, para): # # 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!') + verbout(color.GREEN,' [+] Token tamper from request causes a 50x Internal Error!') NovulLogger(url, 'Anti-CSRF Token tamper by index replacement does not return valid response.') - 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 str(resp.status_code).startswith('3')) 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: '+str(req)) - # [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) @@ -89,7 +91,8 @@ def Tamper(url, action, req, body, query, para): if str(resp.status_code).startswith('50'): verbout(color.RED,' [+] Token tamper from request causes a 50x Internal Error!') NovulLogger(url, 'Anti-CSRF Token tamper by index removal does not return valid response.') - 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 str(resp.status_code).startswith('3')) 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: '+str(req)) @@ -98,7 +101,7 @@ def Tamper(url, action, req, body, query, para): verbout(GR, 'Tampering Token by '+color.GREY+'Token removal'+color.END+'...') # Removing the anti-csrf token from request del req[query] - verbout(color.GREEN, ' [+] Removed token parameter from request!') + verbout(color.GREY, ' [+] Removed token parameter from request!') # Lets build up the request... resp = Post(url, action, req) @@ -110,19 +113,22 @@ def Tamper(url, action, req, body, query, para): if str(resp.status_code).startswith('50'): verbout(color.RED,' [+] Token removal from request causes a 50x Internal Error!') NovulLogger(url, 'Anti-CSRF Token on removal does not return valid response.') - 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 str(resp.status_code).startswith('3')) or (len(body) == len(resp.text)): + verbout(color.RED,' [-] Anti-CSRF Token removal from request returns valid response!') flagx3 = 0x01 VulnLogger(url, 'Anti-CSRF Token on removal returns valid response.', '[i] POST Query: '+str(req)) # If any of the forgeries worked... if (flagx1 or flagx2 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) + 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.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) + 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 From 27123b55749a00110a372d1185c72b8dd5ae19b0 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 29 Dec 2018 16:16:36 +0000 Subject: [PATCH 92/94] Added parameters for token checks --- files/paramlist.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/files/paramlist.py b/files/paramlist.py index 5f8b9f5..0f6ef2d 100644 --- a/files/paramlist.py +++ b/files/paramlist.py @@ -18,6 +18,7 @@ # These are a list of known common tokens parameters 'CSRFName', # OWASP CSRF_Guard 'CSRFToken', # OWASP CSRF_Guard + 'csrf_token', # PHP NoCSRF Class 'anticsrf', # AntiCsrfParam.java '__RequestVerificationToken', # ASP.NET TokenParam 'VerificationToken', # AntiCSRFParam.java @@ -26,6 +27,8 @@ 'authenticity_token', # Ruby on Rails 'csrf_param', # Ruby on Rails 'TransientKey', # VanillaForums Param + 'csrf', # PHP CSRFProtect + 'AntiCSURF', # Anti CSURF (PHP) 'YII_CSRF_TOKEN', # http://www.yiiframework.com/ 'yii_anticsrf' # http://www.yiiframework.com/ '[_token]', # Symfony 2.x @@ -40,7 +43,6 @@ # # TODO: Add more similar csrf token parameters 'token', - 'csrf', 'authenticity', 'auth_token', 'auth', @@ -91,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', + ) From 2b880db07aaca877b7fa4474c2a07547c07161dd Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 29 Dec 2018 16:17:03 +0000 Subject: [PATCH 93/94] Added more precise detection systems --- modules/Analysis.py | 4 ++-- modules/Tamper.py | 36 ++++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/modules/Analysis.py b/modules/Analysis.py index b8376a4..ed11dce 100644 --- a/modules/Analysis.py +++ b/modules/Analysis.py @@ -38,9 +38,9 @@ def Analysis(): for tokenx1, tokenx2 in itertools.combinations(REQUEST_TOKENS, 2): try: verbout(GR, 'Analysing 2 Anti-CSRF Tokens from gathered requests...') - verbout(color.CYAN, 'First Token: '+color.BLUE+tokenx1) + verbout(color.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.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) diff --git a/modules/Tamper.py b/modules/Tamper.py index f02df6f..82f8104 100644 --- a/modules/Tamper.py +++ b/modules/Tamper.py @@ -15,6 +15,7 @@ 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 @@ -65,13 +66,14 @@ 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.GREEN,' [+] Token tamper from request causes a 50x Internal Error!') - NovulLogger(url, 'Anti-CSRF Token tamper by index replacement does not return valid response.') - if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) or (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: '+str(req)) + 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 character # at a specific position and test the response body. @@ -88,13 +90,14 @@ 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!') - NovulLogger(url, 'Anti-CSRF Token tamper by index removal does not return valid response.') - if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) or (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: '+str(req)) + 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. @@ -110,13 +113,14 @@ 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 removal from request causes a 50x Internal Error!') - NovulLogger(url, 'Anti-CSRF Token on removal does not return valid response.') - if (str(resp.status_code).startswith('2') and str(resp.status_code).startswith('3')) or (len(body) == len(resp.text)): - verbout(color.RED,' [-] Anti-CSRF Token removal from request returns valid response!') + 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 on removal returns valid response.', '[i] POST Query: '+str(req)) + 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 or flagx2 or flagx3) == 0x01: From 3c0d1753746c9a14165022c879b027db43099418 Mon Sep 17 00:00:00 2001 From: 0xInfection Date: Sat, 29 Dec 2018 16:17:17 +0000 Subject: [PATCH 94/94] Other necessary changes to code infrastructure --- core/forms.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/forms.py b/core/forms.py index 516ec08..2e79db9 100644 --- a/core/forms.py +++ b/core/forms.py @@ -11,7 +11,7 @@ def testFormx1(): # an example xsrfprobe-test-form to make sure the stuff works properly ;) - test_form_0x01 = """ + test_form_0x01 = """
@@ -25,13 +25,13 @@ def testFormx1(): # an example xsrfprobe-test-form to make sure the stuff works
  • Request new password
  • -
    """ +
    """ return test_form_0x01 def testFormx2(): # an example of a xsrfprobe-test-form (used drupal) - test_form_0x02 = """ + test_form_0x02 = """
    @@ -45,6 +45,6 @@ def testFormx2(): # an example of a xsrfprobe-test-form (used drupal)
  • Request new password
  • -
    """ +
    """ return test_form_0x02