This document discusses security best practices for Django web applications. It begins by introducing the author and their background in Python, Django, and computer security. It then covers common web vulnerabilities and attacks like information disclosure, input validation issues, session hijacking, and denial of service. Throughout, it provides recommendations for how to configure Django and code defensively to mitigate these risks, such as using parameterized queries, input sanitization, secure sessions, and cross-site request forgery protection. It emphasizes adopting a layered security approach and being vigilant about updates and monitoring.
Who is attackingusBotsMalicious SEOSteal user infoHackersScriptKiddiesHackersÜberHackersWe will bankrupt ourselves in the vain search for absolute security. — Dwight D. Eisenhower
4.
Django from asecurity standpoint Django Rocks!Salted SHA1 Hashes (Yummy)sha1 $ e3164 $ 9595556c4f693158c232f0885d266fe30671ca8aTake that Gawker!Secure session frameworkAutomatic variable escapingXXSSQL InjectionCSRF (Cross Site Request Forgery) ProtectionProtection against Email Header injectionProtection against Directory Traversal attacks“If you think technology can solve your security problems, then you don’t understand the problems and you don’t understand the technology”. — Bruce Schneier
5.
Web VulnerabilitiesInformation DisclosureInputValidationClick JackingSession HijackingCSRFPasswordsDenial of Service0 daysIn theory, one can build provably secure systems. In theory, theory can be applied to practice but in practice, it can't. — M. Dacier, Eurecom Institute
Attack SurfaceAdmin SiteDefaultsto /adminViews & URLSCan give someone an intimate view of your application.File LocationsRESTUse PistonSentry
8.
How to protectyourselfNever deploy with the default settingsLong URLS are the best (but your not out of the woods)Change the file name/location of user contentValidate uploadsRemove unneeded softwareif not chroot
Cross Site ScriptingDjangoProtects us by autoescaping outputreturn mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace(' " ', '"').replace(" ' ", '''))|safe/{% autoescape off %} is not Safe
11.
Here comes thesleep deprivationMy Template CodeSecure:<span class={{value}}>{{ value }}</span>Not Secure:<span class="{{value|safe}}">{{value|safe}}</span> Using this value -> " onclick=alert(document.cookie) type="Secure: <span class=" onclick=alert(document.cookie) type=">" onclick=alert(document.cookie) type="</span>Not Secure:<span class="" onclick=alert(document.cookie) type="">" onclick=alert(document.cookie) type="</span>Oops…
12.
How to protectyourself Use the ESAPI (Enterprise Security API)" onclick=alert(document.cookie) type="'" onclick=alert(document.cookie) type="’http://code.google.com/p/owasp-esapi-python/Use QuotesUse Sanitizerslxmlhtml5libUse WhitelistsUse Markdown
13.
SQL InjectionPython protectsusParameterized queries according to PEP 249Django’s ORM Protects usparameterized queriesPerson.objects.filter(first_name__icontains=fname,last_name__icontains=lname)fname = % \ output -> \% \\SELECT "secpre_person"."id", "secpre_person"."first_name", "secpre_person"."last_name" FROM "secpre_person" WHERE ("secpre_person"."first_name" LIKE %\% \\% ESCAPE '\' AND "secpre_person"."last_name" LIKE %s% ESCAPE '\' )smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")NEVER BUILD QUERYIES USING STRING FORMATTINGquery = 'SELECT * FROM secpre_personWHERE last_name = %s' % lnamePerson.objects.raw(query) UseParameterizedqueriesPerson.objects.raw('SELECT * FROM secpre_personWHERE last_name = %s', [lname])
14.
HTTP Response SplittingNewLines in the HTTP HeadersHTTP/1.1 302 Moved TemporarilyDate: Wed, 24 Dec 2003 15:26:41 GMT Location: http://10.1.1.1/someview/?lang=foobarContent-Length: 0 HTTP/1.1 200 OKContent-Type: text/htmlContent-Length: 19 <html>Control</html> Server: ApacheContent-Type: text/html This was just found on Reddit last weekKudos to Neal Poole from MatasanoDjango to the rescue Every HttpResponse object has this code if '\n' in value or '\r' in value: raise BadHeaderError("Header values can't contain newlines (got %r)" % (value))
15.
CRLF InjectionHijack emailformsto:”me@myaddress.com\ncc:bill.gates@microsoft.com\rcc:paul.allen@microsoft.com”Django to the rescue if '\n' in val or '\r' in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
16.
Directory Traversal../../../../../../../../../etc/passwdDjango shouldnever serve static filesYour webserver should serve all static files and be locked into the web root directoryNever allow users to dictate what happendsDjango Static Serve isn’t powerlessdrive, part = os.path.splitdrive(part) head, part = os.path.split(part) if part in (os.curdir, os.pardir): # Strip '.' and '..' in path. continue
17.
Click JackingUse X-FRAMEHTTPheader X-FRAME-OPTIONS: DENYhttps://github.com/paulosman/django-xframeoptionsUse a Framekiller<script type="text/javascript"> if(top != self) top.location.replace(location); </script> Beware of sites that you visit
18.
Session HijackingFireSheepCookie infonot sent over HTTPSPass the hashSESSION_COOKIE_SECURE = TrueSESSION_COOKIE_HTTPONLY = TrueSessionsNever store private data in clear textNever display session data without escaping it
19.
Cross Site RequestForgery<imgsrc="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">We are logged in so it worksDjango protects us (unless we are really stupid)HTTP/1.0 200 OKDate: Mon, 17 Jan 2011 21:55:14 GMTServer: WSGIServer/0.1 Python/2.7.1Expires: Mon, 17 Jan 2011 21:55:14 GMTVary: CookieLast-Modified: Mon, 17 Jan 2011 21:55:14 GMTETag: "4030d6e6a6c31292791e61e8bc58b6e8"Cache-Control: max-age=0Content-Type: text/html; charset=utf-8Set-Cookie: csrftoken=9260e87b366dd2be2515bffffec5a746; Max-Age=31449600; Path=/
20.
Denial Of ServiceEverythingis vulnerable Impossible to defend against every variantHarden your serverRate limitingDo this on a server levelIf you need to do this on a view levelhttps://gist.github.com/719502Fine tune access methods for your viewsrestrict the HTTP method to the appropriate view
21.
PasswordsPasswords are yourbiggest nightmareDon’t trust themMake sure that you are using SHA1Even though it works md5 and crypt shouldn’t be used. crypt should NEVER be used!!! Rate limitingUse Django-axeshttp://code.google.com/p/django-axes/Never rely on just a passwordIf you can use 2 factor authentication do it.
22.
0 Day ProtectionRunfor the hillsGood security is like a big onionMany layersBitterLimit your exposureServer monitoringRemember a good programmer looks both ways before crossing a one way street.
23.
Security TipsBe waryof updatesUpdate on security releasesBeware of 3rd party appsSeparate work from playDon’t rely on passwordsFail2BanStick with DjangoBe careful where you strayScan oftenSkipfish
#5 Salted hashes make it harder to guess the password by making each password unique. They are immune to rainbow table (pre-generated hashes) attacks.
#8 Don’t try to create your own version of REST. Use something like Django-Piston which has a proven track record. Also never use your object ID’s in urls. If needed use UUID’s
#13 The regular Django auto escape helps in almost every case. However you need to protect yourself in every case. That’s why using the ESAPI is one of the best solutions to the overall problem.
#14 The Django ORM is escaping my LIKE query using the function on the bottom. All other queries are parameterized.
#19 SESSION_COOKIE_HTTPONLY should be set if you don’t want JavaScript to touch your cookie.
#20 Without that cookie you get a 403 if you want to post to that form.
#22 Easy 2 factor auth is sending a SMS to a persons cellphone. If your going to use OAUTH then remember to send everything secure (HTTPS).
#24 Django has a lot of security built in so if you ever replace any part of it make sure it’s secure enough to be on your website.