diff --git a/ghost/core/core/server/services/custom-redirects/validation.js b/ghost/core/core/server/services/custom-redirects/validation.js index aaba74b0f7..1107a01c42 100644 --- a/ghost/core/core/server/services/custom-redirects/validation.js +++ b/ghost/core/core/server/services/custom-redirects/validation.js @@ -1,6 +1,7 @@ const _ = require('lodash'); const tpl = require('@tryghost/tpl'); const errors = require('@tryghost/errors'); +const {isSafePattern} = require('redos-detector'); const messages = { redirectsWrongFormat: 'Incorrect redirects file format.', @@ -33,18 +34,35 @@ const validate = (redirects) => { if (!redirect.from || !redirect.to) { throw new errors.ValidationError({ message: tpl(messages.redirectsWrongFormat), - context: redirect, help: tpl(messages.redirectsHelp) }); } + // Ensure valid regex try { - // each 'from' property should be a valid RegExp string new RegExp(redirect.from); } catch (error) { throw new errors.ValidationError({ message: tpl(messages.invalidRedirectsFromRegex), - context: redirect, + errorDetails: { + redirect, + invalid: true + }, + help: tpl(messages.redirectsHelp) + }); + } + + // Ensure safe regex + const analysis = isSafePattern(redirect.from); + + if (analysis.safe === false) { + throw new errors.ValidationError({ + message: tpl(messages.invalidRedirectsFromRegex), + errorDetails: { + redirect, + unsafe: true, + reason: analysis.error + }, help: tpl(messages.redirectsHelp) }); } diff --git a/ghost/core/package.json b/ghost/core/package.json index b3040354f9..b724bf19f4 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -219,6 +219,7 @@ "node-jose": "2.2.0", "path-match": "1.2.4", "probe-image-size": "7.2.3", + "redos-detector": "5.1.0", "rss": "1.2.2", "sanitize-html": "2.13.0", "semver": "7.6.2", diff --git a/ghost/core/test/unit/server/services/custom-redirects/validation.test.js b/ghost/core/test/unit/server/services/custom-redirects/validation.test.js index a3a6f35b7a..a0e61861b9 100644 --- a/ghost/core/test/unit/server/services/custom-redirects/validation.test.js +++ b/ghost/core/test/unit/server/services/custom-redirects/validation.test.js @@ -39,6 +39,26 @@ describe('UNIT: custom redirects validation', function () { should.fail('should have thrown'); } catch (err) { err.message.should.equal('Incorrect RegEx in redirects file.'); + err.errorDetails.redirect.should.equal(config[0]); + err.errorDetails.invalid.should.be.true(); + } + }); + + it('throws for an invalid redirects config having unsafe RegExp in from field', function () { + const config = [{ + permanent: true, + from: '^\/episodes\/([a-z0-9-]+)+\/$', // Unsafe due to the surplus + at the end causing infinite backtracking + to: '/' + }]; + + try { + validate(config); + should.fail('should have thrown'); + } catch (err) { + err.message.should.equal('Incorrect RegEx in redirects file.'); + err.errorDetails.redirect.should.equal(config[0]); + err.errorDetails.unsafe.should.be.true(); + err.errorDetails.reason.should.equal('hitMaxBacktracks'); } }); diff --git a/yarn.lock b/yarn.lock index 6dbe1d4f36..c46e9870f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27331,6 +27331,13 @@ redis-parser@^3.0.0: dependencies: redis-errors "^1.0.0" +redos-detector@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/redos-detector/-/redos-detector-5.1.0.tgz#67660c896a48490e80b35557f876a529680f0f8d" + integrity sha512-08en/ij0//HwKZdKlelRZGQKmQhmKMQPVJPD+1THfYm64mZhLPOG0NBa47+DrOF53DyaRsCFyD7JHJaYITnt9g== + dependencies: + regjsparser "0.10.0" + reflect-metadata@0.1.14: version "0.1.14" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859" @@ -27452,6 +27459,13 @@ regjsgen@^0.2.0: resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" integrity sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g== +regjsparser@0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.10.0.tgz#b1ed26051736b436f22fdec1c8f72635f9f44892" + integrity sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA== + dependencies: + jsesc "~0.5.0" + regjsparser@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"