def login(): """ The login view of this application. Return a login page or login a user if authentication is successful. """ # Redirect if the current user is logged in if g.user and g.user.is_authenticated: next = get_local_redirect() return redirect(next or url_for('index')) # Prepare the login form form = LoginForm() # If the form is filled if form.validate_on_submit(): # Part 1 # A terrible way to implement login # The administrator of Great Bank is very confident in his application and has published the # source code on the internet. That is what you are reading right now. You can thus see how # the code is implemented and try to find a vulnerability. # The code below execute a request to the database where it tries to match a username and # the password provided by the user in the form. # Unfortunately, he did not escape the values from the user in any way and you can thus # control the query by inserting the right values. # Try to input some code in the login page and see how the code changes in the console. # Can you bypass the password check? # See below for the answer. query = "SELECT * FROM user WHERE username = '******' AND password = '******'" # Print the current query into the console for you to see print(query) result = list(db.engine.execute(query)) if result: for row in result: login_user(User.get_by_username(row['username']), remember=form.remember_me.data) flash('Logged in!', 'success') return form.redirect('index') else: flash('Wrong username or password.', 'error') # By exploiting the fact that none of the code is escaped properly, we can control the SQL # statement and basically, make it always true. By using for instance: # Example of SQL injection: 1' OR '1'='1 # we can make the database say: "Give me 'admin' where password is '1' OR 1=1" # As 1=1 is always true, the second part of the predicament is always true and we bypass the # authentication entirely. You successfully logged in as an admin! # This is one of the simplest form of SQL injection and it is unfortunately still too # common today. # Part 2 # # The admin is pissed as you managed to login as him and take some money from the bank. He # discovered the flaw and patched it. (Please comment the whole code above and uncomment the # code below.) # You can no longer login using the magic password as he now verifies the user exist first # and then check the password. # See below for the next part. ### Patched version of the login: # print("Username: {username}".format(username=form.username.data)) # print("Password: {password}".format(password=form.password.data)) # # user = User.get_by_username(form.username.data) # if user and user.check_password(form.password.data): # login_user(user, remember=form.remember_me.data) # flash('Logged in!', 'success') # return form.redirect('index') # else: # flash('Wrong username or password.', 'error') return render_template('login.html', form=form)