Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 10 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,18 @@
# Secure Coding with Python.

## Chapter 3: Weak Password Storage
### Fix
In order to prevent rainbow table attacks, cryptographers incorporated *[salt](https://en.wikipedia.org/wiki/Salt_(cryptography)* to hashing algorithms.
One of the algorithms that incorporates *salt* is [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt).
Said algorithm also uses a technique known as *[key stretching](https://en.wikipedia.org/wiki/Key_stretching)*, while salt prevents precomputation attacks, key stretching helps thwart attacks that rely on hardware that can perform hashes very quickly, such as GPUs and ASICs
## Chapter 4: Broken Authentication
### Requirement
Now that we have users in the system, we need to allow them to login.

To test this concept, here is a function that hashes a password and times how long it takes to do so. We increase the iteration in 4 every time.
```python
In [1]: import bcrypt
### Development
We add a simple form to allow users to login, check for user and password to be correct and add a simple session.
If something goes wrong, we drop some error messages.

In [2]: import time
### Vulnerability
Since we are very transparent and explicit in our error messages, an attacker can take advantage of them to enumerate users on our system.
This could be done to reduce time of a brute force or credential stuffing attack.

In [3]: def hash(passwd, r):
...: start = time.time()
...: salt = bcrypt.gensalt(rounds=r)
...: hashed = bcrypt.hashpw(passwd, salt)
...: end = time.time()
...: print(end - start)
...: print(salt)
...: print(hashed)
...:

In [4]: hash(b'supersecret', 4)
0.0013570785522460938
b'$2b$04$wBySsg90EhLyCxFhuNC9Ze'
b'$2b$04$wBySsg90EhLyCxFhuNC9ZeDZKdauAtlEcegqM0GOyZKIgJhJ6neMW'

In [5]: hash(b'supersecret', 8)
0.01915597915649414
b'$2b$08$QNWHnTrxBQu8pscr5hhveu'
b'$2b$08$QNWHnTrxBQu8pscr5hhveuyNOPwtR4VhxujWE/O.yjc60DhIduWkq'

In [6]: hash(b'supersecret', 12)
0.2138371467590332
b'$2b$12$.Eql6xg1/uUoWr3yuYSOaO'
b'$2b$12$.Eql6xg1/uUoWr3yuYSOaOLkEZ.XoUiJOjuMHtjyWNZoW8JOOSHx.'

In [7]: hash(b'supersecret', 16)
3.2648401260375977
b'$2b$16$A4xDXHZPHPE5tUxdqoJD0u'
b'$2b$16$A4xDXHZPHPE5tUxdqoJD0uXleSIgNGHOOv8yQ6wQIU/rLoVwqtF4C'
```

Now if an attacker gets our hashed passwords, since hashed has been computed using the password and a unique *salt*, the brute-force attack will need to be performed per-hash, rendering rainbow tables useless.
Also since we can configure the iterations, as time passes by, we can increase it's count to make a brute-force attack slower each time.

**Note**: Other algorithms that include the same concepts, and are arguably better, are scrypt and argon2.

**Proceed to [next section](https://github.com/nxvl/secure-coding-with-python/tree/4-broken-authentication/code)**
**Proceed to [next section](https://github.com/nxvl/secure-coding-with-python/tree/4-broken-authentication/fix)**

## Index
### 1. Vulnerable Components
Expand Down
21 changes: 21 additions & 0 deletions marketplace/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from flask import session, redirect, url_for

from .models import User
from . import db


def auth(func):
def decorated_function(*args, **kwargs):
key = session.get('key')
if not key:
return redirect(url_for('users.login'))

user = db.session.query(User).filter_by(session_key=key).scalar()
if not user:
return redirect(url_for('users.login'))

return func(user, *args, **kwargs)

decorated_function.__name__ = func.__name__
return decorated_function

2 changes: 1 addition & 1 deletion marketplace/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
full_name = db.Column(db.String(100))
email = db.Column(db.String(100))
email = db.Column(db.String(100), unique=True)
_password = db.Column('password', db.String(100))

@hybrid_property
Expand Down
16 changes: 16 additions & 0 deletions marketplace/templates/users/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends 'base.html' %}

{% block header %}
<h1>{% block title %}Login{% endblock %}</h1>
{% endblock %}

{% block content %}
{{ error }}
<form method="post">
<label for="email">Email</label>
<input name="email" id="email" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Signup">
</form>
{% endblock %}
9 changes: 9 additions & 0 deletions marketplace/templates/users/welcome.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% extends 'base.html' %}

{% block header %}
<h1>{% block title %}Welcome{% endblock %}</h1>
{% endblock %}

{% block content %}
Welcome to the marketplace!
{% endblock %}
31 changes: 29 additions & 2 deletions marketplace/users.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from flask import Blueprint, request, render_template
from base64 import b64encode

import bcrypt
from flask import Blueprint, request, render_template, session, url_for, redirect

from . import db
from .helpers import auth
from .models import User

bp = Blueprint('users', __name__, url_prefix='/user')


@bp.route('/signup', methods=('GET', 'POST'))
def sign_up():
if request.method == 'POST':
Expand All @@ -17,4 +22,26 @@ def sign_up():
db.session.commit()

return render_template('users/signup_success.html', user=user)
return render_template('users/signup.html')
return render_template('users/signup.html')


@bp.route('/login', methods=('GET', 'POST'))
def login():
error = None
if request.method == 'POST':
error = "The email hasn't been registered."
u = db.session.query(User).filter(User.email == request.form['email']).scalar()
if u:
error = "Invalid password."
password = request.form['password']
if bcrypt.checkpw(password.encode(), u.password.encode()):
session['logged_in'] = True
return redirect(url_for('users.welcome'))

return render_template('users/login.html', error=error)


@bp.route('/welcome', methods=('GET',))
@auth
def welcome():
return render_template('users/welcome.html')