diff --git a/README.md b/README.md index 980b293..54ba978 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/marketplace/models.py b/marketplace/models.py index d160222..7915f95 100644 --- a/marketplace/models.py +++ b/marketplace/models.py @@ -1,3 +1,5 @@ +from secrets import token_urlsafe + import bcrypt from sqlalchemy.ext.hybrid import hybrid_property @@ -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): @@ -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' diff --git a/marketplace/users.py b/marketplace/users.py index 3dd49fe..b8abe48 100644 --- a/marketplace/users.py +++ b/marketplace/users.py @@ -1,5 +1,3 @@ -from base64 import b64encode - import bcrypt from flask import Blueprint, request, render_template, session, url_for, redirect @@ -33,7 +31,8 @@ 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." @@ -41,8 +40,11 @@ def login(): @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')) diff --git a/migrations/versions/0697265799f2_.py b/migrations/versions/0697265799f2_.py new file mode 100644 index 0000000..9aed1d0 --- /dev/null +++ b/migrations/versions/0697265799f2_.py @@ -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 ###