From b5c5c219269a08901d55f303ca73d04f082b24c4 Mon Sep 17 00:00:00 2001 From: Vasco Franco Date: Sat, 13 Nov 2021 23:53:18 +0000 Subject: [PATCH 1/4] Fixes "origin reflected" check Previously there was a missing call to `headers = requester(url, scheme, header_dict, origin)` in the "origin reflected" check. This meant that the code was not using the intended origin (`origin = root + '://' + 'example.com'`). Intead, the check incorreclty used the headers from the first request (with `origin = scheme + '://' + root`). This commit fixes this problem by making the missing request. --- core/tests.py | 144 +++++++++++++++++++++++++------------------------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/core/tests.py b/core/tests.py index e8de404..be042ea 100644 --- a/core/tests.py +++ b/core/tests.py @@ -25,85 +25,87 @@ def passive_tests(url, headers): def active_tests(url, root, scheme, header_dict, delay): origin = scheme + '://' + root headers = requester(url, scheme, header_dict, origin) - if headers: - origin = root + '://' + 'example.com' - acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) - if acao_header and acao_header == (origin): - info = details['origin reflected'] - info['acao header'] = acao_header - info['acac header'] = acac_header - return {url : info} - elif not acao_header: - return - time.sleep(delay) + acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) + if acao_header is None: + return + + origin = root + '://' + 'example.com' + headers = requester(url, scheme, header_dict, origin) + acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) + if acao_header and acao_header == (origin): + info = details['origin reflected'] + info['acao header'] = acao_header + info['acac header'] = acac_header + return {url : info} + time.sleep(delay) - origin = scheme + '://' + root + '.example.com' - headers = requester(url, scheme, header_dict, origin) - acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) - if acao_header and acao_header == (origin): - info = details['post-domain wildcard'] - info['acao header'] = acao_header - info['acac header'] = acac_header - return {url : info} - time.sleep(delay) + origin = scheme + '://' + root + '.example.com' + headers = requester(url, scheme, header_dict, origin) + acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) + if acao_header and acao_header == (origin): + info = details['post-domain wildcard'] + info['acao header'] = acao_header + info['acac header'] = acac_header + return {url : info} + time.sleep(delay) - origin = scheme + '://d3v' + root - headers = requester(url, scheme, header_dict, origin) - acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) - if acao_header and acao_header == (origin): - info = details['pre-domain wildcard'] - info['acao header'] = acao_header - info['acac header'] = acac_header - return {url : info} - time.sleep(delay) + origin = scheme + '://d3v' + root + headers = requester(url, scheme, header_dict, origin) + acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) + if acao_header and acao_header == (origin): + info = details['pre-domain wildcard'] + info['acao header'] = acao_header + info['acac header'] = acac_header + return {url : info} + time.sleep(delay) - origin = 'null' - headers = requester(url, '', header_dict, origin) - acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) - if acao_header and acao_header == 'null': - info = details['null origin allowed'] - info['acao header'] = acao_header - info['acac header'] = acac_header - return {url : info} - time.sleep(delay) + origin = 'null' + headers = requester(url, '', header_dict, origin) + acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) + if acao_header and acao_header == 'null': + info = details['null origin allowed'] + info['acao header'] = acao_header + info['acac header'] = acac_header + return {url : info} + time.sleep(delay) - origin = scheme + '://' + root + '_.example.com' + origin = scheme + '://' + root + '_.example.com' + headers = requester(url, scheme, header_dict, origin) + acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) + if acao_header and acao_header == origin: + info = details['unrecognized underscore'] + info['acao header'] = acao_header + info['acac header'] = acac_header + return {url : info} + time.sleep(delay) + + origin = scheme + '://' + root + '%60.example.com' + headers = requester(url, scheme, header_dict, origin) + acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) + if acao_header and '`.example.com' in acao_header: + info = details['broken parser'] + info['acao header'] = acao_header + info['acac header'] = acac_header + return {url : info} + time.sleep(delay) + + if root.count('.') > 1: + origin = scheme + '://' + root.replace('.', 'x', 1) headers = requester(url, scheme, header_dict, origin) acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) if acao_header and acao_header == origin: - info = details['unrecognized underscore'] + info = details['unescaped regex'] info['acao header'] = acao_header info['acac header'] = acac_header return {url : info} time.sleep(delay) - - origin = scheme + '://' + root + '%60.example.com' - headers = requester(url, scheme, header_dict, origin) - acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) - if acao_header and '`.example.com' in acao_header: - info = details['broken parser'] - info['acao header'] = acao_header - info['acac header'] = acac_header - return {url : info} - time.sleep(delay) - - if root.count('.') > 1: - origin = scheme + '://' + root.replace('.', 'x', 1) - headers = requester(url, scheme, header_dict, origin) - acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) - if acao_header and acao_header == origin: - info = details['unescaped regex'] - info['acao header'] = acao_header - info['acac header'] = acac_header - return {url : info} - time.sleep(delay) - origin = 'http://' + root - headers = requester(url, 'http', header_dict, origin) - acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) - if acao_header and acao_header.startswith('http://'): - info = details['http origin allowed'] - info['acao header'] = acao_header - info['acac header'] = acac_header - return {url : info} - else: - return passive_tests(url, headers) + origin = 'http://' + root + headers = requester(url, 'http', header_dict, origin) + acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) + if acao_header and acao_header.startswith('http://'): + info = details['http origin allowed'] + info['acao header'] = acao_header + info['acac header'] = acac_header + return {url : info} + else: + return passive_tests(url, headers) From 85e1fe8b68e24f9b80d6ad7f87a72945ee2ccf5e Mon Sep 17 00:00:00 2001 From: Vasco Franco Date: Sun, 14 Nov 2021 00:02:28 +0000 Subject: [PATCH 2/4] Fix bug in extractHeaders and simplify its logic `extractHeaders` had an incorrect regex that crashed Corsy with the msg: "sre_constants.error: nothing to repeat at position 1". This was caused by `^?`, which is not a valid regex, and can be reproduced with the README example: `python3 corsy.py -u https://example.com --headers "User-Agent: GoogleBot\nCookie: SESSION=Hacked"`. Instead of using the regex this commit simplifies the logic by using a `split` to split lines and header_name/value pairs. --- core/utils.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/core/utils.py b/core/utils.py index ee433c8..0a5b713 100644 --- a/core/utils.py +++ b/core/utils.py @@ -64,17 +64,13 @@ def prompt(default=None): return tmpfile.read().strip() -def extractHeaders(headers): - headers = headers.replace('\\n', '\n') +def extractHeaders(headers: str): sorted_headers = {} - matches = re.findall(r'^?(.*?):\s(.*?)[\n$]', headers) - for match in matches: - header = match[0] - value = match[1] - try: - if value[-1] == ',': - value = value[:-1] - sorted_headers[header] = value - except IndexError: - pass - return sorted_headers + for header in headers.split('\\n'): + name, value = header.split(":", 1) + name = name.strip() + value = value.strip() + if len(value) >= 1 and value[-1] == ',': + value = value[:-1] + sorted_headers[name] = value + return sorted_headers \ No newline at end of file From bd97d2c7526255073bbeaddb5293bad90b3a92be Mon Sep 17 00:00:00 2001 From: Vasco Franco Date: Sun, 14 Nov 2021 00:10:22 +0000 Subject: [PATCH 3/4] Clarify variable names in requester --- core/requester.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/requester.py b/core/requester.py index a3b485e..5679a14 100644 --- a/core/requester.py +++ b/core/requester.py @@ -10,10 +10,11 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def requester(url, scheme, headers, origin): headers['Origin'] = origin try: - response = requests.get(url, headers=headers, verify=False).headers - for key, value in response.items(): + response = requests.get(url, headers=headers, verify=False) + headers = response.headers + for key, value in headers.items(): if key.lower() == 'access-control-allow-origin': - return response + return headers except requests.exceptions.RequestException as e: if 'Failed to establish a new connection' in str(e): print ('%s %s is unreachable' % (bad, url)) From 9f37125c04942e2d18ea8b2d7da581e04a1b1982 Mon Sep 17 00:00:00 2001 From: Vasco Franco Date: Sun, 14 Nov 2021 00:15:29 +0000 Subject: [PATCH 4/4] Fix "root" where "scheme" was intended --- core/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tests.py b/core/tests.py index be042ea..e65c102 100644 --- a/core/tests.py +++ b/core/tests.py @@ -29,7 +29,7 @@ def active_tests(url, root, scheme, header_dict, delay): if acao_header is None: return - origin = root + '://' + 'example.com' + origin = scheme + '://' + 'example.com' headers = requester(url, scheme, header_dict, origin) acao_header, acac_header = headers.get('access-control-allow-origin', None), headers.get('access-control-allow-credentials', None) if acao_header and acao_header == (origin):