Cross-Site Scripting (XSS)

Cross-site scripting (XSS), is found in Web applications that fail to escape user-submitted content properly before rendering it into HTML. This allows an attacker to insert arbitrary HTML into your Web page, usually in the form of <script> tags. Attackers often use XSS attacks to steal cookie and session information, or to trick users into giving private information to the wrong person (aka phishing). This type of attack can take a number of different forms and has almost infinite permutations, so we’ll just look at a typical example. Consider this extremely simple “Hello, World” view:

def say_hello(request):
name = request.GET.get(‘name’, ‘world’)
return render_to_response(“hello.html”, {“name” : name})

This view simply reads a name from a GET parameter and passes that name to the hello.html template. We might write a template for this view as follows:

<h1>Hello, {{ name }}!</h1>

So if we accessed http://example.com/hello/name=Jacob, the rendered page would contain this:

<h1>Hello, Jacob!</h1>

But wait — what happens if we access http://example.com/hello/name=<i>Jacob</i>? Then we get this:

<h1>Hello, <i>Jacob</i>!</h1>

Of course, an attacker wouldn’t use something as benign as <i> tags; he could include a whole set of HTML that hijacked your page with arbitrary content. This type of attack has been used to trick users into entering data into what looks like their bank’s Web site, but in fact is an XSS-hijacked form that submits their back account information to an attacker.

The problem gets worse if you store this data in the database and later display it it on your site. For example, MySpace was once found to be vulnerable to an XSS attack of this nature. A user inserted JavaScript into his profile that automatically added him as your friend when you visited his profile page. Within a few days, he had millions of friends. Now, this may sound relatively benign, but keep in mind that this attacker managed to get his code — not MySpace’s — running on your computer. This violates the assumed trust that all the code on MySpace is actually written by MySpace. MySpace was extremely lucky that this malicious code didn’t automatically delete viewers’ accounts, change their passwords, flood the site with spam, or any of the other nightmare scenarios this vulnerability unleashes.

The Solution – The solution is simple: always escape any content that might have come from a user. If we simply rewrite our template as follows:

<h1>Hello, {{ name|escape }}!</h1>

then we’re no longer vulnerable. You should always use the escape tag (or something equivalent) when displaying user-submitted content on your site.

Why Doesn’t Django Just Do This for You? – Modifying Django to automatically escape all variables displayed in templates is a frequent topic of discussion on the Django developer mailing list.So far, Django’s templates have avoided this behavior because it subtly changes what should be relatively straightforward behavior (displaying variables). It’s a tricky issue and a difficult tradeoff to evaluate. Adding hidden implicit behavior is against Django’s core ideals (and Python’s, for that matter), but security is equally important.

All this is to say, then, that there’s a fair chance Django will grow some form of auto-escaping (or nearly auto-escaping) behavior in the future. It’s a good idea to check the official Django documentation for the latest in Django features; it will always be more up to date than this book, especially the print edition. Even if Django does add this feature, however, you should still be in the habit of asking yourself, at all times, “Where does this data come from?” No automatic solution will ever protect your site from XSS attacks 100% of the time.

Back to Tutorial

SQL Injection
Cross-Site Request Forgery

Get industry recognized certification – Contact us

keyboard_arrow_up
Open chat
Need help?
Hello 👋
Can we help you?