flask imitates csrf attack and protection
Schematic diagram of csrf attack:
Now we have a webA file as follows
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Sign in</title> </head> <body> <h1>I'm a website A,Login page</h1> <form method="post"> <label>user name:</label><input type="text" name="username" placeholder="enter one user name"><br/> <label>password:</label><input type="password" name="password" placeholder="Please input a password"><br/> <input type="submit" value="Sign in"> </form> </body> </html>
transfer.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>transfer accounts</title> </head> <body> <h1>I'm a website A,Transfer page</h1> <form method="post"> <label>Account:</label><input type="text" name="to_account" placeholder="Please enter opposite account"><br/> <label>amount of money:</label><input type="number" name="money" placeholder="Please enter the transfer amount"><br/> <input type="submit" value="transfer accounts"> </form> </body> </html>
webA.py
from flask import Flask, render_template, make_response from flask import redirect from flask import request from flask import url_for app = Flask(__name__) @app.route('/', methods=["POST", "GET"]) def index(): if request.method == "POST": # Get the parameters submitted in the form username = request.form.get("username") password = request.form.get("password") if not all([username, password]): print('Parameter error') else: print(username, password) if username == 'laowang' and password == '1234': # Maintain the status and set the user name to cookie Indicates successful login response = redirect(url_for('transfer')) response.set_cookie('username', username) return response else: print('Password error') return render_template('login.html') @app.route('/transfer', methods=["POST", "GET"]) def transfer(): # from cookie Get user name from username = request.cookies.get('username', None) # If you don't get it, it means you haven't logged in if not username: return redirect(url_for('index')) if request.method == "POST": to_account = request.form.get("to_account") money = request.form.get("money") print('Pretend to perform the transfer operation and transfer the money of the currently logged in user to the specified account') return 'transfer accounts %s Yuan Dao %s success' % (money, to_account) # Render conversion page response = make_response(render_template('transfer.html')) return response if __name__ == '__main__': app.run(debug=True, port=9000)
The webB file is as follows
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>I'm a website B</h1> <form method="post" action="http://127.0.0.1:9000/transfer"> <input type="hidden" name="to_account" value="999999"> <input type="hidden" name="money" value="190000"> <input type="submit" value="Click to receive coupons"> </form> </body> </html>
webB.py
from flask import Flask from flask import render_template app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') if __name__ == '__main__': app.run(debug=True, port=8000)
Now let's start weba Py file enter user name and password
You can see that the transfer is successful
webA should not turn off Webb that keeps users logged in and then starts malicious attacks py
Click
We can see that we directly asked him to jump to the user's transfer interface and the transfer was successful. What's the secret
<form method="post" action="http://127.0.0.1:9000/transfer"> <input type="hidden" name="to_account" value="999999"> <input type="hidden" name="money" value="190000"> <input type="submit" value="Click to receive coupons"> </form>
In fact, website b directly jumps to the transfer interface of website a with the form form action, and then sets the type of the input tag to hidden. When we click the button, he submits the transfer amount and the user to the server (because the default browser will bring his cookie when accessing and inputting the same ip and port as website a)
How to solve this problem? (just change webA)
Step 1:
Use base64 and os to create a random string of 48
def random_csrf_token(): return bytes.decode(base64.b64encode(os.urandom(48)))
Step 2:
Then, the function will be called and the generated string will be responded, and the generated 48 random number will also be written into the cookie
csrf_token = random_csrf_token() # Render conversion page response = make_response(render_template('transfer.html', csrf_token=csrf_token))response.set_cookie("csrf_token", csrf_token)
Step 3:
Add a hidden tag to the transfer interface to receive the generated 48 bit random number
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
Step 4:
Finally, check the 48 bit random number obtained from the front-end page and cookie respectively
if request.method == "POST": token1 = request.form.get("csrf_token") token2 = request.cookies.get("csrf_token") if not token1 == token2: return "climb"
Run webB again and you can see that the transfer has been unsuccessful
webA finished changing the code
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Sign in</title> </head> <body> <h1>I'm a website A,Login page</h1> <form method="post"> <label>user name:</label><input type="text" name="username" placeholder="enter one user name"><br/> <label>password:</label><input type="password" name="password" placeholder="Please input a password"><br/> <input type="submit" value="Sign in"> </form> </body> </html>
transfer.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>transfer accounts</title> </head> <body> <h1>I'm a website A,Transfer page</h1> <form method="post"> <input type="hidden" name="csrf_token" value="{{ scrf_token }}"> <label>Account:</label><input type="text" name="to_account" placeholder="Please enter opposite account"><br/> <label>amount of money:</label><input type="number" name="money" placeholder="Please enter the transfer amount"><br/> <input type="submit" value="transfer accounts"> </form> </body> </html>
webA.py
from flask import Flask, render_template, make_response from flask import redirect from flask import request from flask import url_for import base64 import os app = Flask(__name__) def random_csrf_token(): return bytes.decode(base64.b64encode(os.urandom(48))) @app.route('/', methods=["POST", "GET"]) def index(): if request.method == "POST": # Get the parameters submitted in the form username = request.form.get("username") password = request.form.get("password") if not all([username, password]): print('Parameter error') else: print(username, password) if username == 'laowang' and password == '1234': # Maintain the status and set the user name to cookie Indicates successful login response = redirect(url_for('transfer')) response.set_cookie('username', username) return response else: print('Password error') return render_template('login.html') @app.route('/transfer', methods=["POST", "GET"]) def transfer(): # from cookie Get user name from username = request.cookies.get('username', None) # If you don't get it, it means you haven't logged in if not username: return redirect(url_for('index')) if request.method == "POST": token1 = request.form.get("csrf_token") token2 = request.cookies.get("csrf_token") if not token1 == token2: return "climb" to_account = request.form.get("to_account") money = request.form.get("money") print('Pretend to perform the transfer operation and transfer the money of the currently logged in user to the specified account') return 'transfer accounts %s Yuan Dao %s success' % (money, to_account) csrf_token = random_csrf_token() # Render conversion page response = make_response(render_template('transfer.html', csrf_token=csrf_token)) response.set_cookie("csrf_token", csrf_token) return response if __name__ == '__main__': app.run(debug=True, port=9000)