diff --git a/textfilter/vibe/textfilter/markdown.d b/textfilter/vibe/textfilter/markdown.d index 4d92cc3185..23871682cd 100644 --- a/textfilter/vibe/textfilter/markdown.d +++ b/textfilter/vibe/textfilter/markdown.d @@ -129,6 +129,9 @@ final class MarkdownSettings { /// Called for every link/image URL to perform arbitrary transformations. string delegate(string url_or_path, bool is_image) urlFilter; + + /// White list of URI schemas that can occur in link/image targets + string[] allowedURISchemas = ["http", "https", "ftp", "mailto"]; } enum MarkdownFlags { @@ -593,8 +596,18 @@ private void writeMarkdownEscaped(R)(ref R dst, ref const Block block, in LinkRe /// private private void writeMarkdownEscaped(R)(ref R dst, string ln, in LinkRef[string] linkrefs, scope MarkdownSettings settings) { + bool isAllowedURI(string lnk) { + auto idx = lnk.indexOf('/'); + auto cidx = lnk.indexOf(':'); + // always allow local URIs + if (cidx < 0 || idx >= 0 && cidx > idx) return true; + return settings.allowedURISchemas.canFind(lnk[0 .. cidx]); + } + string filterLink(string lnk, bool is_image) { - return settings.urlFilter ? settings.urlFilter(lnk, is_image) : lnk; + if (isAllowedURI(lnk)) + return settings.urlFilter ? settings.urlFilter(lnk, is_image) : lnk; + return "#"; // replace link with unknown schema with dummy URI } bool br = ln.endsWith(" "); @@ -696,10 +709,10 @@ private void writeMarkdownEscaped(R)(ref R dst, string ln, in LinkRef[string] li if( parseAutoLink(ln, url) ){ bool is_email = url.startsWith("mailto:"); dst.put(""); - if( is_email ) filterHTMLAllEscape(dst, url[7 .. $]); + if (is_email) filterHTMLAllEscape(dst, url[7 .. $]); else filterHTMLEscape(dst, url, HTMLEscapeFlags.escapeMinimal); dst.put(""); } else { @@ -1316,3 +1329,18 @@ private struct Link { assert(filterMarkdown("> ```\r\n> test\r\n> ```", MarkdownFlags.forumDefault) == "
\n"); } + +@safe unittest { // issue #1845 - malicious URI targets + assert(filterMarkdown("[foo](javascript:foo) ![bar](javascript:bar)\ntest\n
foo \n
\n"); + assert(filterMarkdown("[foo](javascript%3Abar)", MarkdownFlags.forumDefault) == + "foo\n
\n"); + + // extra XSS regression tests + assert(filterMarkdown("[](bar)", MarkdownFlags.forumDefault) == + "\n"); + assert(filterMarkdown("[foo](\">foo\n\n"); +}