diff --git a/.travis.yml b/.travis.yml index fb17ba25..0935b0db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,3 +13,4 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics script: - python xsstrike.py -u "https://public-firing-range.appspot.com/reflected/index.html" --crawl + - echo "No" > input.txt && python xsstrike.py -u https://public-firing-range.appspot.com/reflected/parameter/body?q=query < input.txt diff --git a/core/checker.py b/core/checker.py index 7ffe2d12..ac95ff96 100644 --- a/core/checker.py +++ b/core/checker.py @@ -1,11 +1,10 @@ import re import copy from fuzzywuzzy import fuzz -from core.encoders import base64 from core.config import xsschecker from core.requester import requester from core.utils import replacer, fillHoles -from urllib.parse import quote_plus, unquote +from urllib.parse import unquote def checker(url, params, headers, GET, delay, payload, positions, timeout, encoding): checkString = 'st4r7s' + payload + '3nd' @@ -40,4 +39,4 @@ def checker(url, params, headers, GET, delay, payload, positions, timeout, encod else: efficiencies.append(0) num += 1 - return list(filter(None, efficiencies)) \ No newline at end of file + return list(filter(None, efficiencies)) diff --git a/core/filterChecker.py b/core/filterChecker.py index 468d72bd..849b3f19 100644 --- a/core/filterChecker.py +++ b/core/filterChecker.py @@ -1,7 +1,4 @@ -from core.utils import replacer from core.checker import checker -from core.config import xsschecker -from core.requester import requester def filterChecker(url, params, headers, GET, delay, occurences, timeout, encoding): positions = {} @@ -37,4 +34,4 @@ def filterChecker(url, params, headers, GET, delay, occurences, timeout, encodin sortedEfficiencies[i][environment] = efficiency for efficiency, occurence in zip(sortedEfficiencies.values(), occurences.values()): occurence['score'] = efficiency - return occurences \ No newline at end of file + return occurences diff --git a/core/fuzzer.py b/core/fuzzer.py index 352ea68b..9f8ec3df 100644 --- a/core/fuzzer.py +++ b/core/fuzzer.py @@ -1,12 +1,11 @@ import copy -import requests from time import sleep from random import randint from core.utils import replacer from core.requester import requester from core.config import fuzzes, xsschecker -from urllib.parse import quote_plus, unquote -from core.colors import end, red, white, green, yellow, run, bad, good, info, que +from urllib.parse import unquote +from core.colors import end, red, green, yellow, bad, good, info def counter(string): special = '\'"=/:*&)(}{][><' diff --git a/core/htmlParser.py b/core/htmlParser.py index 5bf02797..94ffb824 100644 --- a/core/htmlParser.py +++ b/core/htmlParser.py @@ -1,6 +1,5 @@ import re from core.config import badTags -from core.encoders import base64 from core.config import xsschecker def htmlParser(response, encoding): diff --git a/xsstrike.py b/xsstrike.py index be7dc8f0..1d5c81d5 100644 --- a/xsstrike.py +++ b/xsstrike.py @@ -2,120 +2,119 @@ from __future__ import print_function -from core.colors import end, red, white, green, yellow, run, bad, good, info, que - -# Just a fancy ass banner -print('''%s -\tXSStrike %sv3.0.3 -%s''' % (red, white, end)) - -try: - from urllib.parse import quote_plus, unquote, urlparse -except ImportError: # throws error in python2 - print ('%s XSStrike isn\'t compatible with python2.\n Use python > 3.4 to run XSStrike.' % bad) - quit() - -# Let's import whatever we need -import re -import os -import sys -import copy +# Let's import whatever we need from standard lib import argparse -import requests import concurrent.futures +import copy +import re +# ... and from core lib import core.config -from core.dom import dom from core.arjun import arjun -from core.photon import photon -from core.prompt import prompt -from core.fuzzer import fuzzer -from core.updater import updater from core.checker import checker +from core.colors import bad, end, good, green, info, que, red, run, white +from core.config import blindPayload, minEfficiency, xsschecker +from core.dom import dom from core.encoders import base64 +from core.filterChecker import filterChecker +from core.fuzzer import fuzzer from core.generator import generator -from core.requester import requester from core.htmlParser import htmlParser +from core.photon import photon +from core.prompt import prompt +from core.requester import requester +from core.updater import updater +from core.utils import extractHeaders, getParams, getUrl, verboseOutput from core.wafDetector import wafDetector -from core.filterChecker import filterChecker -from core.config import xsschecker, minEfficiency, blindPayload -from core.utils import getUrl, getParams, flattenParams, extractHeaders, verboseOutput -# Processing command line arguments +# Just a fancy ass banner +print('''%s +\tXSStrike %sv3.0.3 +%s''' % (red, white, end)) + +try: + from urllib.parse import quote_plus, unquote, urlparse +except ImportError: # throws error in python2 + print('%s XSStrike isn\'t compatible with python2.\n Use python > 3.4 to run XSStrike.' % bad) + quit() + + +# Processing command line arguments, where dest var names will be mapped to local vars with the same name parser = argparse.ArgumentParser() parser.add_argument('-u', '--url', help='url', dest='target') -parser.add_argument('--data', help='post data', dest='data') +parser.add_argument('--data', help='post data', dest='paramData') parser.add_argument('-e', '--encode', help='encode payloads', dest='encode') parser.add_argument('--fuzzer', help='fuzzer', dest='fuzz', action='store_true') parser.add_argument('--update', help='update', dest='update', action='store_true') -parser.add_argument('--timeout', help='timeout', dest='timeout', type=int) +parser.add_argument('--timeout', help='timeout', dest='timeout', type=int, default=core.config.timeout) parser.add_argument('--proxy', help='use prox(y|ies)', dest='proxy', action='store_true') parser.add_argument('--params', help='find params', dest='find', action='store_true') parser.add_argument('--crawl', help='crawl', dest='recursive', action='store_true') -parser.add_argument('-f', '--file', help='load payloads from a file', dest='file') -parser.add_argument('-l', '--level', help='level of crawling', dest='level', type=int) -parser.add_argument('--headers', help='add headers', dest='headers', action='store_true') -parser.add_argument('-t', '--threads', help='number of threads', dest='threads', type=int) -parser.add_argument('-d', '--delay', help='delay between requests', dest='delay', type=int) +parser.add_argument('-f', '--file', help='load payloads from a file', dest='args_file') +parser.add_argument('-l', '--level', help='level of crawling', dest='level', type=int, default=2) +parser.add_argument('--headers', help='add headers', dest='add_headers', action='store_true') +parser.add_argument('-t', '--threads', help='number of threads', dest='threadCount', type=int, default=core.config.threadCount) +parser.add_argument('-d', '--delay', help='delay between requests', dest='delay', type=int, default=core.config.delay) parser.add_argument('--skip', help='don\'t ask to continue', dest='skip', action='store_true') parser.add_argument('--skip-dom', help='skip dom checking', dest='skipDOM', action='store_true') parser.add_argument('-v', '--vectors', help='verbose output', dest='verbose', action='store_true') parser.add_argument('--blind', help='inject blind XSS payload while crawling', dest='blindXSS', action='store_true') args = parser.parse_args() -if args.headers: +if args.add_headers: headers = extractHeaders(prompt()) else: from core.config import headers -find = args.find -fuzz = args.fuzz -encode = args.encode +# Pull all parameter values of dict from argparse namespace into local variables of name == key +# The following works, but the static checkers are too static ;-) locals().update(vars(args)) target = args.target -paramData = args.data -verbose = args.verbose +paramData = args.paramData +encode = args.encode +fuzz = args.fuzz +update = args.update +timeout = args.timeout +proxy = args.proxy +find = args.find +recursive = args.recursive +args_file = args.args_file +level = args.level +add_headers = args.add_headers +threadCount = args.threadCount +delay = args.delay +skip = args.skip skipDOM = args.skipDOM -level = args.level or 2 +verbose = args.verbose blindXSS = args.blindXSS -delay = args.delay or core.config.delay -timeout = args.timeout or core.config.timeout -threadCount = args.threads or core.config.threadCount -if args.file: - if args.file == 'default': +if args_file: + if args_file == 'default': payloadList = core.config.payloads else: payloadList = [] - with open(args.file, 'r') as f: + with open(args_file, 'r') as f: for line in f: payloadList.append(line.strip('\n').encode('utf-8').decode('utf-8')) payloadList = list(filter(None, payloadList)) -encoding = False -if encode: - if encode == 'base64': - encoding = base64 +encoding = base64 if encode and encode == 'base64' else False -if not args.proxy: +if not proxy: core.config.proxies = {} -if args.update: # if the user has supplied --update argument +if update: # if the user has supplied --update argument updater() - quit() # quitting because files have been changed + quit() # quitting because files have been changed -if not target: # if the user hasn't supplied a url +if not target: # if the user hasn't supplied a url print('\n' + parser.format_help().lower()) quit() + def singleTarget(target, paramData, verbose, encoding): - if paramData: - GET, POST = False, True - else: - GET, POST = True, False + GET, POST = (False, True) if paramData else (True, False) # If the user hasn't supplied the root url with http(s), we will handle it - if target.startswith('http'): - target = target - else: + if not target.startswith('http'): try: response = requester('https://' + target, {}, headers, GET, delay, timeout) target = 'https://' + target @@ -123,34 +122,34 @@ def singleTarget(target, paramData, verbose, encoding): target = 'http://' + target response = requester(target, {}, headers, GET, delay, timeout).text if not skipDOM: - print ('%s Checking for DOM vulnerabilities' % run) + print('%s Checking for DOM vulnerabilities' % run) highlighted = dom(response) if highlighted: - print ('%s Potentially vulnerable objects found' % good) - print (red + ('-' * 60) + end) + print('%s Potentially vulnerable objects found' % good) + print(red + ('-' * 60) + end) for line in highlighted: - print (line) - print (red + ('-' * 60) + end) - host = urlparse(target).netloc # Extracts host out of the url + print(line) + print(red + ('-' * 60) + end) + host = urlparse(target).netloc # Extracts host out of the url verboseOutput(host, 'host', verbose) url = getUrl(target, GET) verboseOutput(url, 'url', verbose) params = getParams(target, paramData, GET) verboseOutput(params, 'params', verbose) - if args.find: + if find: params = arjun(url, GET, headers, delay, timeout) if not params: - print ('%s No parameters to test.' % bad) + print('%s No parameters to test.' % bad) quit() - WAF = wafDetector(url, {list(params.keys())[0] : xsschecker}, headers, GET, delay, timeout) + WAF = wafDetector(url, {list(params.keys())[0]: xsschecker}, headers, GET, delay, timeout) if WAF: - print ('%s WAF detected: %s%s%s' % (bad, green, WAF, end)) + print('%s WAF detected: %s%s%s' % (bad, green, WAF, end)) else: - print ('%s WAF Status: %sOffline%s' % (good, green, end)) + print('%s WAF Status: %sOffline%s' % (good, green, end)) if fuzz: for paramName in params.keys(): - print ('%s Fuzzing parameter: %s' % (info, paramName)) + print('%s Fuzzing parameter: %s' % (info, paramName)) paramsCopy = copy.deepcopy(params) paramsCopy[paramName] = xsschecker fuzzer(url, paramsCopy, headers, GET, delay, timeout, WAF, encoding) @@ -158,7 +157,7 @@ def singleTarget(target, paramData, verbose, encoding): for paramName in params.keys(): paramsCopy = copy.deepcopy(params) - print ('%s Testing parameter: %s' % (info, paramName)) + print('%s Testing parameter: %s' % (info, paramName)) if encoding: paramsCopy[paramName] = encoding(xsschecker) else: @@ -170,28 +169,28 @@ def singleTarget(target, paramData, verbose, encoding): positions = parsedResponse[1] verboseOutput(positions, 'positions', verbose) if not occurences: - print ('%s No reflection found' % bad) + print('%s No reflection found' % bad) continue else: - print ('%s Reflections found: %s' % (info, len(occurences))) - print ('%s Analysing reflections' % run) + print('%s Reflections found: %s' % (info, len(occurences))) + print('%s Analysing reflections' % run) efficiencies = filterChecker(url, paramsCopy, headers, GET, delay, occurences, timeout, encoding) verboseOutput(efficiencies, 'efficiencies', verbose) - print ('%s Generating payloads' % run) + print('%s Generating payloads' % run) vectors = generator(occurences, response.text) verboseOutput(vectors, 'vectors', verbose) total = 0 for v in vectors.values(): total += len(v) if total == 0: - print ('%s No vectors were crafted' % bad) + print('%s No vectors were crafted' % bad) continue - print ('%s Payloads generated: %i' % (info, total)) + print('%s Payloads generated: %i' % (info, total)) progress = 0 for confidence, vects in vectors.items(): for vect in vects: progress += 1 - print ('%s Payloads tried [%i/%i]' % (run, progress, total), end='\r') + print('%s Payloads tried [%i/%i]' % (run, progress, total), end='\r') if not GET: vect = unquote(vect) efficiencies = checker(url, paramsCopy, headers, GET, delay, vect, positions, timeout, encoding) @@ -200,31 +199,32 @@ def singleTarget(target, paramData, verbose, encoding): efficiencies.append(0) bestEfficiency = max(efficiencies) if bestEfficiency == 100 or (vect[0] == '\\' and bestEfficiency >= 95): - print (('%s-%s' % (red, end)) * 60) - print ('%s Payload: %s' % (good, vect)) - print ('%s Efficiency: %i' % (info, bestEfficiency)) - print ('%s Confidence: %i' % (info, confidence)) - if not args.skip: + print(('%s-%s' % (red, end)) * 60) + print('%s Payload: %s' % (good, vect)) + print('%s Efficiency: %i' % (info, bestEfficiency)) + print('%s Confidence: %i' % (info, confidence)) + if not skip: choice = input('%s Would you like to continue scanning? [y/N] ' % que).lower() if choice != 'y': quit() elif bestEfficiency > minEfficiency: - print (('%s-%s' % (red, end)) * 60) - print ('%s Payload: %s' % (good, vect)) - print ('%s Efficiency: %i' % (info, bestEfficiency)) - print ('%s Confidence: %i' % (info, confidence)) + print(('%s-%s' % (red, end)) * 60) + print('%s Payload: %s' % (good, vect)) + print('%s Efficiency: %i' % (info, bestEfficiency)) + print('%s Confidence: %i' % (info, confidence)) + def multiTargets(scheme, host, main_url, form, domURL, verbose, blindXSS, blindPayload, headers, delay, timeout): - signatures = set() + signatures = set() # TODO(NN) unused if domURL and not skipDOM: response = requester(domURL, {}, headers, True, delay, timeout).text highlighted = dom(response) if highlighted: - print ('%s Potentially vulnerable objects found at %s' % (good, domURL)) - print (red + ('-' * 60) + end) + print('%s Potentially vulnerable objects found at %s' % (good, domURL)) + print(red + ('-' * 60) + end) for line in highlighted: - print (line) - print (red + ('-' * 60) + end) + print(line) + print(red + ('-' * 60) + end) if form: for each in form.values(): url = each['action'] @@ -238,10 +238,7 @@ def multiTargets(scheme, host, main_url, form, domURL, verbose, blindXSS, blindP elif re.match(r'\w', url[0]): url = scheme + '://' + host + '/' + url method = each['method'] - if method == 'get': - GET = True - else: - GET = False + GET = True if method == 'get' else False inputs = each['inputs'] paramData = {} for one in inputs: @@ -259,8 +256,8 @@ def multiTargets(scheme, host, main_url, form, domURL, verbose, blindXSS, blindP for confidence, vects in vectors.items(): try: payload = list(vects)[0] - print ('%s Vulnerable webpage: %s%s%s' % (good, green, url, end)) - print ('%s Vector for %s%s%s: %s' % (good, green, paramName, end, payload)) + print('%s Vulnerable webpage: %s%s%s' % (good, green, url, end)) + print('%s Vector for %s%s%s: %s' % (good, green, paramName, end, payload)) break except IndexError: pass @@ -270,17 +267,14 @@ def multiTargets(scheme, host, main_url, form, domURL, verbose, blindXSS, blindP def bruteforcer(target, paramData, payloadList, verbose, encoding): - if paramData: - GET, POST = False, True - else: - GET, POST = True, False - host = urlparse(target).netloc # Extracts host out of the url + GET, POST = (False, True) if paramData else (True, False) + host = urlparse(target).netloc # Extracts host out of the url verboseOutput(host, 'host', verbose) url = getUrl(target, GET) verboseOutput(url, 'url', verbose) params = getParams(target, paramData, GET) if not params: - print ('%s No parameters to test.' % bad) + print('%s No parameters to test.' % bad) quit() verboseOutput(params, 'params', verbose) for paramName in params.keys(): @@ -295,17 +289,17 @@ def bruteforcer(target, paramData, payloadList, verbose, encoding): if encoding: payload = encoding(payload) if payload in response: - print ('%s %s' % (good, payload)) + print('%s %s' % (good, payload)) progress += 1 print ('') -if not args.recursive: - if args.file: +if not recursive: + if args_file: bruteforcer(target, paramData, payloadList, verbose, encoding) else: singleTarget(target, paramData, verbose, encoding) else: - print ('%s Crawling the target' % run) + print('%s Crawling the target' % run) scheme = urlparse(target).scheme verboseOutput(scheme, 'scheme', verbose) host = urlparse(target).netloc @@ -325,4 +319,4 @@ def bruteforcer(target, paramData, payloadList, verbose, encoding): for i, _ in enumerate(concurrent.futures.as_completed(futures)): if i + 1 == len(forms) or (i + 1) % threadCount == 0: print('%s Progress: %i/%i' % (info, i + 1, len(forms)), end='\r') - print ('') + print()