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
Add exploit for Node.js HTTP Pipelining DoS #2548
Conversation
| The vulnerability is caused by a lack of backpressure on pipelined | ||
| requests, causing unbounded memory allocation for each request. | ||
| }, | ||
| 'Author' => [ 'titanous' ], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add 'Marek Majkowski', the discoverer here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
A good improvement would be detecting the backpressure, and reporting that the target is not affected. |
|
Yikes. successfully verified with the steps: Yep memory soars and cpu locks at 100%, pretty bad. Testing with: https: |
|
@FiloSottile so send a test request while the pipelined req is being sent? Sounds like a good idea to me, a #check method would be nice too |
| 'License' => MSF_LICENSE, | ||
| 'References' => | ||
| [ | ||
| [ 'URL', 'http://blog.nodejs.org/2013/10/18/node-v0-10-21-stable/' ], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I would suggest putting the issue URL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
Umh, yeah, something like that. Or what do we see from the client side when they apply backpressure? I have to look at that fix commit. |
|
The backpressure is applied by pausing reads from the client socket after reaching a high-water mark for the stream buffer. This causes TCP flow control to kick in, which will slow down the client sending. I can't think of a good way to detect this reliably, but suggestions are welcome. |
| register_options( | ||
| [ | ||
| Opt::RPORT(80), | ||
| OptBool.new('SSL', [true, 'Use SSL', false]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SSL should already be registered by Exploit::Remote::TCP
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. Updated.
|
Taking a look at fingerprinting this. |
|
Here's a check for < 0.10.17, which will at least give you some idea of what you're hitting: I moved the HTTP request creation to a helper so it could be used it in |
|
Any testing/checking you could do as well would be much appreciated :) |
|
@jvennix-r7 Thanks, I've added the check and confirmed that it works. |
|
@titanous +1 for a great first contribution :) |
|
Processing! |
| end | ||
|
|
||
| def http_request(method='GET') | ||
| host = datastore['RHOST'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a new method
def host
host = datastore['RHOST']
host += ":" + datastore['RPORT'].to_s if datastore['RPORT'] != 80
return host
endSo host can be referenced later from the run method too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
|
@titanous do you mind to share a description of your testing target and the RLIMIT you've used to trigger it.... I guess I'm sucking at node.js... but basically I'm running this (weird) sample http server with a vulnerable node.js installation: No luck triggering the overflow condition on the module :? Guess I'm doing something wrong.... xD guidance is welcome :) Thanks for awesome contrib! |
| sock.put(http_request("GEM")) | ||
| begin | ||
| response = sock.get_once | ||
| status = Exploit::CheckCode::Vulnerable if response =~ /HTTP/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like Exploit::CheckCode::Appears fits better here, since as discussed with @jvennix-r7 there are other reasons to get this answer, even on a non vulnerable server. (Example: A web server (no node.js) returning an HTTP "bad method" response)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
|
@jvazquez-r7 I bumped the default RLIMIT to 100000. I ran it against your test script and the memory usage jumped from 5MB to over 125MB and stayed there. A crash would be caused by sending enough requests to trigger an OOM condition (or on OS X fill the hard drive with swap). |
|
Hi @titanous , thanks for keep working on it! :) ooo grgr didn't mean overflow condition.... my head.... I mean trigger the DoS condition in the module: rescue ::Errno::ECONNRESET, ::Errno::EPIPE, ::Timeout::Error
print_status("DoS successful. #{host} not responding.")When running: Even when it's stressing memory: The DoS condition in the module isn't triggered so I never see the "success" message |
|
@jvazquez-r7 Yeah, that message will only show up if the process crashes due to OOM while the module is being run. I couldn't come up with another way of testing, as the DoS is caused by unbounded memory allocation. |
|
oka, yeah, I'm going just to add some print_status to clarify and landing :) thanks for contributing! |
I was bored, so this is my first (ridiculously trivial) Metasploit module. Please be gentle.