tornado learning notes day06 application security

Keywords: JQuery Python Attribute Javascript

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")
Published 193 original articles, won praise 43, visited 10000+
Private letter follow

Posted by zulfer on Tue, 14 Jan 2020 20:27:59 -0800