Tornado-应用安全
Cookie
普通Cookie
-
设置
-
原型
1
2
3
4
5
6
7
8
9
10self.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
)
-
参数
name
:cookie名value
:cookie值domain
:提交cookie时匹配的域名path
:提交cookie时匹配的路径expires
:cookie的有效期,可以是时间戳整数、时间元组、datetime类型;为UTC时间。expires_days
:cookie的有效期天数,优先级低于expires
-
示例
1
2
3
4
5
6class PCookieHandler(RequestHandler):
def get(self, *args, **kwargs):
# 设置
self.set_cookie("sunck", "good")
# self.set_header("Set-Cookie", "kaige=nice; Path=/")
self.write("ok")
-
-
原理
设置cookie实际上是通过设置header的Set-Cookie来实现的
1
self.set_header("Set-Cookie", "kaige=nice; Path=/")
-
获取
-
原型
1
self.get_cookie(self, name: str, default: str = None)
-
参数
name
:要获取的cookie的名称default
:如果名为name的cookie不存在,则返回default的值
-
示例
1
2
3
4
5
6class GetPCookieHandler(RequestHandler):
def get(self, *args, **kwargs):
# 获取cookie
cookie = self.get_cookie("sunck", "未登录")
print("cookie =", cookie)
self.write("ok")
-
-
清除
-
self.clear_cookie()
:删除名为name,并同时匹配domain和path的cookie1
self.clear_cookie(self, name: str, path: str = "/", domain: str = None)
-
self.clear_all_cookies()
:删除同时匹配path和domain的所有cookie1
self.clear_all_cookies(self, path: str = "/", domain: str = None)
-
⚠️注意:执行清除cookie操作后,并不是立即删除浏览器端的cookie,而是给cookie值设置空,并改变其有限期限为失效。真正删除cookie是在关闭浏览器时浏览器自己去清理的。
-
示例
1
2
3
4
5
6
7class ClearPCookieHandler(RequestHandler):
def get(self, *args, **kwargs):
# 清除一个cookie
# self.clear_cookie("sunck")
# 清除所有cookie
self.clear_all_cookies()
self.write("ok")
-
安全Cookie
-
概述
- Cookie是存在客户端浏览器的数据,很容易被篡改
- Tornado提供了一种对Cookie进行简易加密方式来防止Cookie被恶意篡改
-
设置
-
需要为应用配置一个用来给Cookie进行混淆加密的秘钥
1
2
3
4
5
6settings = {
'debug': True,
'static_path': os.path.join(BASE_DIRS, 'static'),
'template_path': os.path.join(BASE_DIRS, 'templates'),
'cookie_secret': 'wtsaTrAfTBuZTx5f9yBhX8ZVZ479HknqnSMKKAmau+0=',
}'cookie_secret': 'wtsaTrAfTBuZTx5f9yBhX8ZVZ479HknqnSMKKAmau+0='
-
生成一个秘钥
1
2
3
4
5import base64
import uuid
secret = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
print(secret)1
b'wtsaTrAfTBuZTx5f9yBhX8ZVZ479HknqnSMKKAmau+0='
-
设置安全Cookie
-
原型
1
2
3
4
5
6
7
8self.set_secure_cookie(
self,
name: str,
value: Union[str, bytes],
expires_days: int = 30,
version: int = None,
**kwargs: Any
) -
作用:设置一个带有签名和时间戳的cookie,防止cookie被伪造
-
-
示例
1
2
3
4class SCookieHandler(RequestHandler):
def get(self, *args, **kwargs):
self.set_secure_cookie("zhangmanyu", "nice")
self.write("ok") -
查看浏览器端的cookie值
1
"2|1:0|10:1596855751|10:zhangmanyu|8:bmljZQ==|3d8c5a68a3ca2b7aacd073e317bf5c664ab53e6a67b8dda6f7b9b3a3c307a073"
说明
|1:0|
:1表示:
后边有多少位;0表示正真的数值;|
表示分隔符- 安全cookie的版本,默认使用版本2
- 默认为0
- 时间戳
- cookie名
- base64编码的cookie值
- 签名值,不带长度说明
-
-
获取
-
原型
1
2
3
4
5
6
7self.get_secure_cookie(
self,
name: str,
value: str = None,
max_age_days: int = 31,
min_version: int = None,
)- 如果cookie存在且验证通过,返回cookie值,反则返回None
- max_age_days不同于expires_days,expires_days设置浏览器中cookie的有效时间;而max_age_days是过滤安全cookie的时间戳
-
示例
1
2
3
4
5class GetSCookieHandler(RequestHandler):
def get(self, *args, **kwargs):
scookie = self.get_secure_cookie("zhangmanyu")
print("scookie =", scookie)
self.write("ok")
-
-
⚠️注意
- 也不是完全安全的,一定程度上增加了破解cookie的难度
- 以后cookie不要存储一些敏感性的数据
XSRF
跨站请求伪造
-
cookie计数
1
2
3
4
5
6
7
8
9
10class CookieNumHandler(RequestHandler):
def get(self, *args, **kwargs):
count = self.get_cookie("count", None)
if not count:
count = 1
else:
count = int(count)
count += 1
self.set_cookie("count", str(count))
self.render('cookienum.html', count=count)1
2
3
4
5
6
7
8
9
10
<html lang="en">
<head>
<meta charset="UTF-8">
<title>cookie计数</title>
</head>
<body>
<h1>第{{ count }}访问</h1>
</body>
</html> -
跨站请求
1
2
3
4
5
6
7
8
9
10
11
<html lang="en">
<head>
<meta charset="utf-8">
<title>搞事情</title>
</head>
<body>
<img src="http://127.0.0.1:8848/cookienum">
<h1>去看看吧,我可能把你搞坏了!</h1>
</body>
</html> -
说明
- 当访问“搞事情”网站时,在我们不知情、为授权的情况下,“cookie计数器”网站cookie被使用,以至于“cookie计数器”网址认为是它自己调用Handler的逻辑
- 上一个程序使用的是GET方式模拟的攻击,为了防止这种攻击,一般对于相对安全的操作一般是不放在GET请求中,常常使用的是POST请求
XSRF保护
-
原理:同源策略,即:
- 协议相同
- 域名相同
- 端口号相同
-
图示
开启XSRF保护
1 | settings = { |
在配置文件
config.py
中添加'xsrf_cookies': True
应用
-
模板中应用
{% module xsrf_form_html() %}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSRF</title>
</head>
<body>
<form action="/postfile" method="post">
{% module xsrf_form_html() %}
姓名:<input type="text" name="username"/>
<hr/>
密码:<input type="password" name="passwd"/>
<hr/>
<input type="submit" value="登陆"/>
</form>
</body>
</html>- 作用
- 为浏览器设置了
_xsrf
的安全cookie,这个cookie在关闭浏览器后会失效 - 为模板表单添加了一个隐藏的域,名为
_xsrf
,值为_xsrf
这个cookie的值1
<input type="hidden" name="_xsrf" value="2|b4826962|86075754385b9af629ccee663023729a|1596862208">
- 为浏览器设置了
-
非模板中应用
- 手动设置
_xsrf
的cookie1
2
3
4
5class SetXSRFCookie(RequestHandler):
def get(self, *args, **kwargs):
# 设置_xsrf的cookie
self.xsrf_token
self.finish("Ok") - 第一种:基本上不会用
手动创建的input,并设置name属性值为_xsrf
,value属性值为名为_xsrf
的cookie的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSRF</title>
</head>
<body>
<form action="/postfile" method="post">
<input id="hi" type="hidden" name="_xsrf" value="">
姓名:<input type="text" name="username"/>
<hr/>
密码:<input type="password" name="passwd"/>
<hr/>
<input type="submit" value="登陆"/>
</form>
<script>
function getCookie(name) {
var cook = document.cookie.match("\\b" + name + "=([^;]*)\\b")
return cook ? cook[1] : undefined
}
console.log(getCookie('_xsrf'))
document.getElementById("hi").value = getCookie("_xsrf")
</script>
</body>
</html> - 第二种:
$.post
发起Ajax请求1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSRF</title>
<script type="text/javascript" charset="UTF-8" src="{{ static_url('js/jquery.min.js') }}"></script>
</head>
<body>
姓名:<input type="text" name="username"/>
<hr/>
密码:<input type="password" name="passwd"/>
<hr/>
<button onclick="login()">登陆</button>
<script>
function getCookie(name) {
var cook = document.cookie.match("\\b" + name + "=([^;]*)\\b")
return cook ? cook[1] : undefined
}
function login() {
// _xsrf=wertyu&username=sunck&passwd=23456
$.post("/postfile", "_xsrf=" + getCookie('_xsrf') + "&username=" + "sunck" + "&passwd=" + "123456789", function (data) {
alert("OK")
})
}
</script>
</body>
</html> - 第三种:
$.ajax
发起Ajax请求1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSRF</title>
<script type="text/javascript" charset="UTF-8" src="{{ static_url('js/jquery.min.js') }}"></script>
</head>
<body>
姓名:<input type="text" name="username"/>
<hr/>
密码:<input type="password" name="passwd"/>
<hr/>
<button onclick="login()">登陆</button>
<script>
function getCookie(name) {
var cook = document.cookie.match("\\b" + name + "=([^;]*)\\b")
return cook ? cook[1] : undefined
}
function login() {
data = {
"username": "sunck",
"passwd": "123456"
}
var datastr = JSON.stringify(data)
$.ajax({
url: "/postfile",
method: "POST",
data: datastr,
success: function (data) {
alert("OK")
},
headers: {
"X-XSRFToken": getCookie("_xsrf")
}
})
}
</script>
</body>
</html> - 问题
需要手动添加_xsrf
的cookie,需要在进入主页时就自动设置上_xsrf
的cookie1
2(r'/(.*)$', index.StaticFileHandler,
{"path": os.path.join(config.BASE_DIRS, "static/html"), "default_filename": "index.html"})1
2
3
4class StaticFileHandler(tornado.web.StaticFileHandler):
def __init__(self, *args, **kwargs):
super(StaticFileHandler, self).__init__(*args, **kwargs)
self.xsrf_token1
2
3
4
5
6
7
8
9
10
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<h1>这是主页</h1>
</body>
</html>static -> html -> index.html
- 手动设置
用户验证
- 指在收到用户请求后进行预先判断用户的认证状态(是否登录),若验证通过则正常处理,否则进如到登录界面
tornado.web.authenticated
装饰器,Tornado将确保这个方法的主体只有合法的用户才能调用get_current_user()
- 验证用户的逻辑应该写在该方法中,如果该方法返回的为True说明验证成功,否则验证失败
- 验证失败,请求会将访客重定向到配置中的
login_url
所指定的路由1
2
3
4
5
6
7
8settings = {
'debug': False,
'xsrf_cookies': True,
'login_url': '/login',
'static_path': os.path.join(BASE_DIRS, 'static'),
'template_path': os.path.join(BASE_DIRS, 'templates'),
'cookie_secret': 'wtsaTrAfTBuZTx5f9yBhX8ZVZ479HknqnSMKKAmau+0=',
}'login_url': '/login'
- 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="{{ url }}" method="post">
{% module xsrf_form_html() %}
姓名:<input type="text" name="username"/>
<hr/>
密码:<input type="password" name="passwd"/>
<hr/>
<input type="submit" value="登陆"/>
</form>
</body>
</html>templates -> login.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37class LoginHandler(RequestHandler):
def get(self, *args, **kwargs):
next = self.get_argument("next", "/")
url = "login?next=" + next
self.render("login.html", url=url)
def post(self, *args, **kwargs):
name = self.get_argument("username")
passwd = self.get_argument("passwd")
if name == "1" and passwd == "1":
next = self.get_argument("next", "/")
self.redirect(next + "?flag=logined")
else:
next = self.get_argument("next", "/")
self.redirect("/login?next=" + next)
class HomeHandler(RequestHandler):
def get_current_user(self):
# /home
flag = self.get_argument("flag", None)
return flag
def get(self, *args, **kwargs):
self.render("home.html")
class CartHandler(RequestHandler):
def get_current_user(self):
# /home
flag = self.get_argument("flag", None)
return flag
def get(self, *args, **kwargs):
self.render("cart.html")index.py