Security in Django
Who am I 🚀? Django core developer Member of Django's security & ops team Part time student, part time worker apollo13 on IRC & Github
Security would be easier without humans in the picture! We will not talk about … Security would be easier without humans in the picture! https://xkcd.com/538/
… or … any other of the attacks against SSL/TLS out there (or server missconfigs for that matter) Oh: Update to the latest Python point release But please read and apply: https://cipherli.st/ https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ https://www.ssllabs.com/ssltest/
First stop https://djangoproject.com/security Only contact: security@djangoproject.com We fix, prenotify and release Please give us time and work with us!
OWASP (or: How are we doing) Taken from: OWASP Top 10 2013, CC-BY-SA 3.0 (https://www.owasp.org)
Security in Django SQL/SMTP/OS injections Authentication and Session management Cross site scripting (XSS) Cross site request forgery (CSRF) Unvalidated redirects and forwards Activation of browser security features
Before we start DO NOT TRUST USER INPUT (EVER) pigtailpalsblog.com
SQL injections Database adapters cursor.execute(), .extra(), .raw() & RawSQL() Exploit via: sql = 'select * from auth_user where username=%s' cursor.execute(sql % (username,)) Be safe (same for .extra(), .raw() & RawSQL()): cursor.execute(sql, (username,))
OS/LDAP/SMTP/etc. injections Use Django components instead of rolling your own (storage/email) Read 3rd party docs (LDAP/os.Popen) Generally: String interpolation is bad
DO NOT TRUST USER INPUT (EVER) Repeating myself DO NOT TRUST USER INPUT (EVER) Everything the browser sends is user input, including EVERY header and filenames/contenttypes in uploads.
Auth and session management What happens during login? (or: why shouldn't I implement it on my own) Authenticate the user Set session_auth_hash Flush/rotate session key Rotate CSRF token Check redirects Set session cookie & redirect
In detail Authentication: Compare (safely) against stored password and update if needed session_auth_hash: Sign out all other sessions after password change Change session and CSRF tokens on login to prevent fixation Check next target to ensure that the user stays on the same site
Password storage/validation Multiple Algorithms ootb (bcrypt/PBKDF2/…) Iterations increased every release Upgrade to new algos always possible constant_time_compare Password validators since 1.9 Check length/numeric/common… Enabled only for new projects!
Password reset User requests a password reset link Emailed to the user Link can be used once (!) to reset the password /reset/MQ/46h-6965b6f67bcf041e513a/ User Timestamp HMAC of User.pk, password, last login & timestamp
Signing in your code django.core.signing.* dumps('HI', 'password', 'my_ns') => 'IkhJIg:1ZstkN:IflKlCmedvbp6- y8NYHdF5yBsi8' Includes the data! Adds an optional timestamp (Salted) HMAC over everything
XSS Cross-site scripting Reflected or persistent https://site.com/?search=<b>banana</b> => You searched for: banana Often easy and can be very dangerous www.minionland.com
XSS protection Auto escaping HTML only, not context aware Replaces < > ' “ & with entities Always use quotes around attributes Javascript requires different escaping var mystr = '{{ value|escapejs }}'; Only for use in strings!
XSS Examples Attack: data = "</script><script>alert('xss');//" Worst case (in a script tag): var json = {{ data|dumps|safe }}; A little bit better and visibly broken: var json = {{ data|dumps }}; (Browser dev tools!)
XSS Examples 2 Using Django: var json = JSON.parse('{{ data|escapejs }}'); Or simpler: var json = {{ data|json }}; from django-argonauts Do not use inside attributes!
XSS protection 2 Defense in Depth X-XSS-Protection: 1; mode=block No inline JS, no event handlers Content-Security-Policy (not yet in Django) Check your (filter) libraries & code, many people just do mark_safe(json.dumps())
CSRF Cross-site request forgery <img src='mybank.com/t/?amount=1000&to=ap ollo13'/> Still no kittens and money gone
CSRF protection Enabled by default Does not cover GET/HEAD/OPTIONS/TRACE Server generates random value Put it into form (header for ajax) and cookie Compare values on the server again
CSRF protection 2 Why does it work and why can we trust the browser here. Form on evil.com: <form method='post' action='somesite.com'> <input name='csrfmiddlewaretoken' value='?' /> How to get the CSRF token? But you said the value is also in the Cookie!
CSRF protection 3 Our implementation is “secure” (Even if auditors tend to disagree) Changing token via Firebug is NOT a security issue
Unvalidated redirects/forwards Attacker redirects to: /auth/login/?next=http://evil.com After login you are on evil.com! django.utils.http.is_safe_url More comments than code → hard to get right
Security checklist ./manage.py check --deploy System check identified 9 issues (0 silenced). DEBUG = False, SECRET_KEY is secret Enable SECURE_* settings *_COOKIE_(SECURE|HTTPONLY) = True X_FRAME_OPTIONS = 'DENY'
TODO in Django Implement rate limiting (for login etc) 2FA (TOTP & U2F as reference implementations) CSRF improvements (#16859) JSON filter for templates?
Even more TODOs Enhance SecurityMiddleware (github.com/twitter/secureheaders) Implement Content-Security-Policy (admin & #15727 ) Limit POST data length & number (#21231)
Questions? & Thank you!