Home Unauthenticated Stored XSS on Django-Markdownx
Post
Cancel

Unauthenticated Stored XSS on Django-Markdownx

INTRODUCTION

Django-markdownx is a famous markdown library for python. According to github, it is used by 1.6k projects, on githubs, that doesnt include closed source projects and websites.

In this writeup, i will show you a bug that i find in this django plugin

CODE REVIEW

Starting from the urls.py, it only has two endpoints

1
2
3
4
urlpatterns = [
    url('upload/', ImageUploadView.as_view(), name='markdownx_upload'),
    url('markdownify/', MarkdownifyView.as_view(), name='markdownx_markdownify'),
]

The upload endpoint is pointing to the ImageUploadView class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ImageUploadView(BaseFormView):
    form_class = ImageForm
    success_url = '/'

    def form_invalid(self, form):
        #Uninteresting snippet

    def form_valid(self, form):
        response = super(ImageUploadView, self).form_valid(form)

        if self.request.headers.get('x-requested-with') == 'XMLHttpRequest':
            image_path = form.save(commit=True)
            image_code = '![]({})'.format(image_path)
            return JsonResponse({'image_code': image_code})

        return response

It is an BaseFormView class which is just the FormView Class. You can see how the FormView class works in diagram here https://www.brennantymrak.com/articles/formviewdiagram. It uses the class ImageForm for the form and it save the form. Here is the ImageForm class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ImageForm(forms.Form):
    image = forms.FileField()
    
    _SVG_TYPE = 'image/svg+xml'

    def save(self, commit=True):
        image = self.files.get('image')
        content_type = image.content_type
        file_name = image.name
        image_extension = content_type.split('/')[-1].upper()
        image_size = image.size

        #Unimportant code

        if (content_type.lower() == self._SVG_TYPE
                and MARKDOWNX_SVG_JAVASCRIPT_PROTECTION
                and xml_has_javascript(uploaded_image.read())):

            raise MarkdownxImageUploadError(
                'Failed security monitoring: SVG file contains JavaScript.'
            )

        return self._save(uploaded_image, file_name, commit)

If the file is svg, it checks it using the xml_has_javascript() function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def xml_has_javascript(data):
    from re import search, IGNORECASE, MULTILINE

    data = str(data, encoding='UTF-8')
    pattern = r'(<\s*\bscript\b.*>.*)|(.*\bif\b\s*\(.?={2,3}.*\))|(.*\bfor\b\s*\(.*\))'

    found = search(
        pattern=pattern,
        string=data,
        flags=IGNORECASE | MULTILINE
    )

    if found is not None:
        return True
    #Unimportant Code
    return False

Here, it only checks if it has a \<script> or <if> or <for> node. However, there are ways to achieve xss without using any of those, i made a poc with it posted in github https://gist.github.com/noobexploiterhuntrdev/c4db7e87841f43f3befdeb1de4f18092.

REPRODUCTION

I dont code myself, hopefully tho, i found a github project using this library. So i installed it and hosted it in my vps. https://github.com/vladyslavnUA/foodanic.

I made the file upload request to upload/, with the svg payload that i have.

The filename and the directory of the uploaded image, is shown in the response. Visiting this endpoint, will give us the xss popup that we expected

Our xss payload worked

CLOSING NOTES

I tried my best to disclose this to the maintainer of the project, but they are really unresponsive. I reported it last febuary, and it is now April, so i decided to disclose it. Thanks for reading

This post is licensed under CC BY 4.0 by the author.