安全测试之 CSRF 跨站点请求伪造
▌CSRF 攻击
CSRF 跨站点请求伪造 (Cross—Site Request Forgery):大概可以理解为攻击者盗用了你的身份,以你的名义在恶意网站发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,甚至于购买商品、转账等。
例如:Web A 为存在 CSRF 漏洞的网站,Web B 为攻击者构建的恶意网站,User C 为 Web A 网站的合法用户。
CSRF 攻击攻击原理及过程如下:
1.用户 C 打开浏览器,访问受信任网站 A,输入用户名和密码请求登录网站 A;
5.浏览器在接收到这些攻击性代码后,根据网站 B 的请求,在用户不知情的情况下携带 Cookie 信息,向网站 A 发出请求。网站 A 并不知道该请求其实是由 B 发起的,所以会根据用户 C 的 Cookie 信息以 C 的权限处理该请求,导致来自网站 B 的恶意代码被执行。
▌CSRF 漏洞检测
CSRF 漏洞检测工具的测试原理如下:使用 CSRFTester 进行测试时,首先需要抓取我们在浏览器中访问过的所有链接以及所有的表单等信息,然后通过在 CSRFTester 中修改相应的表单等信息,重新提交,这相当于一次伪造客户端请求。如果修改后的测试请求成功被网站服务器接受,则说明存在 CSRF 漏洞,当然这些工具也可以被用来进行 CSRF 攻击。
▌防御 CSRF 攻击
在以上 CSRF 攻击的第四步,该请求的 Referer 是指向黑客自己的网站 B 而不是网站 A。因此,要防御 CSRF 攻击,网站 A 只需要对于每一个请求验证其 Referer 值,如果是 A 网站的域名,则说明该请求是来自己的请求,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。
Referer 的优势与劣势:
a.简单易行,开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以,非常便捷。
#每个请求执行之前先执行before验证请求头中的referer
@app.before_request
def csrf_check_referer():
# 获取请求头中的referer,login页面不需要检查因为没有,只需要检查后续的跳转页面请求是否带referer字段
if request.path != "/login":
referer=request.referrer
print(referer)
if referer[:22] != "http://127.0.0.1:5000/":
return "page not found",404
b.Referer 的值是由浏览器提供的,并不能保证浏览器自身没有安全漏洞。对于某些浏览器,目前已经有一些方法可以篡改 Referer 值,黑客完全可以修改 Referer 值,这样就可以通过验证,从而进行 CSRF 攻击。
c.即便黑客无法篡改 Referer 值,因为 Referer 值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权,因此用户自己可以设置浏览器使其在发送请求时不再提供 Referer。当他们正常访问网站时,网站会因为请求没有 Referer 值而拒绝合法用户的访问。
(2) 在请求中添加 token 并验证
demo 代码如下:
app.py 需要设置秘钥,使用 form 表单验证模块 flask-wtf 对前端数据进行验证,包括前端传回的 csrf-token 的验证;
import os
from flask import Flask, request, render_template
#初始化application
from wtforms import ValidationError
from registerform import MyForm
app = Flask(__name__)
#设置秘钥
app.config["SECRET_KEY"] = os.urandom(10)
#每个请求执行之前先执行before验证请求头中的referer
@app.before_request
def csrf_check_referer():
# 获取请求头中的referer,login页面不需要检查因为没有,只需要检查后续的跳转页面请求是否带referer字段
if request.path != "/login":
referer=request.referrer
print(referer)
if referer[:22] != "http://127.0.0.1:5000/":
return "page not found",404
@app.route("/")
def index():
return {"user":"lei"}
@app.route('/login',methods=['GET','POST'])
def login():
#使用flask-wtf表单验证,会自动加上CSRF攻击的验证
form = MyForm()
if request.method == "GET":
return render_template('index.html', form=form)
if form.validate_on_submit():
return 'OK'
else:
raise ValidationError(message=form.errors)
if __name__ == '__main__':
app.run(debug=True)
form 表单验证模块:
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired,Length
class MyForm(FlaskForm):
验证前端传入的name不能为空,长度2-10;
name = StringField('name', validators=[DataRequired("姓名不能为空"),Length(2,10,"名字长度错误")])
html 文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>flask</title>
</head>
<body>
<div style="color:red">欢迎~~~</div>
<form method="POST" action="/login">
#请求页面时会加上csrf_token,提交表单时会带上这个隐藏的token以和参数一起提交给服务器;
{{ form.csrf_token }}
<lable>姓名:</lable>
<input type="text" name="name" value="">
<input type="submit" value="submit">
</form>
<a href="http://127.0.0.1:5000">点我</a>
</body>
</html>
1.在客户端向后端请求界面数据的时候,需要在 Form 表单中添加一个隐藏的的字段,值是 csrf_token。
2.在用户点击提交的时候,会带上这个值向后台发起请求;
3.后端接受到请求,会比较值是否正确,如果没取到或者不正确,代表不是正常的请求,不执行下一步操作。
如下用 postman 直接发起请求,则会报 csrf_token 错误;
行业活动推荐
↙↙阅读原文可查看相关链接,并与作者交流。