Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ docs/_build/

# PyBuilder
target/
targets.txt

# Jupyter Notebook
.ipynb_checkpoints
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### 3.1.6
- Added scanning and brute-forcing of multiple targets
- Scanned targets now have a summary output
- Stop scanning if the "skip" parameter is provided

### 3.1.5
- Fix color bug that resulted in DOM XSS vulnerabilities not
being reported on certain systems (Windows, macOS, iOS)
Expand Down
8 changes: 8 additions & 0 deletions core/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ def log_red_line(self, amount=60, level='INFO'):
_switch_to_default_loggers(self)


def log_yellow_summary_line(self, amount=60, level='INFO'):
_switch_to_no_format_loggers(self)
_get_level_and_log(self, f"{yellow}{'=' * amount}{end}", level)
_switch_to_default_loggers(self)


def log_no_format(self, msg='', level='INFO'):
_switch_to_no_format_loggers(self)
_get_level_and_log(self, msg, level)
Expand Down Expand Up @@ -187,6 +193,8 @@ def setup_logger(name='xsstrike'):

# Create logger method to only log a red line
logger.red_line = MethodType(log_red_line, logger)
# Create logger method to only log a yellow line
logger.yellow_summary_line = MethodType(log_yellow_summary_line, logger)
# Create logger method to log without format
logger.no_format = MethodType(log_no_format, logger)
# Create logger method to convert data to json and log with debug level
Expand Down
4 changes: 2 additions & 2 deletions core/requester.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ def requester(url, data, headers, GET, delay, timeout):
logger.warning('WAF is dropping suspicious requests.')
logger.warning('Scanning will continue after 10 minutes.')
time.sleep(600)
except Exception as e:
logger.warning('Unable to connect to the target.')
except Exception:
logger.warning('Unable to connect to the target.\r')
return requests.Response()
2 changes: 1 addition & 1 deletion core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def writer(obj, path):


def reader(path):
with open(path, 'r') as f:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
result = [line.rstrip(
'\n').encode('utf-8').decode('utf-8') for line in f]
return result
Expand Down
7 changes: 3 additions & 4 deletions core/wafDetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ def wafDetector(url, params, headers, GET, delay, timeout):
# Opens the noise injected payload
response = requester(url, params, headers, GET, delay, timeout)
page = response.text
code = str(response.status_code)
code = response.status_code
headers = str(response.headers)
logger.debug('Waf Detector code: {}'.format(code))
logger.debug_json('Waf Detector headers:', response.headers)

if int(code) >= 400:
if code and code >= 400:
bestMatch = [0, None]
for wafName, wafSignature in wafSignatures.items():
score = 0
Expand All @@ -33,7 +32,7 @@ def wafDetector(url, params, headers, GET, delay, timeout):
if re.search(pageSign, page, re.I):
score += 1
if codeSign:
if re.search(codeSign, code, re.I):
if re.search(codeSign, str(code), re.I):
score += 0.5 # increase the overall score by a smaller amount because http codes aren't strong indicators
if headersSign:
if re.search(headersSign, headers, re.I):
Expand Down
47 changes: 30 additions & 17 deletions modes/bruteforcer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
import copy
from threading import Lock
from urllib.parse import urlparse, unquote

from core.colors import good, green, end
Expand All @@ -7,9 +9,10 @@
from core.log import setup_logger

logger = setup_logger(__name__)
lock = Lock()


def bruteforcer(target, paramData, payloadList, encoding, headers, delay, timeout):
def bruteforcer(target, paramData, payloadList, encoding, headers, delay, timeout, threadCount):
GET, POST = (False, True) if paramData else (True, False)
host = urlparse(target).netloc # Extracts host out of the url
logger.debug('Parsed host to bruteforce: {}'.format(host))
Expand All @@ -20,20 +23,30 @@ def bruteforcer(target, paramData, payloadList, encoding, headers, delay, timeou
if not params:
logger.error('No parameters to test.')
quit()
for paramName in params.keys():
progress = 1
paramsCopy = copy.deepcopy(params)
for payload in payloadList:
logger.run('Bruteforcing %s[%s%s%s]%s: %i/%i\r' %
(green, end, paramName, green, end, progress, len(payloadList)))
if encoding:
payload = encoding(unquote(payload))
paramsCopy[paramName] = payload
response = requester(url, paramsCopy, headers,
GET, delay, timeout).text
if encoding:
payload = encoding(payload)
if payload in response:
logger.info('%s %s' % (good, payload))
progress += 1
progress = {'lap': 0}
with ThreadPoolExecutor(max_workers=threadCount) as executor:

for paramName in params.keys():
paramsCopy = copy.deepcopy(params)
for payload in payloadList:
executor.submit(
make_request, url, payload, paramsCopy, headers, GET,
delay, timeout, paramName, encoding, progress, len(payloadList))
logger.no_format('')


def make_request(url, payload, paramsCopy, headers, GET, delay, timeout, paramName, encoding, progress, total):
logger.run('Bruteforcing %s[%s%s%s]%s: %i/%i \r' %
(green, end, paramName, green, end, progress['lap'], total))
if encoding:
payload = encoding(unquote(payload))
paramsCopy[paramName] = payload
response = requester(url, paramsCopy, headers,
GET, delay, timeout).text
if encoding:
payload = encoding(payload)
if payload in response:
li = response.split(payload)
if li[0][-1] != '"' and li[1][0] != '"':
logger.good(payload)
progress['lap'] += 1
67 changes: 48 additions & 19 deletions modes/scan.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
import copy
import re
from threading import Lock
from urllib.parse import urlparse, quote, unquote

from core.checker import checker
Expand All @@ -16,9 +18,10 @@
from core.log import setup_logger

logger = setup_logger(__name__)
lock = Lock()


def scan(target, paramData, encoding, headers, delay, timeout, skipDOM, skip):
def scan(target, paramData, encoding, headers, delay, timeout, skipDOM, skip, threadCount):
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 not target.startswith('http'):
Expand Down Expand Up @@ -86,35 +89,61 @@ def scan(target, paramData, encoding, headers, delay, timeout, skipDOM, skip):
logger.error('No vectors were crafted.')
continue
logger.info('Payloads generated: %i' % total)
progress = 0
for confidence, vects in vectors.items():
for vect in vects:
if core.config.globalVariables['path']:
vect = vect.replace('/', '%2F')
loggerVector = vect
progress += 1
logger.run('Progress: %i/%i\r' % (progress, total))
if not GET:
vect = unquote(vect)
efficiencies = checker(
url, paramsCopy, headers, GET, delay, vect, positions, timeout, encoding)
if not efficiencies:
for i in range(len(occurences)):
efficiencies.append(0)
bestEfficiency = max(efficiencies)
progress = {'lap': 0}
running_futures = []
with ThreadPoolExecutor(max_workers=threadCount) as executor:

# Add all comb
for confidence, vects in vectors.items():
for vect in vects:
running_futures.append(executor.submit(checky, target, url, paramsCopy, headers, GET, delay,
vect, positions, timeout, encoding, occurences, confidence, progress, total))

for future in as_completed(running_futures):

# retrieve the result
bestEfficiency, target, loggerVector, confidence = future.result()

if bestEfficiency == 100 or (vect[0] == '\\' and bestEfficiency >= 95):
lock.acquire()
logger.red_line()
logger.good('Payload: %s' % loggerVector)
logger.info('Efficiency: %i' % bestEfficiency)
logger.info('Confidence: %i' % confidence)
if not skip:
choice = input(
'%s Would you like to continue scanning? [y/N] ' % que).lower()
if choice != 'y':
quit()
lock.release()
if skip or choice != 'y':
[f.cancel() for f in running_futures]
return target, loggerVector
elif bestEfficiency > minEfficiency:
lock.acquire()
logger.red_line()
logger.good('Payload: %s' % loggerVector)
logger.info('Efficiency: %i' % bestEfficiency)
logger.info('Confidence: %i' % confidence)
lock.release()

logger.no_format('')


def checky(
target, url, paramsCopy, headers, GET, delay,
vect, positions, timeout, encoding, occurences, confidence, progress, total):
if core.config.globalVariables['path']:
vect = vect.replace('/', '%2F')
loggerVector = vect
progress['lap'] += 1
lock.acquire()
logger.run(f'Progress: {progress["lap"]}/{total} \r')
lock.release()
if not GET:
vect = unquote(vect)
efficiencies = checker(
url, paramsCopy, headers, GET, delay, vect, positions, timeout, encoding)
if not efficiencies:
for i in range(len(occurences)):
efficiencies.append(0)
bestEfficiency = max(efficiencies)
return bestEfficiency, target, loggerVector, confidence
33 changes: 27 additions & 6 deletions xsstrike.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# Just a fancy ass banner
print('''%s
\tXSStrike %sv3.1.5
\tXSStrike %sv3.1.6
%s''' % (red, white, end))

try:
Expand Down Expand Up @@ -39,6 +39,7 @@
# 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('-ul', '--url_list', help='list of urls', dest='targets')
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',
Expand Down Expand Up @@ -85,6 +86,7 @@
# 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
targets = args.targets
path = args.path
jsonData = args.jsonData
paramData = args.paramData
Expand Down Expand Up @@ -142,6 +144,12 @@
headers['Content-type'] = 'application/json'
paramData = converter(paramData)

target_list = []
if targets:
target_list = list(filter(None, reader(targets)))
elif target:
target_list.append(target)

if args_file:
if args_file == 'default':
payloadList = core.config.payloads
Expand All @@ -161,17 +169,30 @@
updater()
quit() # quitting because files have been changed

if not target and not args_seeds: # if the user hasn't supplied a url
if not target_list and not args_seeds: # if the user hasn't supplied a url
logger.no_format('\n' + parser.format_help().lower())
quit()

if fuzz:
singleFuzz(target, paramData, encoding, headers, delay, timeout)
elif not recursive and not args_seeds:
if args_file:
bruteforcer(target, paramData, payloadList, encoding, headers, delay, timeout)
else:
scan(target, paramData, encoding, headers, delay, timeout, skipDOM, skip)
results = []
for i, target in enumerate(target_list):
logger.red_line()
logger.info(f'Target: {target} ({i + 1}/{len(target_list)})')
if args_file:
bruteforcer(target, paramData, payloadList, encoding, headers, delay, timeout, threadCount)
else:
result = scan(target, paramData, encoding, headers, delay, timeout, skipDOM, skip, threadCount)
results.append(result) if result else 'The target is not vulnerable!'

if results:
logger.yellow_summary_line()
logger.run('SUMMARY')
logger.info(f'Total {len(target_list)} target{"s"[:len(target_list)^1]}')
logger.info(f'Vulnerable {len(results)} target{"s"[:len(results)^1]}')
for i, result in enumerate(results):
logger.good(f'Pwned {result[0]} ({result[1]})')
else:
if target:
seedList.append(target)
Expand Down