Greg Todd
Greg Todd

Reputation: 11

Broken Access Control with JWT in Python

I'm currently working on a project to create a secure login with JWT and python. It's a simple bank account app that shows the username, date of birth and account balance. In my code, I'm still able to access other accounts by changing the URL account numbers assigned to other users such as www.domain.com/account/#.

If foo is account 16 and is logged in, I'm able to change the numbers to 17 allowing me to access bar's account presenting their info which is a big no no. I want to only be able to only see foo's own account information. I'm trying to check the JWT to make sure that foo's is the user and it is foo's account information before sending the data to the user.

The JWT that is passing to the function includes a username, and logged_in field (or claim) of the authenticated user. I'm trying to compare with the account that is being looked up in the database via the number in the URL. I'm also trying to make sure that if the username in the JWT does not match the username returned from the DB then to raise a "You do not have access" exception.

I've been beating my head against the wall to figure this out.

Payload

jwt_token = {
      "username": "foo",
      "logged_in": true
    }

Code

import jwt
import pymysql


class LoggedOutException(Exception):
    '''Raising a LoggedOutException will redirect the user to the login screen
    in the app.
    '''
    pass

def account_lookup(account_id, jwt_token):
    try:
        token = jwt.decode(jwt_token, 'secret', algorithm='HS256')
    except Exception as e:
        raise LoggedOutException('User is not logged in')

    if "logged_in" in token.keys() and token["logged_in"] == True:
        conn = pymysql.connect(
            host='mysql',
            port=3306,
            user='root',
            passwd='letmein',
            db='BankApp'
        )
        cursor = conn.cursor()

        statement = "SELECT username FROM tbl_user WHERE id = " + account_id + ";"
        cursor.execute(statement)
        username_results = cursor.fetchone()

        if username_results:
            username = username_results[0]

            statement = "SELECT balance, dob FROM tbl_account WHERE user_id = " + account_id + ";"
            cursor.execute(statement)
            account_results = cursor.fetchone()

            conn.commit()
            cursor.close()
            conn.close()

            return {
                'balance': account_results[0],
                'dob': account_results[1],
                'username': username
            }
        else:
            raise Exception('Account not found')
    else:
        raise LoggedOutException('User is not logged in')

Upvotes: 1

Views: 1927

Answers (2)

CUTLER
CUTLER

Reputation: 71

if username_results and username_results[0] == token['username']:

Upvotes: 2

Matus Dubrava
Matus Dubrava

Reputation: 14462

I'm also trying to make sure that if the username in the JWT does not match the username returned from the DB then to raise a "You do not have access" exception.

Where? There is no such logic in your code. You are using the JWT only to check whether the user is logged in or not based on the logged_in value which doesn't help much because only logged in users should hold a valid JWT token, therefore the presence of such token itself represents the logged-in status of a user.

The logic should be something like when a user logs in, you generate a JWT token for that user and when that user logs out, you invalidate that token (of course, you might want to consider implementing token age and refreshing as well later on).

You have 2 SQL queries in your code

statement = "SELECT username FROM tbl_user WHERE id = " + account_id + ";"
statement = "SELECT balance, dob FROM tbl_account WHERE user_id = " + account_id + ";"

but they depend only on the value of account_id which is not part of the JWT (and why are you using account_id name for the variable if it is clearly a user_id? It is just confusing).

The easy solution would be to place account_id (or to name it more properly user_id) into JWT instead of username which should not be there anyway (in my opinion), and use that value to fetch data from DB.

Upvotes: 1

Related Questions