Application Security
cookie
Ordinary cookie
What do we usually have in our user list
When you are shopping, join the shopping cart and let you log in. After you log in, how does he know you are logged in
The value of token is random and exists in the cookie
Set up
- Prototype: how to set cookie s
def set_cookie( self, name: str, value: Union[str, bytes], domain: str = None, expires: Union[float, Tuple, datetime.datetime] = None, path: str = "/", expires_days: int = None, **kwargs: Any) -> None:
- parameter
- Name: the name of the cookie we set
- Value: the value of the cookie
- Domain: the matching domain name when submitting the cookie, that is, which ip is taken from it
- Path: path matching when cookie is submitted
- expires: set the validity period of the cookie, which can be a time integer, a time tuple, a datetime type, and UTC time
- expires_days: set the validity period (days) of the cookie, but its priority is lower than expires
- Example:
principle
Setting cookies is actually achieved by setting the header's set cookie
Obtain
We need to get it after setting. Why do we need to get it? Because we will carry this cookie when we send a request in the future
- Archetype:
def get_cookie(self, name: str, default: str = None) -> Optional[str]:
- Parameters:
- Name: the name of the cookie to get
- Default: if the obtained cookie named name does not exist, go to the default value
- Example:
cookie = self.get_cookie("suck","Not logged in") print(cookie) self.write("getcookie page info tornado!") self.write(cookie)
Eliminate
- Archetype:
def clear_cookie(self, name: str, path: str = "/", domain: str = None) -> None:
- Effect:
- Delete the cookie named name and match the cookie of domain and path at the same time
- Well, it's possible that the name is the same. Then you need to match path and domain
- Be careful:
- After the cookie is deleted, it is not deleted directly,
- Instead, set the cookie content to null and empty, and delete the delay time,
- The real deletion is done by the browser itself. If the browser finds that it is useless, it will delete it for him. It depends on the browser
- There is also a prototype:
def clear_all_cookies(self, path: str = "/", domain: str = None) -> None:
- Function: delete all cookie s matching path and domain at the same time
- Example:
# Clear cookie class ClearPCookieHandler(RequestHandler): def get(self): # After deleting the cookie, he can't decide this time # Next time it's deleted # Clear a cookie self.clear_cookie("suck") # Clear all cookie s self.clear_all_cookies() self.write("ClearPCookieHandler page info tornado!")
Safety cookie
Summary:
Cookies are data stored in the client browser, which are easy to be tampered with and not very secure
tornado provides a simple encryption method for cookie s to prevent them from being tampered with maliciously
Set up:
- The key to obfuscate encryption that needs to be configured for the application
- Generate a secret key:
We need to use a library base64. All you need to know is that it's encrypted. It's a unique identifier. You don't need to worry about the others, because you can't care about the others if you want to
md5 is not safe now. Now some websites can crack md5 online
We can generate a uuid by entering the following code in the python console
base64.b64encode(uuid.uuid4().bytes+uuid.uuid4().bytes) b'j94bbx0zSY6yYkCgawwJ1bzyM4jzDUuKtLyPC/MMmZA='
We need to configure it in config.py
The contents are as follows
settings = { # The name of key is not an easy one. It's a good one, # It's like upfile doesn't exist. You write it for nothing 'template_path': os.path.join(BASE_DIR, "templates"), 'static_path': os.path.join(BASE_DIR, "static"), "debug": True, # "autoreload" : True "cookie_secret":"j94bbx0zSY6yYkCgawwJ1bzyM4jzDUuKtLyPC/MMmZA=", # It's said that it takes ten billion years to use up }
And then I'll show you a way. His prototype is as follows
def set_secure_cookie( self, name: str, value: Union[str, bytes], expires_days: int = 30, version: int = None, **kwargs: Any ) -> None:
Function: set a cookie with signature and time stamp to prevent the cookie from being forged
It's actually written like this
# Safety cookie class SCookieHandler(RequestHandler): def get(self): self.set_secure_cookie("victor","nice") self.write("SCookieHandler page info tornado!")
Then let's check the value of the cookie in the browser
Set-Cookie: victor="2|1:0|10:1575853257|6:victor|8:bmljZQ ==|ac4c19c8598fc7c7d07c1484f07a0cc8e1a1adb022d084bcc3d3d4 75129fab63"; expires=Wed, 08 Jan 2020 01:00:57 GMT; Path=/
Now this value, you can't see what the content is
Explain:
- Security cookie version, default 2
- The default is 0.
- time stamp
- cookie name
- base64cookie encoded value
- Signature value (without length description)
Obtain:
Just throw the prototype right here
def get_secure_cookie( self, name: str, value: str = None, max_age_days: int = 31, min_version: int = None, ) -> Optional[bytes]:
And I wrote that
# Get security cookie class GetSCookieHandler(RequestHandler): def get(self): sc = self.get_secure_cookie("victor") print(sc) self.write("getSCookieHandler page info tornado!") self.write(sc)
If the cookie passes the verification, the value of the cookie will be returned. Otherwise, None will be returned
Max age days is different from the effective time of cookie s in the browser set by exports days
Max age days is the time stamp for filtering security cookie s. The default is 31 days
Be careful:
- This safe cookie is not absolutely safe, but it increases the difficulty of cracking to some extent
- Do not store sensitive data in cookie s in the future
XSRF
Cross Station Request Forgery code
Example:
# cookie count class CookieNumHandler(RequestHandler): def get(self): count = self.get_cookie("count",None) if count: # Nth visit count = str(int(count)+1) pass else: # First visit # Set cookie count = '0' self.set_cookie("count",count) self.render("cookienum.html",count = count)
The page looks like this:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Do things</title> </head> <body> <h1>The first{ { count } }Secondary visit</h1> <p>Business website,I'll see if I can break it!!!</p> <div> <img src="http://127.0.0.1:8080/cookienum" alt=""> </div> </body> </html>
When we visit the website, when we don't know, and without authorization, the cookie in the cookie counter website is used, which causes the cookie counter website to think that it calls Handler logic by itself
The last program uses GET mode to simulate the attack. In order to prevent this attack, the relatively safe operation is generally not included in the GET request
We often use POST requests
XSRF protection
Same source: same domain name, same protocol, same port
Only homology gives him access
Open protection
Add in configuration
"xsrf_cookies":True
And don't forget to straighten it out here
super(Application, self).__init__( handlers, # Template path template_path=settings["template_path"], # Static path static_path=settings["static_path"], # cookie signature cookie_secret=settings["cookie_secret"], xsrf_cookies=settings["xsrf_cookies"], )
Now that the protection is open, no one wants to visit
So what are we going to do?
How can we make our own visit
additional
The keys and values in the configuration file don't need to be written one by one. Just * * break them up
# Are you stupid? It's just a matter of * * here super(Application, self).__init__( handlers,**settings )
application
Apply in template
Add a sentence like this in the form form form, and the template can be used
{ % module xsrf_form_html() % }
That's it
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Post page</title> </head> <body> <form action="/postfile" method="post"> { % module xsrf_form_html() % } //Full name:<input type="text" name="username"> <hr/> //Password:<input type="password" name="password"> <hr/> <input type="submit" value="Sign in"> </form> </body> </html>
Effect:
- A secure cookie of_xsrf is set for the browser. This cookie will fail when the browser is closed
- A hidden output is added to the template. The hidden field is named_xsrf and the value is the value of the cookie
- A hidden field in the form named The value is... , but it's still not absolutely safe
Apply in non template
Manually set the cookie of ﹣ xsrf
'''Set up XsrfCookieHandler''' class SetXsrfCookieHandler(RequestHandler): def get(self): # Set a cookie for_xsrf self.xsrf_token self.finish("OK")
First kind
- Instead of writing that line of code in the page, we can write a script JS code, which can manually add a hidden input
- Manually create the input and set the attribute value of name to be ﹐ xsrf, and the attribute value of value to be the value of cookie named ﹐ xsrf
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Post page</title> </head> <body> <form action="/postfile" method="post"> <input id="hi" type="hidden" name="_xsrf" value=""> Full name:<input type="text" name="username"> <hr/> Password:<input type="password" name="password"> <hr/> <input type="submit" value="Sign in"> </form> <script> function getCookie(name) { // What's written here doesn't matter what he means var cook = document.cookie.match("\\b" + name + "=([^;]*)\\b"); // We're going to return cookies, not cookies // If so, I'll take his first value, which is the cookie value return cook ? cook[1] : undefined } // Get cookie gc = getCookie("_xsrf"); console.log(gc); document.getElementById("hi").value = gc; </script> </body> </html>
Second kinds
To initiate an ajax request:
Let's remove this form, and then click this button in js to start the ajax request
We haven't learned the native ajax, so we need to use jQuery!
The specific code is as follows:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Post page</title> <!-- Import jQuery--> <script type="text/javascript" charset="utf-8" src="{{static_url('js/jquery.min.js')}}"></script> </head> <body> <input id="hi" type="hidden" name="_xsrf" value=""> Full name:<input type="text" name="username"> <hr/> Password:<input type="password" name="password"> <hr/> <input type="submit" value="Sign in"> <button id="btn" onclick="login()">Sign in</button> <script> function getCookie(name) { // What's written here doesn't matter what he means var cook = document.cookie.match("\\b" + name + "=([^;]*)\\b"); // We are going to return a cookie, not a cook // If so, I'll take his first value, which is the cookie value return cook ? cook[1] : undefined } function login() { // There are many ways to make ajax requests // There are two ways to send a post request. One is this $.post("/postfile", "_xsrf=" + getCookie("_xsrf") + "&username=" + "victor" + "&passwd=" + "123456", // Callback function after successful request function (data) { alert("ok\\(^o^)/~"); alert(data); } ); // TODO actually gets the values of username and passwd through DOM element, // TODO, because it's not the point /** * Here is mainly to take this xsrf protection, otherwise this can't give you a response */ } </script> </body> </html>
Third kinds
In fact, this is the most commonly used way. This $. Post can only send post requests, and has few parameters or controls
$. ajax is the most NB way
Come on! Just code it
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Post page</title> <!-- Import jQuery--> <script type="text/javascript" charset="utf-8" src="{{static_url('js/jquery.min.js')}}"></script> </head> <body> <input id="hi" type="hidden" name="_xsrf" value=""> //Full name:<input type="text" name="username"> <hr/> //Password:<input type="password" name="password"> <hr/> <input type="submit" value="Sign in"> <button id="btn" onclick="login()">Sign in</button> <script> function getCookie(name) { // What's written here doesn't matter what he means var cook = document.cookie.match("\\b" + name + "=([^;]*)\\b"); // We are going to return a cookie, not a cook // If so, I'll take his first value, which is the cookie value return cook ? cook[1] : undefined } function login() { // TODO is the same thing. Data can be obtained dynamically through DOM data = { "username": "victor", "passwd": "123456", }; // We need to turn our data into a string // This method is from JS, not from Python or jQuery var data_str = JSON.stringify(data); // How to write this ajax? There is only one parameter in it, which is a dictionary $.ajax({ url: "/postfile", // He can send whatever you write and what he is method: "POST", data: data_str, // You can see that the corresponding value of a key in this dictionary is a function, // Although there is a dictionary type parameter outside, there are callback functions success: function () { alert("ok!!!") }, // It's impossible to succeed here, because how can I succeed without my xsrf // If we want to initiate a request, we need to add a header, which is the request header headers:{ // Come here and write it // This value is set to the previous cookie value "X-XSRFToken": getCookie("_xsrf"), }, }) // In the future, it is recommended to use this to make ajax requests more powerful } </script> </body> </html>
problem
In fact, handsome people don't write in the first way
Need manual Set token
Solution: in fact, when we enter a website, do we usually enter the homepage first? When you enter the homepage, you write the xsrf in it
First, we add a static default page to the home page
( r"/(.*)$", # We can't use the system, so we inherit it, # tornado.web.StaticFileHandler, # Let's write this for ourselves, and inherit it from the system index.MyStaticFileHandler, { "path": os.path.join(BASE_DIR, "static/html"), "default_filename": "index.html" } ),
Then rewrite StaticFileHandler
from tornado.web import StaticFileHandler # Set static default class MyStaticFileHandler(StaticFileHandler): # When we use him, we just need to rewrite his init def __init__(self,*args,**kwargs): super(MyStaticFileHandler,self).__init__(*args,**kwargs) self.xsrf_token
supplement
- _xsrf is underlined. We think of it as private
- __xsrf is really private
User verification
concept
After receiving the user's request, perform a pre judgment on the user's authentication status (login or not). If the authentication passes the normal processing, otherwise enter the login interface and roll off the login
tornado.web.authenticate decorator
tornado will ensure that the body of this method (what it modifies) can only be called by legitimate users
get_current_user
Let's define a method to get current user ()
Function: verify the user's logic, which should be written in this method,
If the method returns True, the validation is successful, otherwise the validation fails
Validation failure: visitors are redirected to the established route in the configuration (login interface)
You need to write the configuration location in the configuration file config.py, because you don't have to jump to the login interface!
"login_url":"/login",
So if you don't log in, he'll kick you into that page
And I'll go straight to the code
'''First of all, the routing is like this ''' # Login interface tornado.web.url(r"/login", index.LoginHandler,name='login'), # home page (r"/home", index.HomeHandler), # An anonymous normal page for testing (r"/cart", index.CartHandler), ( r"/(.*)$", # We can't use the system, so we inherit it, # tornado.web.StaticFileHandler, # Let's write this for ourselves, and inherit it from the system index.MyStaticFileHandler, { "path": os.path.join(BASE_DIR, "static/html"), "default_filename": "index.html" } ),
'''Login page''' class LoginHandler(RequestHandler): def get(self): # When I log in, I need to know where I got in next = self.get_argument("next","/") url = "login" + "?next=" + next self.render("login.html", url=url) def post(self): ''' //We don't have to worry about get or post name = self.get_body_argument("username") passwd = self.get_body_argument("passwd") ''' # I'll write directly name = self.get_argument("username") passwd = self.get_argument("passwd") # TODO, I still write down the account and password here if name == "victor" and passwd == "123456": # This thing represents the page from which to jump. It's finished # If it's all right, I'll send it to you next = self.get_argument("next", "/") # After "/" is the default value # Redirect away from you, but not just redirect # We have to add a sign self.redirect(next + "?flag=logined") else: next = self.get_argument("next", "/") ''' # Validation failed, # The reverse resolution of the route is very high # Wrong password input. Remember where they came from several times ''' log = self.reverse_url("login")+"?next="+next # I still have to redirect self.redirect(log)
''' This is the main page ''' class HomeHandler(RequestHandler): def get_current_user(self): ''' # Return True for successful verification, otherwise it will be cool # return False # Then we can judge by the previous flag # There is also a default value to be set here, otherwise it is # WARNING:tornado.general:400 GET /home (127.0.0.1): Missing argument flag ''' flag = self.get_argument("flag", None) # If you can get the flag, return true # If you don't get married, you will go back to False return flag @tornado.web.authenticated def get(self): self.render("home.html")
''' this cart It's a normal page''' class CartHandler(RequestHandler): def get_current_user(self): return self.get_argument("flag", None) @tornado.web.authenticated def get(self): self.render("cart.html")