Skip to content

Commit 5ab4ae3

Browse files
davisjamstyfle
authored andcommitted
security: replace vulnerable regex with parser (#1223)
* security: replace vulnerable regex with parser Problem: link regex was vulnerable Solution: dedicated parser Fixes: #1222
1 parent fa15a37 commit 5ab4ae3

File tree

1 file changed

+62
-3
lines changed

1 file changed

+62
-3
lines changed

lib/marked.js

+62-3
Original file line numberDiff line numberDiff line change
@@ -554,9 +554,68 @@ inline.normal = merge({}, inline);
554554
inline.pedantic = merge({}, inline.normal, {
555555
strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
556556
em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
557-
link: edit(/^!?\[(label)\]\(\s*<?([\s\S]*?)>?(?:\s+(['"][\s\S]*?['"]))?\s*\)/)
558-
.replace('label', inline._label)
559-
.getRegex(),
557+
/* Original link re: /^!?\[(label)\]\(\s*<?([\s\S]*?)>?(?:\s+(['"][\s\S]*?['"]))?\s*\)/
558+
* This captures the spec reasonably well but is vulnerable to REDOS.
559+
* Instead we use a custom parser that follows the RegExp.exec semantics. */
560+
link: {
561+
exec: function (s) {
562+
// [TEXT](DESTINATION)
563+
var generalLinkRe = edit(/^!?\[(label)\]\((.*?)\)/)
564+
.replace('label', inline._label)
565+
.getRegex();
566+
567+
// destination: DESTINATION from generalLinkRe
568+
// returns [destination, title]: no angle-brackets on destination, no quotes on title
569+
function splitIntoDestinationAndTitle (destination) {
570+
function unwrapAngleBrackets (str) {
571+
if (str.match(/^<.*>$/)) {
572+
str = str.slice(1, -1);
573+
}
574+
return str;
575+
}
576+
577+
// Valid DESTINATIONs, in decreasing specificity.
578+
var destinationAndTitleRe = /^([^'"(]*[^\s])\s+(['"(].*['")])/;
579+
var destinationRe = /^(<?[\s\S]*>?)/;
580+
var parsingRegexes = [destinationAndTitleRe, destinationRe];
581+
582+
var match = false;
583+
for (var i = 0; i < parsingRegexes.length; i++) {
584+
match = parsingRegexes[i].exec(destination);
585+
if (match) {
586+
break;
587+
}
588+
}
589+
590+
if (!match) {
591+
return null;
592+
}
593+
594+
var dest = match[1];
595+
var title = match[2] || ''; // Not all parsingRegexes have 2 groups.
596+
597+
// Format dest.
598+
dest = dest.trim();
599+
dest = unwrapAngleBrackets(dest);
600+
601+
return [dest, title];
602+
}
603+
604+
var fullMatch = generalLinkRe.exec(s);
605+
if (!fullMatch) {
606+
return null;
607+
}
608+
609+
var text = fullMatch[1];
610+
var destination = fullMatch[2];
611+
612+
var destinationAndTitle = splitIntoDestinationAndTitle(destination);
613+
if (!destinationAndTitle) {
614+
return null;
615+
}
616+
return [fullMatch[0], text, destinationAndTitle[0], destinationAndTitle[1]];
617+
}
618+
},
560619
reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/)
561620
.replace('label', inline._label)
562621
.getRegex()

0 commit comments

Comments
 (0)