Please consider citing our paper if you do scentific research (Click me).
18 |
19 |
20 | *Latex version:*
21 |
22 | ```tex
23 | @inproceedings{chen-cors,
24 | author = {Jianjun Chen and Jian Jiang and Haixin Duan and Tao Wan and Shuo Chen and Vern Paxson and Min Yang},
25 | title = {We Still Don{\textquoteright}t Have Secure Cross-Domain Requests: an Empirical Study of {CORS}},
26 | booktitle = {27th {USENIX} Security Symposium ({USENIX} Security 18)},
27 | year = {2018},
28 | isbn = {978-1-939133-04-5},
29 | address = {Baltimore, MD},
30 | pages = {1079--1093},
31 | url = {https://www.usenix.org/conference/usenixsecurity18/presentation/chen-jianjun},
32 | publisher = {{USENIX} Association},
33 | month = aug,
34 | }
35 | ```
36 |
37 | *Word version:*
38 |
39 | Jianjun Chen, Jian Jiang, Haixin Duan, Tao Wan, Shuo Chen, Vern Paxson, and Min Yang. "We Still Don’t Have Secure Cross-Domain Requests: an Empirical Study of CORS." In 27th USENIX Security Symposium (USENIX Security 18), pp. 1079-1093. 2018.
40 |
41 |
42 |
43 |
44 | ## Screenshots
45 |
46 | 
47 |
48 | ## Installation
49 |
50 | - Download this tool
51 | ```
52 | git clone https://github.com/chenjj/CORScanner.git
53 | ```
54 |
55 | - Install dependencies
56 | ```
57 | sudo pip install -r requirements.txt
58 | ```
59 | CORScanner depends on the `requests`, `gevent`, `tldextract`, `colorama` and `argparse` python modules.
60 |
61 | ## Python Version:
62 |
63 | * Both Python 2 (**2.7.x**) and Python 3 (**3.7.x**) are supported.
64 |
65 | ## CORScanner as a library
66 |
67 | - Install CORScanner via pip
68 |
69 | ```
70 | sudo pip install corscanner
71 | ```
72 |
73 | or use the short name:
74 |
75 | ```
76 | sudo pip install cors
77 | ```
78 |
79 | - Example code:
80 | ```python
81 | >>> from CORScanner.cors_scan import cors_check
82 | >>> ret = cors_check("https://www.instagram.com", None)
83 | >>> ret
84 | {'url': 'https://www.instagram.com', 'type': 'reflect_origin', 'credentials': 'false', 'origin': 'https://evil.com', 'status_code': 200}
85 | ```
86 |
87 | You can also use CORScanner via the `corscanner` or `cors` command: `cors -vu https://www.instagram.com`
88 |
89 | ## Usage
90 |
91 | Short Form | Long Form | Description
92 | ------------- | ------------- |-------------
93 | -u | --url | URL/domain to check it's CORS policy
94 | -d | --headers | Add headers to the request
95 | -i | --input | URL/domain list file to check their CORS policy
96 | -t | --threads | Number of threads to use for CORS scan
97 | -o | --output | Save the results to json file
98 | -v | --verbose | Enable the verbose mode and display results in realtime
99 | -T | --timeout | Set requests timeout (default 10 sec)
100 | -p | --proxy | Enable proxy (http or socks5)
101 | -h | --help | show the help message and exit
102 |
103 | ### Examples
104 |
105 | * To check CORS misconfigurations of specific domain:
106 |
107 | ``python cors_scan.py -u example.com``
108 |
109 | * To enable more debug info, use -v:
110 |
111 | ``python cors_scan.py -u example.com -v``
112 |
113 | * To save scan results to a JSON file, use -o:
114 |
115 | ``python cors_scan.py -u example.com -o output_filename``
116 |
117 | * To check CORS misconfigurations of specific URL:
118 |
119 | ``python cors_scan.py -u http://example.com/restapi``
120 |
121 | * To check CORS misconfiguration with specific headers:
122 |
123 | ``python cors_scan.py -u example.com -d "Cookie: test"``
124 |
125 | * To check CORS misconfigurations of multiple domains/URLs:
126 |
127 | ``python cors_scan.py -i top_100_domains.txt -t 100``
128 |
129 | * To enable proxy for CORScanner, use -p
130 |
131 | ```python cors_scan.py -u example.com -p http://127.0.0.1:8080```
132 |
133 | To use socks5 proxy, install PySocks with `pip install PySocks`
134 |
135 | ```python cors_scan.py -u example.com -p socks5://127.0.0.1:8080```
136 |
137 | * To list all the basic options and switches use -h switch:
138 |
139 | ```python cors_scan.py -h```
140 |
141 | ## Misconfiguration types
142 | This tool covers the following misconfiguration types:
143 |
144 | Misconfiguration type | Description
145 | ------------------------ | --------------------------
146 | Reflect_any_origin | Blindly reflect the Origin header value in `Access-Control-Allow-Origin headers` in responses, which means any website can read its secrets by sending cross-orign requests.
147 | Prefix_match | `wwww.example.com` trusts `example.com.evil.com`, which is an attacker's domain.
148 | Suffix_match | `wwww.example.com` trusts `evilexample.com`, which could be registered by an attacker.
149 | Not_escape_dot | `wwww.example.com` trusts `wwwaexample.com`, which could be registered by an attacker.
150 | Substring match | `wwww.example.com` trusts `example.co`, which could be registered by an attacker.
151 | Trust_null | `wwww.example.com` trusts `null`, which can be forged by iframe sandbox scripts
152 | HTTPS_trust_HTTP | Risky trust dependency, a MITM attacker may steal HTTPS site secrets
153 | Trust_any_subdomain | Risky trust dependency, a subdomain XSS may steal its secrets
154 | Custom_third_parties | Custom unsafe third parties origins like `github.io`, see more in [origins.json](./origins.json) file. Thanks [@phackt](https://github.com/phackt)!
155 | Special_characters_bypass| Exploiting browsers’ handling of special characters. Most can only work in Safari except `_`, which can also work in Chrome and Firefox. See more in [Advanced CORS Exploitation Techniques](https://www.corben.io/advanced-cors-techniques/). Thanks [@Malayke](https://github.com/Malayke).
156 |
157 | Welcome to contribute more.
158 |
159 | ## Exploitation examples
160 | Here is an example about how to exploit "Reflect_any_origin" misconfiguration on Walmart.com(fixed). Localhost is the malicious website in the video.
161 |
162 | Walmart.com video on Youtube:
163 |
164 | [](http://www.youtube.com/watch?v=3abaevsSHXY)
165 |
166 | Here is the exploitation code:
167 | ```javascript
168 |
188 | ```
189 |
190 | If you have understood how the demo works, you can read Section 5 and Section 6 of the [CORS paper](https://www.jianjunchen.com/publication/an-empirical-study-of-cors/) and know how to exploit other misconfigurations.
191 |
192 | ## License
193 |
194 | CORScanner is licensed under the MIT license. take a look at the [LICENSE](./LICENSE) for more information.
195 |
196 |
197 | ## Credits
198 | This work is inspired by the following excellent researches:
199 |
200 | * James Kettle, “Exploiting CORS misconfigurations for Bitcoins and bounties”, AppSecUSA 2016*
201 | * Evan Johnson, “Misconfigured CORS and why web appsec is not getting easier”, AppSecUSA 2016*
202 | * Von Jens Müller, "CORS misconfigurations on a large scale", [CORStest](https://github.com/RUB-NDS/CORStest)*
203 |
204 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 | import sys
5 |
6 | sys.dont_write_bytecode = True
7 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
8 |
--------------------------------------------------------------------------------
/common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenjj/CORScanner/593043f836a158246fc6a13a89a5a1401cbff0b5/common/__init__.py
--------------------------------------------------------------------------------
/common/common.py:
--------------------------------------------------------------------------------
1 | import linecache
2 |
3 |
4 | def normalize_url(i):
5 | if '://' in i:
6 | return [i]
7 | else:
8 | return ["http://" + i, "https://" + i]
9 |
10 | def parse_headers(headers):
11 | if headers == None:
12 | return None
13 | else:
14 | parsedheaders = {}
15 |
16 | for header in headers:
17 | index = header.find(":")
18 | if index == -1:
19 | return None
20 | parsedheaders[header[0:index].strip()] = header[index+1:].strip()
21 | return parsedheaders
22 |
23 | def read_file(input_file):
24 | lines = linecache.getlines(input_file)
25 | return lines
26 |
27 |
28 | def read_urls(test_url, input_file, queue):
29 | if test_url:
30 | for u in normalize_url(test_url):
31 | queue.put(u)
32 | if input_file:
33 | lines = read_file(input_file)
34 | for i in lines:
35 | for u in normalize_url(i.strip()):
36 | queue.put(u)
37 |
--------------------------------------------------------------------------------
/common/corscheck.py:
--------------------------------------------------------------------------------
1 | import gevent.monkey
2 | gevent.monkey.patch_all()
3 |
4 | import requests, json, os, inspect, tldextract
5 |
6 | from future.utils import iteritems
7 | try:
8 | from urllib.parse import urlparse
9 | except Exception as e:
10 | from urlparse import urlparse
11 |
12 | import urllib3
13 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
14 |
15 | from threading import Thread
16 |
17 | class CORSCheck:
18 | """docstring for CORSCheck"""
19 | url = None
20 | cfg = None
21 | headers = None
22 | timeout = None
23 | result = {}
24 |
25 | def __init__(self, url, cfg):
26 | self.url = url
27 | self.cfg = cfg
28 | self.timeout = cfg["timeout"]
29 | self.all_results = []
30 | if cfg["headers"] != None:
31 | self.headers = cfg["headers"]
32 | self.proxies = {}
33 | if cfg.get("proxy") != None:
34 | self.proxies = {
35 | "http": cfg["proxy"],
36 | "https": cfg["proxy"],
37 | }
38 |
39 | def send_req(self, url, origin):
40 | try:
41 |
42 | headers = {
43 | 'Origin':
44 | origin,
45 | 'Cache-Control':
46 | 'no-cache',
47 | 'User-Agent':
48 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
49 | }
50 | if self.headers != None:
51 | headers.update(self.headers)
52 |
53 | # self-signed cert OK, follow redirections
54 | resp = requests.get(self.url, timeout=self.timeout, headers=headers,
55 | verify=False, allow_redirects=True, proxies=self.proxies)
56 |
57 | # remove cross-domain redirections, which may cause false results
58 | first_domain =tldextract.extract(url).registered_domain
59 | last_domain = tldextract.extract(resp.url).registered_domain
60 |
61 | if(first_domain.lower() != last_domain.lower()):
62 | resp = None
63 |
64 | except Exception as e:
65 | resp = None
66 | return resp
67 |
68 | def get_resp_headers(self, resp):
69 | if resp == None:
70 | return None
71 | resp_headers = dict(
72 | (k.lower(), v) for k, v in iteritems(resp.headers))
73 | return resp_headers
74 |
75 | def check_cors_policy(self, test_module_name,test_origin,test_url):
76 | resp = self.send_req(self.url, test_origin)
77 | resp_headers = self.get_resp_headers(resp)
78 | status_code = resp.status_code if resp is not None else None
79 |
80 | if resp_headers == None:
81 | return None
82 |
83 | parsed = urlparse(str(resp_headers.get("access-control-allow-origin")))
84 | if test_origin != "null":
85 | resp_origin = parsed.scheme + "://" + parsed.netloc.split(':')[0]
86 | else:
87 | resp_origin = str(resp_headers.get("access-control-allow-origin"))
88 |
89 | msg = None
90 |
91 | # test_origin does not have to be case sensitive
92 | if test_origin.lower() == resp_origin.lower():
93 | credentials = "false"
94 |
95 | if resp_headers.get("access-control-allow-credentials") == "true":
96 | credentials = "true"
97 |
98 | # Set the msg
99 | msg = {
100 | "url": test_url,
101 | "type": test_module_name,
102 | "credentials": credentials,
103 | "origin": test_origin,
104 | "status_code" : status_code
105 | }
106 | return msg
107 |
108 | def is_cors_permissive(self,test_module_name,test_origin,test_url):
109 | msg = self.check_cors_policy(test_module_name,test_origin,test_url)
110 |
111 | if msg != None:
112 | self.cfg["logger"].warning(msg)
113 | self.result = msg
114 | self.all_results.append(msg)
115 | return True
116 |
117 | self.cfg["logger"].info("nothing found for {url: %s, origin: %s, type: %s}" % (test_url, test_origin, test_module_name))
118 | return False
119 |
120 | def test_reflect_origin(self):
121 | module_name = inspect.stack()[0][3].replace('test_','');
122 | test_url = self.url
123 | parsed = urlparse(test_url)
124 | test_origin = parsed.scheme + "://" + "evil.com"
125 |
126 | self.cfg["logger"].info(
127 | "Start checking %s for %s" % (module_name,test_url))
128 |
129 | return self.is_cors_permissive(module_name,test_origin,test_url)
130 |
131 | def test_prefix_match(self):
132 | module_name = inspect.stack()[0][3].replace('test_','');
133 | test_url = self.url
134 | parsed = urlparse(test_url)
135 | test_origin = parsed.scheme + "://" + parsed.netloc.split(':')[0] + ".evil.com"
136 |
137 | self.cfg["logger"].info(
138 | "Start checking %s for %s" % (module_name,test_url))
139 |
140 | return self.is_cors_permissive(module_name,test_origin,test_url)
141 |
142 |
143 | def test_suffix_match(self):
144 | module_name = inspect.stack()[0][3].replace('test_','');
145 | test_url = self.url
146 | parsed = urlparse(test_url)
147 | sld = tldextract.extract(test_url.strip()).registered_domain
148 | test_origin = parsed.scheme + "://" + "evil" + sld
149 |
150 | self.cfg["logger"].info(
151 | "Start checking %s for %s" % (module_name,test_url))
152 |
153 | return self.is_cors_permissive(module_name,test_origin,test_url)
154 |
155 |
156 | def test_trust_null(self):
157 | module_name = inspect.stack()[0][3].replace('test_','');
158 | test_url = self.url
159 | test_origin = "null"
160 |
161 | self.cfg["logger"].info(
162 | "Start checking %s for %s" % (module_name,test_url))
163 |
164 | return self.is_cors_permissive(module_name,test_origin,test_url)
165 |
166 |
167 | def test_include_match(self):
168 | module_name = inspect.stack()[0][3].replace('test_','');
169 | test_url = self.url
170 | parsed = urlparse(test_url)
171 | sld = tldextract.extract(test_url.strip()).registered_domain
172 | test_origin = parsed.scheme + "://" + sld[1:]
173 |
174 | self.cfg["logger"].info(
175 | "Start checking %s for %s" % (module_name,test_url))
176 |
177 | return self.is_cors_permissive(module_name,test_origin,test_url)
178 |
179 |
180 | def test_not_escape_dot(self):
181 | module_name = inspect.stack()[0][3].replace('test_','');
182 | test_url = self.url
183 | parsed = urlparse(test_url)
184 | sld = tldextract.extract(test_url.strip()).registered_domain
185 | domain = parsed.netloc.split(':')[0]
186 | test_origin = parsed.scheme + "://" + domain[::-1].replace(
187 | '.', 'a', 1)[::-1]
188 |
189 | self.cfg["logger"].info(
190 | "Start checking %s for %s" % (module_name,test_url))
191 |
192 | return self.is_cors_permissive(module_name,test_origin,test_url)
193 |
194 |
195 | def test_trust_any_subdomain(self):
196 | module_name = inspect.stack()[0][3].replace('test_','');
197 | test_url = self.url
198 | parsed = urlparse(test_url)
199 | test_origin = parsed.scheme + "://" + "evil." + parsed.netloc.split(':')[0]
200 |
201 | self.cfg["logger"].info(
202 | "Start checking %s for %s" % (module_name,test_url))
203 |
204 | return self.is_cors_permissive(module_name,test_origin,test_url)
205 |
206 |
207 | def test_https_trust_http(self):
208 | module_name = inspect.stack()[0][3].replace('test_','');
209 | test_url = self.url
210 | parsed = urlparse(test_url)
211 | if parsed.scheme != "https":
212 | return
213 | test_origin = "http://" + parsed.netloc.split(':')[0]
214 |
215 | self.cfg["logger"].info(
216 | "Start checking %s for %s" % (module_name,test_url))
217 |
218 | return self.is_cors_permissive(module_name,test_origin,test_url)
219 |
220 |
221 | def test_custom_third_parties(self):
222 | module_name = inspect.stack()[0][3].replace('test_','');
223 | test_url = self.url
224 | parsed = urlparse(test_url)
225 | sld = tldextract.extract(test_url.strip()).registered_domain
226 | domain = parsed.netloc.split(':')[0]
227 |
228 | self.cfg["logger"].info(
229 | "Start checking %s for %s" % (module_name,test_url))
230 |
231 | is_cors_perm = False
232 |
233 | # Opening origins file
234 | with open(os.path.join(os.path.dirname(os.path.realpath(__file__)),'..%sorigins.json' % os.sep)) as origins_file:
235 | origins = json.load(origins_file)['origins']
236 |
237 | for test_origin in origins:
238 |
239 | is_cors_perm = self.is_cors_permissive(module_name,test_origin,test_url)
240 | if is_cors_perm: break
241 |
242 | return is_cors_perm
243 |
244 | def test_special_characters_bypass(self):
245 | module_name = inspect.stack()[0][3].replace('test_','');
246 | test_url = self.url
247 | parsed = urlparse(test_url)
248 | special_characters = ['_','-','"','{','}','+','^','%60','!','~','`',';','|','&',"'",'(',')','*',',','$','=','+',"%0b"]
249 |
250 | origins = []
251 |
252 | for char in special_characters:
253 | attempt = parsed.scheme + "://" + parsed.netloc.split(':')[0] + char + ".evil.com"
254 | origins.append(attempt)
255 |
256 | is_cors_perm = False
257 |
258 | self.cfg["logger"].info(
259 | "Start checking %s for %s" % (module_name,test_url))
260 |
261 | for test_origin in origins:
262 | is_cors_perm = self.is_cors_permissive(module_name,test_origin,test_url)
263 | if is_cors_perm: break
264 |
265 | return is_cors_perm
266 |
267 | def check_one_by_one(self):
268 | functions = [
269 | 'test_reflect_origin',
270 | 'test_prefix_match',
271 | 'test_suffix_match',
272 | 'test_trust_null',
273 | 'test_include_match',
274 | 'test_not_escape_dot',
275 | 'test_custom_third_parties',
276 | 'test_special_characters_bypass',
277 | 'test_trust_any_subdomain',
278 | 'test_https_trust_http',
279 | ]
280 |
281 | for fname in functions:
282 | func = getattr(self,fname)
283 | # Stop if we found a exploit case.
284 | if(func()): break
285 |
286 | return self.result
287 |
288 | def check_all_in_parallel(self):
289 | functions = [
290 | 'test_reflect_origin',
291 | 'test_prefix_match',
292 | 'test_suffix_match',
293 | 'test_trust_null',
294 | 'test_include_match',
295 | 'test_not_escape_dot',
296 | 'test_custom_third_parties',
297 | 'test_special_characters_bypass',
298 | 'test_trust_any_subdomain',
299 | 'test_https_trust_http',
300 | ]
301 |
302 | threads = []
303 | for fname in functions:
304 | func = getattr(self,fname)
305 | t = Thread(target=func)
306 | t.start()
307 | threads.append(t)
308 |
309 | for t in threads:
310 | t.join()
311 |
312 | return self.all_results
--------------------------------------------------------------------------------
/common/logger.py:
--------------------------------------------------------------------------------
1 | import time
2 | import json
3 | import sys
4 |
5 |
6 | class Log:
7 | """Class Log for logging CORS misconfiguration message"""
8 | print_level = 0
9 | msg_level = {0: 'DEBUG', 1: 'INFO', 2: 'WARNING', 3: 'ALERT'}
10 | auto_timestamp = 1
11 |
12 | def __init__(self, filename, print_level, auto_timestamp=1):
13 | self.filename = filename
14 | self.print_level = print_level
15 | self.auto_timestamp = auto_timestamp
16 |
17 | def write(self, msg, level=0, auto_timestamp=1):
18 | try:
19 | if level >= self.print_level:
20 | if self.auto_timestamp == 1:
21 | timestamp = time.strftime("%Y-%m-%d %H:%M:%S",
22 | time.localtime())
23 | record = "%s %s %s" % (timestamp, self.msg_level[level],
24 | msg)
25 | sys.stdout.write(record + "\r\n")
26 | else:
27 | sys.stdout.write(msg + "\r\n")
28 | sys.stdout.flush()
29 | except KeyboardInterrupt:
30 | self.close()
31 |
32 | def debug(self, msg):
33 | self.write(msg, 0)
34 |
35 | def info(self, msg):
36 | self.write(msg, 1)
37 |
38 | def warning(self, msg):
39 | record = "Found misconfiguration! " + json.dumps(msg)
40 | self.write("""%s%s%s""" % ('\033[91m', record, '\033[0m'), 2)
41 |
42 | def alert(self, msg):
43 | self.write(msg, 3)
44 |
45 | def close(self):
46 | if self.log:
47 | self.log.close()
48 |
--------------------------------------------------------------------------------
/cors_scan.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import json
4 | import sys
5 | import os
6 | import argparse
7 | import threading
8 |
9 | from common.common import *
10 | from common.logger import Log
11 | from common.corscheck import CORSCheck
12 |
13 | import gevent
14 | from gevent import monkey
15 | monkey.patch_all()
16 | from gevent.pool import Pool
17 | from gevent.queue import Queue
18 | from colorama import init
19 |
20 | # Globals
21 | results = []
22 |
23 | def banner():
24 | print(("""%s
25 | ____ ___ ____ ____ ____ _ _ _ _ _ _____ ____
26 | / ___/ _ \| _ \/ ___| / ___| / \ | \ | | \ | | ____| _ \
27 | | | | | | | |_) \___ \| | / _ \ | \| | \| | _| | |_) |
28 | | |__| |_| | _ < ___) | |___ / ___ \| |\ | |\ | |___| _ <
29 | \____\___/|_| \_\____/ \____/_/ \_\_| \_|_| \_|_____|_| \_\
30 | %s%s
31 | # Coded By Jianjun Chen - whucjj@gmail.com%s
32 | """ % ('\033[91m', '\033[0m', '\033[93m', '\033[0m')))
33 |
34 |
35 | def parser_error(errmsg):
36 | banner()
37 | print(("Usage: python " + sys.argv[0] + " [Options] use -h for help"))
38 | print(("Error: " + errmsg))
39 | sys.exit()
40 |
41 |
42 | def parse_args():
43 | # parse the arguments
44 | parser = argparse.ArgumentParser(
45 | epilog='\tExample: \r\npython ' + sys.argv[0] + " -u google.com")
46 | parser.error = parser_error
47 | parser._optionals.title = "OPTIONS"
48 | parser.add_argument(
49 | '-u', '--url', help="URL/domain to check it's CORS policy")
50 | parser.add_argument(
51 | '-i',
52 | '--input',
53 | help='URL/domain list file to check their CORS policy')
54 | parser.add_argument(
55 | '-t',
56 | '--threads',
57 | help='Number of threads to use for CORS scan',
58 | type=int,
59 | default=50)
60 | parser.add_argument('-o', '--output', help='Save the results to json file')
61 | parser.add_argument(
62 | '-v',
63 | '--verbose',
64 | help='Enable Verbosity and display results in realtime',
65 | action='store_true',
66 | default=False)
67 | parser.add_argument('-d', '--headers', help='Add headers to the request.', default=None, nargs='*')
68 | parser.add_argument(
69 | '-T',
70 | '--timeout',
71 | help='Set requests timeout (default 5 sec)',
72 | type=int,
73 | default=10)
74 | parser.add_argument('-p', '--proxy', help='Enable proxy (http or socks5)')
75 | args = parser.parse_args()
76 | if not (args.url or args.input):
77 | parser.error("No url inputed, please add -u or -i option")
78 | if args.input and not os.path.isfile(args.input):
79 | parser.error("Input file " + args.input + " not exist.")
80 | return args
81 |
82 |
83 | # Synchronize results
84 | c = threading.Condition()
85 |
86 | def scan(cfg):
87 | log = cfg["logger"]
88 | global results
89 |
90 | while not cfg["queue"].empty():
91 | try:
92 | item = cfg["queue"].get(timeout=1.0)
93 | cors_check = CORSCheck(item, cfg)
94 | msg = cors_check.check_one_by_one()
95 |
96 | # Keeping results to be written to file only if needed
97 | if log.filename and msg:
98 | c.acquire()
99 | results.append(msg)
100 | c.release()
101 | except Exception as e:
102 | print(e)
103 | break
104 |
105 | """
106 | CORScanner library API interface for other projects to use. This will check
107 | the CORS policy for a given URL. Example Usage:
108 |
109 | >>> from CORScanner.cors_scan import cors_check
110 | >>> ret = cors_check("https://www.instagram.com", None)
111 | >>> ret
112 | {'url': 'https://www.instagram.com', 'type': 'reflect_origin',...}
113 |
114 | """
115 | def cors_check(url, headers=None):
116 | # 0: 'DEBUG', 1: 'INFO', 2: 'WARNING', 3: 'ALERT', 4: 'disable log'
117 | log = Log(None, print_level=4)
118 | cfg = {"logger": log, "headers": headers, "timeout": 5}
119 |
120 | cors_check = CORSCheck(url, cfg)
121 | #msg = cors_check.check_all_in_parallel()
122 | msg = cors_check.check_one_by_one()
123 | return msg
124 |
125 | def main():
126 | init()
127 | args = parse_args()
128 | #banner()
129 |
130 | queue = Queue()
131 | log_level = 1 if args.verbose else 2 # 1: INFO, 2: WARNING
132 |
133 | log = Log(args.output, log_level)
134 | cfg = {"logger": log, "queue": queue, "headers": parse_headers(args.headers),
135 | "timeout": args.timeout, "proxy": args.proxy}
136 |
137 | read_urls(args.url, args.input, queue)
138 |
139 | sys.stderr.write("Starting CORS scan...(Tips: this may take a while, add -v option to enable debug info)\n")
140 | sys.stderr.flush()
141 | threads = [gevent.spawn(scan, cfg) for i in range(args.threads)]
142 |
143 | try:
144 | gevent.joinall(threads)
145 | except KeyboardInterrupt as e:
146 | pass
147 |
148 | # Writing results file if output file has been set
149 | if log.filename:
150 | with open(log.filename, 'w') as output_file:
151 | output_file.write(json.dumps(results, indent=4))
152 | output_file.close()
153 | sys.stderr.write("Finished CORS scanning...\n")
154 | sys.stderr.flush()
155 |
156 |
157 | if __name__ == '__main__':
158 | main()
159 |
--------------------------------------------------------------------------------
/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenjj/CORScanner/593043f836a158246fc6a13a89a5a1401cbff0b5/images/screenshot.png
--------------------------------------------------------------------------------
/images/walmart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenjj/CORScanner/593043f836a158246fc6a13a89a5a1401cbff0b5/images/walmart.png
--------------------------------------------------------------------------------
/origins.json:
--------------------------------------------------------------------------------
1 | {
2 | "origins":[
3 | "https://whatever.github.io",
4 | "http://jsbin.com",
5 | "https://codepen.io",
6 | "https://jsfiddle.net",
7 | "http://www.webdevout.net",
8 | "https://repl.it"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | gevent
3 | tldextract
4 | argparse
5 | colorama
6 | future
7 | PySocks
8 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from setuptools import setup, find_packages
4 |
5 | setup(
6 | name='cors',
7 | version='1.0.1',
8 | description='Fast CORS misconfiguration vulnerabilities scanner',
9 | long_description=open('README.md').read(),
10 | long_description_content_type='text/markdown',
11 | author='Jianjun Chen',
12 | author_email= 'whucjj@hotmail.com',
13 | url='http://github.com/chenjj/CORScanner',
14 | project_urls={
15 | 'Bug Reports': 'https://github.com/chenjj/CORScanner/issues',
16 | 'Source': 'https://github.com/chenjj/CORScanner/',
17 | },
18 | license='MIT',
19 | packages=find_packages(),
20 | install_requires=['colorama', 'requests', 'argparse', 'gevent', 'tldextract', 'future', 'PySocks'],
21 | include_package_data=True,
22 | zip_safe=False,
23 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers
24 | classifiers=[
25 | 'Development Status :: 4 - Beta',
26 | 'License :: OSI Approved :: MIT License',
27 | 'Natural Language :: English',
28 | 'Operating System :: OS Independent',
29 | 'Programming Language :: Python',
30 | 'Environment :: Console',
31 | 'Topic :: Security',
32 | ],
33 | entry_points={
34 | 'console_scripts': [
35 | 'cors = CORScanner.cors_scan:main',
36 | ],
37 | },
38 | )
39 |
--------------------------------------------------------------------------------
/top_100_domains.txt:
--------------------------------------------------------------------------------
1 | google.com
2 | youtube.com
3 | facebook.com
4 | baidu.com
5 | wikipedia.org
6 | reddit.com
7 | yahoo.com
8 | qq.com
9 | taobao.com
10 | twitter.com
11 | google.co.in
12 | amazon.com
13 | sohu.com
14 | tmall.com
15 | instagram.com
16 | live.com
17 | vk.com
18 | jd.com
19 | sina.com.cn
20 | weibo.com
21 | google.co.jp
22 | yandex.ru
23 | 360.cn
24 | google.co.uk
25 | login.tmall.com
26 | google.ru
27 | google.com.br
28 | pornhub.com
29 | twitch.tv
30 | netflix.com
31 | google.com.hk
32 | linkedin.com
33 | google.de
34 | google.fr
35 | csdn.net
36 | microsoft.com
37 | t.co
38 | bing.com
39 | yahoo.co.jp
40 | office.com
41 | ebay.com
42 | google.it
43 | alipay.com
44 | google.ca
45 | mail.ru
46 | msn.com
47 | xvideos.com
48 | ok.ru
49 | microsoftonline.com
50 | google.es
51 | imgur.com
52 | aliexpress.com
53 | pages.tmall.com
54 | whatsapp.com
55 | google.com.mx
56 | imdb.com
57 | tumblr.com
58 | stackoverflow.com
59 | wordpress.com
60 | wikia.com
61 | github.com
62 | google.com.tw
63 | xhamster.com
64 | deloton.com
65 | hao123.com
66 | amazon.co.jp
67 | livejasmin.com
68 | google.com.tr
69 | blogspot.com
70 | paypal.com
71 | popads.net
72 | google.com.au
73 | apple.com
74 | bongacams.com
75 | googleusercontent.com
76 | tribunnews.com
77 | pinterest.com
78 | xnxx.com
79 | coccoc.com
80 | savefrom.net
81 | youth.cn
82 | google.pl
83 | diply.com
84 | fbcdn.net
85 | providr.com
86 | adobe.com
87 | txxx.com
88 | amazon.de
89 | dropbox.com
90 | detail.tmall.com
91 | thestartmagazine.com
92 | google.co.id
93 | pixnet.net
94 | tianya.cn
95 | quora.com
96 | bbc.co.uk
97 | cnn.com
98 | amazon.co.uk
99 | bbc.com
100 | amazonaws.com
101 |
--------------------------------------------------------------------------------