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
'<': '<', '>': '>'}
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.