diff --git a/README.md b/README.md index 588ccf9..14a9df9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/marketplace/helpers.py b/marketplace/helpers.py new file mode 100644 index 0000000..4dd8a5a --- /dev/null +++ b/marketplace/helpers.py @@ -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 + diff --git a/marketplace/models.py b/marketplace/models.py index 660f9c3..d160222 100644 --- a/marketplace/models.py +++ b/marketplace/models.py @@ -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 diff --git a/marketplace/templates/users/login.html b/marketplace/templates/users/login.html new file mode 100644 index 0000000..e3df28d --- /dev/null +++ b/marketplace/templates/users/login.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Login{% endblock %}

+{% endblock %} + +{% block content %} +{{ error }} +
+ + + + + +
+{% endblock %} diff --git a/marketplace/templates/users/welcome.html b/marketplace/templates/users/welcome.html new file mode 100644 index 0000000..95f17d4 --- /dev/null +++ b/marketplace/templates/users/welcome.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Welcome{% endblock %}

+{% endblock %} + +{% block content %} +Welcome to the marketplace! +{% endblock %} diff --git a/marketplace/users.py b/marketplace/users.py index 3d7e54f..4e9412c 100644 --- a/marketplace/users.py +++ b/marketplace/users.py @@ -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': @@ -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') \ No newline at end of file + 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')