53

I have learned that Django provides built-in protection against the three main types of web app attacks (SQL injection, XSS and CSRF), which is really awesome.

Yet I have spoken to a few Django developers and they have essentially told me not to rely on these too heavily, if at all.

They told me to treat all data as "unsafe" and handle it appropriately.

I have two questions to ask everyone about this:

  1. Can Django's built-in security features be relied upon?
  2. What do my friends mean by "handle it (unsafe data) appropriately"? I know its about escaping and/or removing unsafe phrases and/or characters, but my biggest fear is that if I try to do that myself I will end up producing a "filter" that is worse off than Django's built in stuff. Does anyone have any guides for building on top of Django and not overriding it, in this regard?
Anders
  • 64,406
  • 24
  • 178
  • 215
pleasedesktop
  • 633
  • 1
  • 6
  • 6

1 Answers1

77

SQL injection. If you use Django's object-relational mapper (ORM) layer, you are basically protected from SQL injection.

The only caveat is that you need to avoid manually forming SQL queries using string concatenation. For instance, do not use raw SQL queries (e.g., raw()). Similarly, do not use the extra() method/modifier to inject raw SQL. Do not execute custom SQL directly; if you bypass Django's ORM layer, you bypass its protections against SQL injection.

CSRF. Django's built-in CSRF protection is good. Make sure you enable it and use it everywhere. Django provides ways to disable it locally or globally; obviously, don't do that.

It is important that you make sure that GET requests do not have any side effects. For requests which can have a side-effect, make sure you use a POST request (and do not accept a GET request for those). This is standard web design, but some developers screw it up; Django's built-in CSRF prevention assumes you get this right.

There are some caveats if you have subdomains (e.g., your web app is hosted on www.example.com and there is a subdomain alice.example.com that hosts user-controlled content); the built-in CSRF protection might not be sufficient in that case. That's a tricky case for a number of reasons. Most web applications won't have to worry about these caveats.

XSS. If you use Django's template system and make sure that auto-escaping is enabled, you're 95% of the way there. Django provides an auto-escaping mechanism for stopping XSS: it'll automatically escape data that's dynamically inserted into the template, if it hasn't already been escaping (see e.g., mark_safe, safe, etc.). This mechanism is good stuff, but it is not quite enough on its own. It takes you much of the way to stopping XSS, but you still have to be aware of some issues.

In particular, Django's auto-escaping will escape data sufficiently for most HTML contexts, but in some special cases you must manually do additional escaping:

  • Make sure you quote all attributes where dynamic data is inserted. Good: <img alt="{{foo}}" ...>. Bad: <img alt={{foo}} ...>. Django's auto-escaping isn't sufficient for unquoted attribute values.

  • For data inserted into CSS (style tags and attributes) or Javascript (script blocks, event handlers, and onclick/etc. attributes), you must manually escape the data using escaping rules that are appropriate for CSS or Javascript.

  • For data inserted into an attribute where a URL is expected (e.g., a href, img src), you must manually validate the URL to make sure it is safe. You need to check the protocol against a whitelist of allowed protocols (e.g., http:, https:, mailto:, ftp:, etc. -- but definitely not javascript:).

  • For data inserted inside a comment, you have to do something extra to escape -s. Actually, better yet, just don't insert dynamic data inside a HTML comment; that's an obscure corner of HTML that's just asking for subtle problems. (Similarly, don't insert dynamic data into attributes where it is crazy for user input to appear (e.g., foo class=...).)

  • You still must avoid client-side XSS (also known as DOM-based XSS) separately; Django doesn't help you with this. YOu could read Adam Barth's advice on avoiding XSS for some guidelines that will help you avoid client-side XSS.

  • If you use mark_safe to manually tell that Django that some data has already been escaped and is safe, you'd better know what you're doing, and it really better be safe. It's easy to make mistakes here if you don't know what you are doing. For example, if you generate HTML programmatically, store it into the database, and later send it back to the client (unescaped, obviously), it's very easy to make mistakes; you're on your own, and the auto-escaping won't help you.

The reason why you have to do something extra for these cases is that Django's auto-escaping functionality uses an escaping function that is supposed to be one-size-fits-all, but in practice, one size doesn't always fit in every case; in some cases, you need to do something extra on your own.

For more information about what escaping rules to use in these special contexts, see the OWASP XSS cheatsheet.

Other stuff. There are some other things you didn't mention:

For more information. See Django's documentation on security, including the chapter on security in the Django book.

D.W.
  • 98,420
  • 30
  • 267
  • 572
  • 3
    Thank you for this brilliant answer! You covered all three aspects I mentioned and more... thank you so much, exactly what I was after. – pleasedesktop Jan 10 '13 at 07:04
  • I have a question though, regarding the ORM layer. I lie within the Django ORM layer except for a couple of extra() calls. Am I still "safe" even with those extra() calls, if the input into extra() is not affected by the user (i.e. hard-coded stuff)? – pleasedesktop Jan 10 '13 at 07:05
  • I forgot to mention that I will definitely be hooking up HTTPS prior to the site going live, but don't see the point during development. I was told it is an easy switch from standard HTTP. – pleasedesktop Jan 10 '13 at 07:11
  • +1 - D.W. had a great comprehensive answer, but you should definitely be aware of the types of attacks and code safely using best practices. Django has a great framework, but even the best tool let's you still do stupid things. E.g., if you say modify DB data based on HTTP GET requests (instead of POST) you are vulnerable to CSRF. Or if you let users inject HTML content that you've `mark_safe` or `{{ something|safe }}`, they maybe able to inject say an HTML ` – dr jimbob Jan 10 '13 at 07:11
  • D.W. mentioned about HTTP GET and not to have side effects. Thanks, this "..but even the best tool let's you still do stupid things." is a great eye opener. – pleasedesktop Jan 10 '13 at 07:14
  • 3
    Also to answer @freshquiz questions: `extra()` is fine if its not based on manual string processing like `Entry.objects.extra(where=['%headline=%s' % user_input])`. There is a safe way to do that -- with `params` that makes it a bound parameter see: https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.extra . Also no problem with initially developing without SSL, just be sure to thoroughly test with SSL before deploying. – dr jimbob Jan 10 '13 at 07:18
  • Yes I am not passing anything into extra(), all hard-coded, thanks for that. (SSL) Will do, thank you dr jimbob. – pleasedesktop Jan 10 '13 at 07:49