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
42 changes: 9 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,17 @@
# Secure Coding with Python.

## Chapter 5: Broken De-Authentication
### Test
To test this we are going to make use of probably the most essential tool that web security professionals use:
[Burp Suite](https://portswigger.net/burp). For the purposes of this course we are only going to use the community
edition.
### Fix
In order to avoid sessions to be used even after the user has logged out, we should use a random unique value in the
session that we could revoke on logout, invalidating the session.

1. Please download and install Burp Community Edition.
2. Run Burp Suite. It will give you some options for creating or opening a project.
3. Select `Temporary project` as all we need and the only one allowed for the community edition.
4. Click `Next`.
5. Select `Use Burp defaults` on the configuration page.
6. Click `Start Burp`.
7. Go to the `Proxy` tab on Burp.
8. Select the `Options` sub-tab.
9. Configure your browser to use the proxy settings from `Proxy Listeners`. **Note**: Chrome will ignore proxy request on localhost, the use of Firefox is recommended.
10. Go to the `Intercept` sub-tab.
11. Make sure `Intercept is off` (it's usually on by default, we will enable it later.)
12. Navigate to [http://localhost:5000/user/login](http://localhost:5000/user/login)
13. Login with the credentials of the user you created.
14. On `Burp` go to the sub-tab `HTTP history`.
15. Find the `/user/welcome` request.
16. On the bottom half under `Request` -> `Raw` you can see the cookie being set like `Cookie: session=eyJsb2dnZWRfaW4iOnRydWV9.XXnIiQ.U46jDCKmFDSH-b4_0FiyiBhNMqQ`
17. Copy the cookie value.
18. On the web app click `Logout`.
19. In `Proxy` `Intercept` turn `Intercept is on`.
20. Navigate to [http://localhost:5000/user/welcome](http://localhost:5000/user/welcome)
21. In `Proxy` `Intercept` `Params` change the cookie value to the one we copied on step 17.
22. Click `Forward`.
Since we are adding a new column to our user model we need to update our Database with:
```bash
> $ flask db migrate
> $ flask db upgrade
```

As you can see even after the user logged out, we were able to log in using the session value captured previously
successfully performing a session hijacking attack.

**Note**: At the moment of this writing the latest Burp Suite Community Edition version is v2.1.02


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

## Index
### 1. Vulnerable Components
Expand Down
8 changes: 8 additions & 0 deletions marketplace/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from secrets import token_urlsafe

import bcrypt

from sqlalchemy.ext.hybrid import hybrid_property
Expand All @@ -10,6 +12,7 @@ class User(db.Model):
full_name = db.Column(db.String(100))
email = db.Column(db.String(100), unique=True)
_password = db.Column('password', db.String(100))
session_key = db.Column('session_key', db.String(50), unique=True)

@hybrid_property
def password(self):
Expand All @@ -20,6 +23,11 @@ def password(self, plaintext):
salt = bcrypt.gensalt(rounds=12)
self._password = bcrypt.hashpw(plaintext.encode(), salt).decode()

def new_session_key(self):
key = token_urlsafe()
self.session_key = key
return self.session_key

class Listing(db.Model):
__tablename__ = 'listings'

Expand Down
12 changes: 7 additions & 5 deletions marketplace/users.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from base64 import b64encode

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

Expand Down Expand Up @@ -33,16 +31,20 @@ def login():
if u:
password = request.form['password']
if bcrypt.checkpw(password.encode(), u.password.encode()):
session['logged_in'] = True
session['key'] = u.new_session_key()
db.session.commit()
return redirect(url_for('users.welcome'))
error = "Invalid email or password."

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


@bp.route('/logout', methods=('GET',))
def logout():
session['logged_in'] = False
@auth
def logout(user):
session.pop('key')
user.new_session_key()
db.session.commit()
return redirect(url_for('users.login'))


Expand Down
46 changes: 46 additions & 0 deletions migrations/versions/0697265799f2_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""empty message

Revision ID: 0697265799f2
Revises: 67168ab4efaa
Create Date: 2019-09-11 23:49:27.582749

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '0697265799f2'
down_revision = '67168ab4efaa'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('listings', 'description',
existing_type=sa.VARCHAR(length=500),
nullable=True)
op.alter_column('listings', 'title',
existing_type=sa.VARCHAR(length=128),
nullable=True)
op.add_column('users', sa.Column('session_key', sa.String(length=50), nullable=True))
op.create_unique_constraint(None, 'users', ['session_key'])
op.create_unique_constraint(None, 'users', ['email'])
op.drop_column('users', 'verified')
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('verified', sa.BOOLEAN(), autoincrement=False, nullable=True))
op.drop_constraint(None, 'users', type_='unique')
op.drop_constraint(None, 'users', type_='unique')
op.drop_column('users', 'session_key')
op.alter_column('listings', 'title',
existing_type=sa.VARCHAR(length=128),
nullable=False)
op.alter_column('listings', 'description',
existing_type=sa.VARCHAR(length=500),
nullable=False)
# ### end Alembic commands ###