Security context
Low· 5.0GHSA-qw6h-vgh9-j6wx CVE-2024-43796CWE-79Published Sep 10, 2024

express vulnerable to XSS via response.redirect()

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

0 → fixed in 4.20.05.0.0-alpha.1 → fixed in 5.0.0

Details

### Impact In express <4.20.0, passing untrusted user input - even after sanitizing it - to `response.redirect()` may execute untrusted code ### Patches this issue is patched in express 4.20.0 ### Workarounds users are encouraged to upgrade to the patched version of express, but otherwise can workaround this issue by making sure any untrusted inputs are safe, ideally by validating them against an explicit allowlist ### Details successful exploitation of this vector requires the following: 1. The attacker MUST control the input to response.redirect() 1. express MUST NOT redirect before the template appears 1. the browser MUST NOT complete redirection before: 1. the user MUST click on the link in the template

The fix

fix: don't render redirect values in anchor href

Chris de Almeida· Sep 9, 2024, 10:16 PM+22454271f69b5
lib/response.js+1 1
@@ -969,7 +969,7 @@ res.redirect = function redirect(url) {
html: function(){
var u = escapeHtml(address);
- body = '<p>' + statuses.message[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'
+ body = '<p>' + statuses.message[status] + '. Redirecting to ' + u + '</p>'
},
default: function(){
test/res.redirect.js+21 3
@@ -106,7 +106,7 @@ describe('res', function(){
.set('Accept', 'text/html')
.expect('Content-Type', /html/)
.expect('Location', 'http://google.com')
- .expect(302, '<p>Found. Redirecting to <a href="http://google.com">http://google.com</a></p>', done)
+ .expect(302, '<p>Found. Redirecting to http://google.com</p>', done)
})
it('should escape the url', function(done){
@@ -122,9 +122,27 @@ describe('res', function(){
.set('Accept', 'text/html')
.expect('Content-Type', /html/)
.expect('Location', '%3Cla\'me%3E')
- .expect(302, '<p>Found. Redirecting to <a href="%3Cla&#39;me%3E">%3Cla&#39;me%3E</a></p>', done)
+ .expect(302, '<p>Found. Redirecting to %3Cla&#39;me%3E</p>', done)
})
+ it('should not render evil javascript links in anchor href (prevent XSS)', function(done){
+ var app = express();
+ var xss = 'javascript:eval(document.body.innerHTML=`<p>XSS</p>`);';
+ var encodedXss = 'javascript:eval(document.body.innerHTML=%60%3Cp%3EXSS%3C/p%3E%60);';
+
+ app.use(function(req, res){
+ res.redirect(xss);
+ });
+
+ request(app)
+ .get('/')
+ .set('Host', 'http://example.com')
+ .set('Accept', 'text/html')
+ .expect('Content-Type', /html/)
+ .expect('Location', encodedXss)
+ .expect(302, '<p>Found. Redirecting to ' + encodedXss +'</p>', done);
+ });
+
it('should include the redirect type', function(done){
var app = express();
@@ -137,7 +155,7 @@ describe('res', function(){
.set('Accept', 'text/html')
.expect('Content-Type', /html/)
.expect('Location', 'http://google.com')
- .expect(301, '<p>Moved Permanently. Redirecting to <a href="http://google.com">http://google.com</a></p>', done);
+ .expect(301, '<p>Moved Permanently. Redirecting to http://google.com</p>', done);
})
})

References