-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcsrf-scan.py
More file actions
106 lines (92 loc) · 4.12 KB
/
csrf-scan.py
File metadata and controls
106 lines (92 loc) · 4.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import sys, re, urllib.request as req, urllib.parse as parse
from urllib.error import HTTPError
TOKEN_KEYWORDS = ['csrf', 'token', '_token', 'authenticity_token',
'__requestverification', 'random', 'nonce']
UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
HELP_MSG = ("python csrf-scan.py -u http://target.com/profile "
"-d email=example@a.com -c PHPSESSID=xxx --no-referer")
def color_print(c, s):
palette = {'r': 31, 'g': 32, 'y': 33}
print(f"\033[1;{palette[c]}m{s}\033[0m")
def fetch(url, cookie=None, data=None, headers=None):
hd = {'User-Agent': UA}
if headers: hd.update(headers)
if cookie: hd['Cookie'] = cookie
rq = req.Request(url, data=data and parse.urlencode(data).encode() or None, headers=hd)
try:
with req.urlopen(rq) as rp:
return rp.status, rp.read(), rp.headers
except HTTPError as e:
return e.code, e.read(), e.headers
def parse_forms(html):
forms = []
for m in re.finditer(r'<form[^>]*>', html, re.I):
action = re.search(r'action=[\'"](.*?)[\'"]', m.group(0), re.I)
method = re.search(r'method=[\'"](.*?)[\'"]', m.group(0), re.I)
forms.append({'action': action.group(1) if action else '',
'method': method.group(1).upper() if method else 'GET',
'inputs': []})
for inp in re.finditer(r'<input[^>]*>', html, re.I):
name = re.search(r'name=[\'"](.*?)[\'"]', inp.group(0), re.I)
value = re.search(r'value=[\'"](.*?)[\'"]', inp.group(0), re.I)
if name:
forms[-1]['inputs'].append({name.group(1): value.group(1) if value else ''})
return forms
def find_token(inputs):
for item in inputs:
for k in item:
if any(t in k.lower() for t in TOKEN_KEYWORDS):
return k
return None
def parse_args():
args = {'url': None, 'data': None, 'cookie': None, 'no_referer': False}
if len(sys.argv) == 1 or '-h' in sys.argv or '--help' in sys.argv:
print(HELP_MSG)
sys.exit(0)
i = 1
while i < len(sys.argv):
a = sys.argv[i]
if a == '-u' and i + 1 < len(sys.argv):
args['url'] = sys.argv[i + 1]; i += 2
elif a == '-d' and i + 1 < len(sys.argv):
args['data'] = sys.argv[i + 1]; i += 2
elif a == '-c' and i + 1 < len(sys.argv):
args['cookie'] = sys.argv[i + 1]; i += 2
elif a == '--no-referer':
args['no_referer'] = True; i += 1
else:
i += 1
if not args['url']:
color_print('r', '[-] 必须提供 -u 参数'); sys.exit(1)
return args
def main():
args = parse_args()
color_print('y', '[*] 获取原始响应...')
normal_data = dict(parse.parse_qsl(args['data'])) if args['data'] else {}
status0, body0, hdr0 = fetch(args['url'], args['cookie'], normal_data)
len0 = len(body0)
forms = parse_forms(body0.decode('utf-8', 'ignore'))
token_name = forms and find_token(forms[0]['inputs']) or None
color_print('g' if token_name else 'r',
f'[+] 发现 CSRF-Token 参数: {token_name}' if token_name else '[-] 未找到 CSRF-Token')
color_print('y', '[*] 发送「不带 token」请求...')
if token_name and token_name in normal_data:
del normal_data[token_name]
status1, body1, _ = fetch(args['url'], args['cookie'], normal_data)
len1 = len(body1)
if args['no_referer']:
color_print('y', '[*] 发送「不带 Referer」请求...')
status2, body2, _ = fetch(args['url'], args['cookie'], normal_data,
headers={'Referer': ''})
len2 = len(body2)
else:
status2, len2 = status1, len1
ratio = abs(len1 - len0) / (len0 + 1e-6)
if status1 == status0 and ratio < 0.05:
color_print('r', '\n[!] 可能存在 CSRF 漏洞(不带 token 返回一致)')
else:
color_print('g', '\n[+] 不带 token 时返回差异较大,相对安全')
if args['no_referer'] and status2 == status0 and abs(len2 - len0) / (len0 + 1e-6) < 0.05:
color_print('r', '[!] 同时删除 Referer 后仍成功,风险更高')
if __name__ == '__main__':
main()