🔖 Day17 - Markdown DIY prevent XSS attack (Part 1)

2018 - 06 - 26
🔖 Day17 - Markdown DIY prevent XSS attack (Part 1)
In Django, there is CSRF protection against the Cross-Site Request Forgery. It is so handy which reduces our workloads on security issues. However, XSS attack is also a problem. So what is XSS? Well, simply speaking, it is about some sneaky attackers who found out your site contains fields that enable scripts, e.g. comments with scripts running. These sneaky guys then post some scripts such as html or javascript. When a visitor come in and load the comment page, the scripts are invoked. That innocent visitor is now vulnerable to privacy exposure which may be further sent to attackers in background. This picture from Incapsula briefly sums up.
1. [Django safe filter] So, what exactly does it deal with Django site? While rendering fields in a template, we use {{ field }}. To enable scripts on certain field, a template filter safe can be used, e.g. {{ field | safe }}. Hence, any scripts in this field will not be autoescaped but able to run smoothly. Consequently, the page containing this field is now susceptible to XSS attacks. Say you have a comment session with scripts enabled and is constructed with form.py:
from django import forms
from .models import Comment

class CommentForm(forms.ModelForm):
    content = forms.CharField(required=True, widget=forms.Textarea)
    
    class Meta:
        model = Comment
        fields = ('content',)
views.py :
from django.shortcuts import render
from .form import CommentForm

def viewComment(request):
    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            form.save()
        else:
            form = CommentForm()
    else:
        form = CommentForm()
    return render(request, 'comment.html', {'form': form})
    
and template : (Note that this is the template for entering comment, but not viewing the comment)
<form method='POST'>
    {% csrf_token %}
    {{ form.as_p }}
    <input type='submit' value='Submit'>
</form>
Nothing in form.py or views.py is going to deal with XSS. Thus, we have to do it on our own. There, is what a markdown tool can help. Of course, you can clone any markdown tool from github, but in this demo I am going to build my own.
2. [buttons for using scripts] We love to have text-decorations and insert links while typing online. To avoid users from typing these common scripts manually, (and to assist those not familiar with HTML tags), we can create some buttons for them. Firstly, head to template :
<form method='POST'>
    {% csrf_token %}
    <div>
        <button type='button' class='btn' id='bold'>Bold</button>
        <button type='button' class='btn' id='link'>Link</button>
    </div>
    {{ form.as_p }}
    <input type='submit' value='Submit'>
</form>
This will add 2 buttons Bold and Link above the fields. Then add the following javascript/jQuery to the same template:
<script>
$(document).ready(function(){
    $('#bold').click(function(){
        var temp = $('#id_content').val();
        $(#'id_content').val(temp + '[bold]Text Here...[/bold]');
    });
    $('#link').click(function(){
        var temp = $('#id_content').val();
        $(#'id_content').val(temp + '[link]Url Here...[linkTitle]Name of Page Here..." target="_blank">Url Here...[/link]Name of Page Here...');
    })
});
</script>
Such that when users click on either button, this will add corresponding tag, e.g. [bold]Text Here...[/bold], into the content field. #id_content should be the id of your content field as long as your field name is set as content in form.py .
3. [adding form method] Secondly, go to form.py , import regex and add a form method updateContent :
from django import forms
from .models import Comment
#Add this
import re

class CommentForm(forms.ModelForm):
    content = forms.CharField(required=True, widget=forms.Textarea)
    
    class Meta:
        model = Comment
        fields = ('content',)
    
    #Add this
    def updateContent(self):
        data = self.cleaned_data['content']
        lines = data.split('\n')
        newLines = []
        tags = {'[bold]':'<b>', '[/bold]':'</b>',
                 '[link]':'<a href="', '[linkTitle]':'">', '[/link]':'</a>'}
        for line in lines:
            for t in tags:
                line = re.sub(t, tags[t], line)
            newLines.append(line)
        newData = '\n'.join(newLines)
        return newData
Now our form has a method updateContent which detects the tags we created and return the comment content with proper HTML tags instead.
4. [calling the form method] finally head to views.py , here we modify a few lines, include calling the form method, updating the content before saving the form:
from django.shortcuts import render
from .form import CommentForm

def viewComment(request):
    if request.method == 'POST':
        #Change this
        temp = CommentForm(request.POST)
        if temp.is_valid():
            #Add these
            content = temp.updateContent()
            form = temp.save(commit=False)
            form.content = content
            
            form.save()
        else:
            form = CommentForm()
    else:
        form = CommentForm()
    return render(request, 'comment.html', {'form': form})
In this snippet, we named a new variable temp to validate first. After that, run the form method updateContent and return the new content to a new variable content . Since we need to modify the form details before saving, we have to call temp.save(commit=False) . Lastly, assign content to form.content and de facto save the form.
5. [escaping the angle brackets] It is time to check out your comment page in browser. There should be the bold and link buttons, which add tags to content field on click. Through submitting the comment, these tags become proper HTML tags, which can be loaded on the page with {{ field | safe }}. But what we have done is just providing alternatives to users, not restricting them from typing their own scripts! Oh, this is cake now. What we have done so far is created some tags for masking proper HTML tags. Similary, we can mask users' angle brackets via the same old form method. Head back to form.py and add 2 more lines:
from django import forms
from .models import Comment
import re

class CommentForm(forms.ModelForm):
    content = forms.CharField(required=True, widget=forms.Textarea)
    
    class Meta:
        model = Comment
        fields = ('content',)
    
    def updateContent(self):
        data = self.cleaned_data['content']
        lines = data.split('\n')
        newLines = []
        tags = {'[bold]':'<b>', '[/bold]':'</b>',
                 '[link]':'<a href="', '[linkTitle]':'">', '[/link]':'</a>',
                #Add these 2 key-value pairs
                 '<': '&lt;', '>': '&gt;'}
        for line in lines:
            for t in tags:
                line = re.sub(t, tags[t], line)
            newLines.append(line)
        newData = '\n'.join(newLines)
        return newData
See? It's done! By converting the angle brackets into their corresponding HTML entities, whenever users type their own scripts, it fails to run. Instead, the brackets will show up as normal brackets in the viewing template.

Comments

There is no comment yet

New Comment

Please Login to comment