Django Security and Passwords
Django Security and Passwords
Objectives
Identify the ways in which Django is secure by default
Define salting
In the Terminal
Clone the repo. Your command should look something like this:
One example might be PHP. If you’re not using a framework, you need to
be very careful to escape HTML strings before outputting them; similarly
falling back to low level database functions can lead to SQL injection
attacks. Django prevents these with HTMl escaping by default in templates;
and its ORM, respectively.
A good primer for web security is the Open Web Application Security
Project® (OWASP) Top 10, which is a list of the top ten web application
security risks. Some of them are not risks to your application unless you’re
building some specific types of tools, but we’ll discuss which one Django
mitigates.
Injection
Injection flaws are usually attacks against SQL, but could also affect NoSQL
databases, files or executed commands. Django protects against SQL
injection with its ORM, but if you decided to write raw SQL to execute
queries, be sure to parameterize any input (if you’re having to write raw
SQL you probably know how to do this). When using other databases (for
example, Cassandra or MongoDB) be sure to use up-to-date libraries to
connect and run queries – these should handle escaping for you.
Broken Authentication
It’s also important to use SSL/TLS to encrypt data during transit. Django can
help ensure this by adding the setting SECURE_SSL_REDIRECT = True, which
forces a redirect from the http to https version of the URL. Your web server
will need to configured correctly for SSL for this to work though.
If your application has broken access control, it’s probably not a fault of
Django itself. Instead, someone might have written a view that doesn’t
correctly check for a valid user. Instead of checking inside a view, consider
using the login_required decorator, which will also automatically redirect
to a login page. This will make it clear which views have or haven’t had this
protection applied.
But it’s not a magic bullet. Even if you check that a user has logged in, you
need to make sure you’re fetching objects that belong to that user and not
someone else. This basically comes down to being fastidious with code and
encouraging code review from your team to make sure nothing has been
missed.
Security Misconfiguration
Security misconfiguration is “the most commonly seen issue”, according to
OWASP. Django is secure by default, and doesn’t send stack traces or
verbose information to users.
But it’s only one piece of the puzzle, your whole infrastructure needs to be
secure, have patches applied, not expose any ports it shouldn’t, and so on.
If you’re unsure about configuration, have someone experience help out.
Insecure Deserialization
This is not something Django can proactively prevent against – if they knew
they had security vulnerabilities then they would fix them! Instead, it’s up
to the end user to keep Django, other Python libraries, and the OS, up to
date and patched.
The right logging and monitoring can help mitigate the severity of attacks
by being able to respond to them quickly. This kind of monitoring is not
something Django has built in, and it might not be the best place to have
these kind of checks. You could use an Intrusion Detection System to help
with alerting, but logging admin logins, failed login attempts, and other
suspicious requests, as well as having a process for reviewing the logs
regularly, is a good process to have in place.
Hashing Intro
Before reading this section, please understand that it is designed to give
background on how Django stores passwords, and not advice on how to
write your own password storage method. We will go through a few
examples of insecure methods of doing it, so that you know what not to do.
In short, trust Django’s built-in methods, and don’t write your own. If
you’re not interested in how passwords are stored you can skip this section,
but it’s a good primer into some of the issues which might seem like a
straightforward problem.
When a user wants to log into Blango (or just about any other website),
they put in their username and password. How does the website validate
that these are correct?
One way might be to store the username and password in the database, but
this is a terrible idea, never do this! Sure, it makes it extremely simple to
check that the credentials are correct, just compare the username and
password and make sure they match exactly, but it’s a security nightmare.
If your database were to leak, then all your user’s passwords are exposed.
Many people use the same username and password for multiple websites,
and so the attacker would have access to any of them too (although the
increasing prevalence of password managers has reduced this risk).
print(hashlib.sha256(b"password").hexdigest())
print(hashlib.sha256(b"hello wold").hexdigest())
print(hashlib.sha256(b"password123").hexdigest())
5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
03457c36995bc52df8fa0430393535842195c72ffff555a9febdf2098c959ce8
ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f
The next level of security is to add a salt: a small amount of random data
that’s added to the password before it’s hashed, when it’s first set. The salt
is stored unhashed, and is read and reused on password validation.
For example, let’s say we generate the salt abc123 when the user sets their
password/registers. The password hash would be generated like this:
import hashlib
print(hashlib.sha256(b"abc123" + b"password123").hexdigest())
When the user tries to log in, we’d fetch this salt + hash from the database.
Then we’d extract the salt (the bit before the $), prepend it to the password
they supplied, and hash that string. We know the password is correct if that
resulting hash matches the bit after the $.
Our security is getting stronger, but most hash functions have a problem:
they are designed to be fast – this is probably the only time that we’d say
fast code is a “problem”! We can improve our security more by changing
our hash function and using multiple iterations. We’ll return to a new hash
function in the next section, but let’s talk about why the number of
iterations is important now.
We can increase the time it takes calculate the hash by simply re-hashing
the output over and over, say 1,000 times. Here’s some Python code that
illustrates how this is done:
import hashlib
019598128a03681d7c83bae0ea9db7ebfa1c63c1749bd08ebc7aa8e95e99dcf8
The output that’s stored in the database is the final hash 01959…. In our
scenario, the time it takes to log in a user goes from 0.1 seconds to 1.099
seconds (or about an extra second). Again, in reality the actual durations
will be different, but even in our case this is probably not that noticeable to
a user, especially since they would probably only log in once per session,
not on every page. But the effect on an attacker is much more dramatic. It
will slow down brute force attempts from 10 per second to less than 1 per
second, meaning it would take 10 times longer to guess the password of a
user.
This introduction has given you an idea of how not to store passwords, and
what pitfalls there could be to building your own password storage.
Luckily, we don’t have to do that as Django provides good (but not the best!)
security by default. Let’s look at how Django stores passwords and how you
can improve on its default security.
Storing Passwords
pbkdf2_sha256$260000$ud2D0L3h8b98JDjGaffUnQ$LAYlrboOAUGZdRhEh7xT
/oom2Nv/dpJpmDOpnmPze0k=
<algorithm>$<iterations>$<salt>$<hash>
So in our case:
The hash functions that Django can use are set using the PASSWORD_HASHERS
setting, which defaults to:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
Let’s make Blango a bit more secure, by switching to Argon2 for password
hashing. First install the Argon2 algorithm with pip in the terminal.
Installing [argon2]
Depending on your shell, you might have to quote the name of the package:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
Since we leave the other algorithms in the list, we can still validate our
password that was set with PBKDF2PasswordHasher.
Try updating your password in Django Admin. While you can’t see the
whole password you can see the hash it uses. You should see this update to
argon2.
Figure 1
View Blog
If you want to make your passwords even more secure and harder to
compute, check out the Password management in Django. It gives you some
example of how to change the parameters for the algorithm you’ve chosen.
That’s all we’re going to cover for security in Django for now. In future
courses if we use a specific filter or output method we might point out the
security rationale behind it and the reason for doing it this specific way.
We’re going to finish this module with a brief look at some deployment
options for Django.
Pushing to GitHub
Pushing to GitHub
Before continuing, you must push your work to GitHub. In the terminal:
git add .
git commit -m "Finish Introduction and Django Admin"
Push to GitHub:
git push