diff --git a/core/colors.py b/core/colors.py new file mode 100644 index 0000000..21ec78b --- /dev/null +++ b/core/colors.py @@ -0,0 +1,21 @@ +import sys + +colors = True # Output should be colored +machine = sys.platform # Detecting the os of current system +if machine.lower().startswith(('os', 'win', 'darwin', 'ios')): + colors = False # Colors shouldn't be displayed in mac & windows +if not colors: + end = red = white = green = yellow = grey = run = bad = good = info = que = '' +else: + grey = '\033[37m' + white = '\033[97m' + green = '\033[92m' + red = '\033[91m' + yellow = '\033[93m' + end = '\033[0m' + back = '\033[7;91m' + info = '\033[93m[!]\033[0m' + que = '\033[94m[?]\033[0m' + bad = '\033[91m[-]\033[0m' + good = '\033[92m[+]\033[0m' + run = '\033[97m[~]\033[0m' diff --git a/core/requester.py b/core/requester.py new file mode 100644 index 0000000..5f7fa00 --- /dev/null +++ b/core/requester.py @@ -0,0 +1,16 @@ +import requests + +headers = { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip', + 'DNT': '1', + 'Connection': 'close', +} + +def requester(url, scheme, origin): + headers['Origin'] = scheme + origin + response = requests.get(url, headers).headers + if 'Access-Control-Allow-Origin' in response: + return response['Access-Control-Allow-Origin'] diff --git a/core/tests.py b/core/tests.py new file mode 100644 index 0000000..a16ba46 --- /dev/null +++ b/core/tests.py @@ -0,0 +1,51 @@ +import time + +from core.utils import host +from core.requester import requester + +def passive_tests(url, acao_header): + root = host(url) + if root: + if root != host(acao_header): + return 'Third party allowed' + elif url.startswith('http://'): + return 'HTTP origin allowed' + else: + return False + elif acao_header == '*': + return 'Wildcard value' + else: + return 'Invalid value' + +def active_tests(url, root, scheme, delay): + acao_header = requester(url, scheme, 'example.com') + if acao_header: + if acao_header == (scheme + 'example.com'): + return 'Origin reflected' + time.sleep(delay) + acao_header = requester(url, scheme, root + '.example.com') + if acao_header: + if acao_header == (scheme + root + '.example.com'): + return 'Post domain wildcard' + time.sleep(delay) + acao_header = requester(url, scheme, 'd3v' + root) + if acao_header: + if acao_header == (scheme + 'd3v' + root): + return 'Pre domain wildcard' + time.sleep(delay) + acao_header = requester(url, '', 'null') + if acao_header: + if acao_header == 'null': + return 'Null origin allowed' + time.sleep(delay) + acao_header = requester(url, scheme, root + '%60.example.com') + if acao_header: + if '`.example.com' in acao_header: + return 'Broken parser' + time.sleep(delay) + acao_header = requester(url, 'http', root) + if acao_header: + if acao_header.startswith('http://'): + return 'HTTP origin allowed' + else: + return passive_tests(url, acao_header) diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 0000000..b61cfe6 --- /dev/null +++ b/core/utils.py @@ -0,0 +1,17 @@ +import tld +import json + +def load_file(path): + with open(path, 'r') as f: + result = [line.rstrip('\n').encode('utf-8').decode('utf-8') for line in f] + return '\n'.join(result) + +def host(string): + if string and '*' not in string: + try: + return tld.get_fld(string, fix_protocol=True) + except: + return False + +def load_json(file): + return json.loads(load_file('./db/details.json')) diff --git a/corsy.py b/corsy.py new file mode 100644 index 0000000..4ff5217 --- /dev/null +++ b/corsy.py @@ -0,0 +1,52 @@ +import argparse + +from core.utils import load_json, host +from core.tests import active_tests +from core.colors import white, green, info, bad, good, grey, end + +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + +print(''' + %sCORSY %s{%sv0.1-beta%s}%s +''' % (green, white, grey, white, end)) + +parser = argparse.ArgumentParser() +parser.add_argument('-u', help='target url', dest='url') +parser.add_argument('-d', help='request delay', dest='delay', type=float, default=0) +args = parser.parse_args() + +target_url = args.url +delay = args.delay + +def cors(target, delay, scheme=False): + url = target + if not target.startswith(('http://', 'https://')): + url = scheme + '://' + url + root = host(url) + parsed = urlparse(url) + netloc = parsed.netloc + scheme = parsed.scheme + url = scheme + '://' + netloc + active = active_tests(url, root, scheme, delay) + return active + +details = load_json('./db/details.json') + +if target_url: + if target_url.startswith(('http://', 'https://')): + result = cors(target_url, delay) + if result: + print('%s Misconfiguration found!' % good) + print('%s Title: %s' % (info, result)) + print('%s Description: %s' % (info, details[result.lower()]['Description'])) + print('%s Severity: %s' % (info, details[result.lower()]['Severity'])) + print('%s Exploitation: %s' % (info, details[result.lower()]['Exploitation'])) + else: + print('%s No misconfiguration found.' % bad) + else: + print('%s Please use https://example.com not example.com' % bad) +else: + print('\n' + parser.format_help().lower()) diff --git a/db/details.json b/db/details.json new file mode 100644 index 0000000..5f67cc7 --- /dev/null +++ b/db/details.json @@ -0,0 +1,48 @@ +{ + "wildcard value" : { + "Description" : "This host allows requests made from any origin. However, browsers will block all requests to this host by default.", + "Severity" : "Low", + "Exploitation" : "Not possible" + }, + "third party allowed" : { + "Description" : "This host has whitelisted a third party host for cross origin requests.", + "Severity" : "Medium", + "Exploitation" : "If the whitelisted host is a code hosting platform such as codepen.io or has an XSS vulnerbaility, it can be used to exploit this misconfiguration." + + }, + "origin reflected" : { + "Description" : "This host allows any origin to make requests to it.", + "Severity" : "High", + "Exploitation" : "Make requests from any domain you control." + }, + "invalid value" : { + "Description" : "Header's value is invalid, this CORS implementation doesn't work at all.", + "Severity" : "Low", + "Exploitation" : "Not possible" + }, + "post domain wildcard" : { + "Description" : "The origin verification is flawed, it allows requests from a host that has this host as as preffix.", + "Severity" : "High", + "Exploitation" : "Make requests from target.com.attacker.com" + }, + "pre domain wildcard" : { + "Description" : "The origin verification is flawed, it allows requests from a host that has this host as as suffix.", + "Severity" : "High", + "Exploitation" : "Make requests from attacker-target.com" + }, + "null origin allowed" : { + "Description" : "This host allows requests from 'null' origin.", + "Severity" : "High", + "Exploitation" : "Make requests from a sanboxed iframe." + }, + "http origin allowed" : { + "Description" : "This host allows sharing resources over an unencrypted (HTTP) connection.", + "Severity" : "Low", + "Exploitation" : "Sniff requests made over the unencrypted channel." + }, + "broken parser" : { + "Description" : "The origin verification is flawed and can be bypassed using a backtick (`).", + "Severity" : "High", + "Exploitation" : "Set the 'Origin' header to %60.example.com" + } +} \ No newline at end of file