2
\$\begingroup\$

I'm trying to determine I missed something here since python isn't my native language.

  1. Python app starts
  2. Python creates a cert.pem and key.pem running this command pem openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365
  3. Python sets the flask secret key to a per instance generated uuid
  4. Flask has the ssl context set to the cert.pem and key.pem just generated

User connects to running flask app accepts the self signed cert and goes to log in. Their credentials are sent to the server using basic authentication

              $.ajax({
                   url: '/',
                   method:'POST',
                   headers:{
                       "authorization": "Basic " + btoa($("#usernameinput").val() + ":" + $("#password").val())
                   },
                   success: function(res, textStatus, data) {
                       redirect = data.getResponseHeader('location')
                       if (redirect != null) {
                           window.location.href = redirect;
                       } 
                   }
               })

The server then begins the authentication process.

First it attempts to find the user in the db using a parameterized query on the user's username.

If a row is found the user row is returned. Inside that row there is a hashed password and a salt. I then take the password the user typed in and I encrypt it using sha512 and the salt from the user row in the db. If the hashed password from the db and the password provided by the user hashed with the salt from the db match, then I confirm the user is authenticated.

   def log_user_in(username, password):
        user = database_access.get_user_by_username(username)
        
        if(user == None):
            return 0
        else:
            testPasswordHash = salt_password(password,user["Salt"])
            passwordsMatch = testPasswordHash == user["PasswordHash"]
            if(passwordsMatch == True):
                return 1
            else:
                return 0

If a user is authenticated, using the session module from flask, I set the session['user'] to the username


@app.route("/", methods=['GET', 'POST']) 
def home(): 
    if request.method == 'GET':
        if(check_logged_in()):
            return redirect(url_for('dashboard'))
        elif(useraccess.admin_user_exists() == True):
            return render_template('login.html')
        else:
            return render_template('createadminuser.html', title="Create Admin User")

    elif request.method == 'POST':
        auth = request.authorization
        if auth:
            result = useraccess.log_user_in(auth.username, auth.password)
            if result == 0:
                return redirect('/')
            elif result == 1:
                response = jsonify()
                session["user"] = auth.username
                response.headers['location'] = "/dashboard"
                response.autocorrect_location_header = False
                return response
            else:
                return redirect('/')
        else:
            return "Unauthorized"

@app.route("/dashboard", methods=['GET']) 
def dashboard():
    if(check_logged_in()):
        return render_template('dashboard.html')
    else:
        return redirect('/')
    

Finally when a user attempts to visit a section of the site, I first check if they have a valid session

def check_logged_in():    
    return "user" in session

Does this seem right or am I missing something?

\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$

I had a general concern regarding plaintext password being transmitted over the network, but I guess HTTPS/SSL exists for that purpose anyway. However, to prevent memory-scanning attacks, you should not hold the raw password in memory any longer than needed. That is, auth.password should be either immediately replaced (overwritten) with the salted version, or just zeroed out after the salting has been done. Immutable strings in Python make cleaning out the password annoying, so it might be better from a security standpoint to hash the password before transmission client-side, then only ever work with the hash (yeah, that would include salting the hash of the password as well).

Aside from this security concern, log_user_in would really benefit from having a bool return instead of int.

import ctypes
def log_user_in(username, password):
    user = database_access.get_user_by_username(username)
    
    if not user:
        return False

    testPasswordHash = salt_password(password, user["Salt"])
    # this works to zero out the password variable,
    # but if it is ever accessed again after this, Python will segfault.
    # Needless to say this isn't the best way of doing this in Python, 
    # if someone knows a better way of doing this, let me know!
    ctypes.memset(id(password), 0, len(password))

    return testPasswordHash == user["PasswordHash"]
   

You do not, in general, need parentheses around if conditions in Python!

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your feedback. I really appreciate it. I'm going to leave the question unanswered so we can get a few more opinions on it. Thanks again +1 \$\endgroup\$ Aug 4, 2022 at 21:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.