Web 安全之 CSRF 攻击

简介: Web 安全之 CSRF 攻击

CSRF 攻击中文为跨站请求伪造,全拼为 Cross-site request forgery,也常被称为 XSRF 攻击。虽然 CSRF 攻击没有 XSS 攻击更常见,但其破坏性同样不容小觑。

攻击原理

CSRF 攻击是攻击者利用目标网站对用户浏览器的信任,对已登录用户执行非用户本意的操作。

比如用户登录了 A 网站,记录用户登录的会话信息就会以 Cookie 的形式保存在浏览器中,此时用户访问攻击者提供的 B 网站,B 网站就可以在用户不知情的情况下,伪造一个请求发送给 A 网站的服务器,浏览器会自动在这个请求中携带 Cookie,A 网站的服务器收到这个请求,以为这个请求是 A 网站已登录的用户自己发送过来的,于是执行相应的操作(如发邮件、转账等),就此实现攻击。

攻击示例

这里我用 Flask 编写两个简单的 Web 应用来作为 CSRF 攻击的示例。执行以下代码之前你需要使用 pip install flask 的方式安装 Flask

首先编写一个带有转账功能的 Web 应用,代码如下:

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
# demo/app.py
from flask import Flask, request, make_response, render_template, redirect
app = Flask(__name__)
@app.route('/')
defindex():
"""首页"""
    session_id = request.cookies.get('session_id')
if session_id:
        is_login = '已登录'
else:
        is_login = '未登录'
return render_template('index.html', is_login=is_login)
@app.route('/login')
deflogin():
"""登录"""
    response = make_response(redirect('/'))
    response.set_cookie('session_id', '025fcb1587eea0a25493653dfcecc6f1')
return response
@app.route('/transfer', methods=['POST'])
deftransfer():
"""转账"""
    session_id = request.cookies.get('session_id')
    target_user = request.form.get('target_user')
if session_id:
returnf'给用户{target_user}转账成功'
else:
return'用户未登录'
if __name__ == '__main__':
    app.run(port=5000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- demo/templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<metacharset="UTF-8">
<title>Index</title>
</head>
<body>
是否登录:{{ is_login }}
<formaction="/transfer"method="post">
    接收用户:<inputtype="text"name="target_user">
<button>转账</button>
</form>
</body>
</html>

这个 Web 应用中包含了三个视图函数,分别为首页、登录和转账,程序监听 5000 端口。

应用逻辑比较简单,index 视图函数通过判断请求中是否存在键为 session_idCookie 信息来确认用户是否登录。login 视图函数用来进行登录操作,其实就是给用户浏览器设置一个键为 session_idCookie 信息,登录成功后重定向到首页。transfer 视图函数用来实现转账功能,只接受 POST 请求,判断用户是否登录的逻辑与 index 视图函数相同,已登录返回转账成功信息,未登录则返回 用户未登录

应用只包含了一个 index.html 页面,登录后即可通过 form 表单的形式进行转账操作。

虽然这个 Web 应用看起来比较傻,但足以演示 CSRF 攻击过程。启动 Flask 应用,使用浏览器访问 http://127.0.0.1:5000/ 即可看到首页内容。

此时在接收用户的输入框中输入 张三,点击 转账 按钮,会提示 用户未登录

 

那么我们可以先访问 http://127.0.0.1:5000/login 进行登录操作。

 

登录成功后,再次进行转账操作即可成功。

 

接下来站在攻击者的角度,编写一个能够对这个转账应用进行攻击的 Web 应用,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# demo_csrf/app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
defindex():
return render_template('index.html')
if __name__ == '__main__':
    app.run(port=5001)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- demo_csrf/templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<metacharset="UTF-8">
<title>CSRF</title>
</head>
<body>
Hello World!
<formaction="http://127.0.0.1:5000/transfer"method="post">
<inputhiddentype="text"name="target_user"value="张三">
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>

这个 Web 应用中只包含一个首页视图函数,程序监听 5001 端口。

index.html 页面中,有一句 Hello World! 和一个 form 表单。由于 form 表单中的 input 输入框带有 hidden 属性,所以不会显示在页面中。页面中还有一个 <script> 标签,包含一句 JavaScript 代码 document.forms[0].submit();,其作用是提交 form 表单。

启动这个具有攻击效果的 Flask 应用,在刚刚已经成功登录到 http://127.0.0.1:5000/ 的浏览器中访问 http://192.168.3.14:5001/(为了模拟不同域名,所以这里使用了内网 IP,如果你想在自己的电脑上运行程序则需要改为自己电脑的内网 IP)。

 

页面显示的内容一瞬间就从 Hello World! 变成了 给用户张三转账成功,此时攻击已经完成。

打开浏览器的开发者工具查看请求过程,可以发现浏览器共发送了两个请求。

第一个请求即为在浏览器地址栏中访问 http://192.168.3.14:5001/,第二个请求则是通过 JavaScript 代码自动提交的 form 表单请求 http://127.0.0.1:5000/transfer,并且浏览器自动携带了 Cookie 信息。

总结一下攻击流程:

  1. 用户访问目标网站 http://127.0.0.1:5000/ 并进行登录。
  2. 用户访问攻击者网站 http://192.168.3.14:5001/
  3. 攻击者网站 http://192.168.3.14:5001/ 通过 JavaScript 代码向 http://127.0.0.1:5000/transfer 发送转账请求,此时浏览器会自动携带 http://127.0.0.1:5000/ 下的 Cookie 信息。
  4. http://127.0.0.1:5000/ 网站的后台服务器接收到攻击者发送过来的请求,通过 Cookie 信息验证用户身份通过,完成转账操作。

当然在现实场景,用户不会主动去访问攻击者提供的网站。但攻击者会想尽各种办法诱导用户去访问。比如攻击者在给目标网站的留言中携带网站链接、在给用户发送的邮件中携带链接等。

CSRF 攻击不止这一种方式,还可以通过 a标签的 href 属性、img 标签的 src 属性等。

防范方法

CSRF 攻击的本质是在攻击者网站发送的跨站请求中,浏览器自动携带了 Cookie 信息,而浏览器和网站服务器不具备验证这个请求是否为用户自愿发起的请求的能力。

所以防范方法就是在访问敏感数据请求时,要求用户浏览器提供不保存在 Cookie 中的,并且攻击者无法伪造的数据作为校验,这样 CSRF 攻击就无法完成。

根据以上分析修改这个带有转账功能的 Web 应用,代码如下:

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
42
43
44
45
46
47
# demo/app.py
import uuid
from flask import Flask, request, make_response, render_template, redirect
app = Flask(__name__)
csrftoken = uuid.uuid4().hex
@app.route('/')
defindex():
"""首页"""
    session_id = request.cookies.get('session_id')
if session_id:
        is_login = '已登录'
else:
        is_login = '未登录'
return render_template('index.html', is_login=is_login, csrftoken=csrftoken)
@app.route('/login')
deflogin():
"""登录"""
    response = make_response(redirect('/'))
    response.set_cookie('session_id', '025fcb1587eea0a25493653dfcecc6f1')
return response
@app.route('/transfer', methods=['POST'])
deftransfer():
"""转账"""
    session_id = request.cookies.get('session_id')
    target_user = request.form.get('target_user')
    token = request.form.get('csrftoken')
if session_id:
if csrftoken != token:
return'csrftoken验证失败'
returnf'给用户{target_user}转账成功'
else:
return'用户未登录'
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- demo/templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<metacharset="UTF-8">
<title>Index</title>
</head>
<body>
是否登录:{{ is_login }}
<formaction="/transfer"method="post">
    接收用户:<inputtype="text"name="target_user">
<inputhiddentype="text"value="{{ csrftoken }}">
<button>转账</button>
</form>
</body>
</html>

Flask 应用启动时,生成一个随机的 csrftoken 用来校验请求发起方。当用户访问首页,这个 csrftoken 会被传递到 index.html 中,并且作为 form 表单中一个 input 标签的 value 值。为了不影响页面原有数据展示,将这个 input 标签隐藏了起来(带有 hidden 属性)。当提交 form 表单时 csrftoken 会随表单数据一同被提交。在 transfer 视图函数中从表单数据获取 csrftoken 值,对比内存中已有的 csrftoken 值,如果两者相同则说明该请求是用户发起的,如果两者不同则说明请求是攻击者伪造的跨站请求。因为攻击者无法在自己的网站中构造出这个 csrftoken 值,所以攻击者网站提交的 form 表单数据中无法携带有效的 csrftoken,这样就能够有效的防范 CSRF 攻击。

现在再次尝试访问 http://192.168.3.14:5001/,攻击将无法完成。

可以看到,即使用户已经登录了,但 csrftoken 校验失败,依然无法转账成功。

虽然我们的 Web 应用有效的防范了 CSRF 攻击,但这个实现并不完美。在生产环境的项目中,我们还需要考虑 csrftoken 的时效性,使其在一定时间内过期,并且使用过一次的 csrftoken 也要做失效处理。不过别担心,这些功能早有人帮我们实现了。在 Flask 项目中我们可以通过第三方扩展来实现,如 Flask-WTF 内置的 CSRFProtectDjango 框架甚至默认集成了此功能。

实际上还有很多其他方法可以用来防范 CSRF 攻击,如同源检测,服务端检查 HTTP 请求头中的 OriginReferer 字段,再比如使用 CookieSameSite 属性来限制第三方 Cookie

每种防范机制都有各自的优缺点,使用哪种机制应该结合 Web 应用程序情况综合考量,一般来说我们这里举例用的 csrftoken 机制是比较通用的做法。

此外,我们还需要注意程序中的 XSS 漏洞。如果网站中存在 XSS 漏洞,攻击者同样可以通过 XSS 攻击的方式先拿到 csrftoken,然后再进行 CSRF 攻击。

当然,所谓安全都是相对的,并没有绝对的安全,对敏感数据的操作,多加一层校验,虽然看似麻烦,但总好过亡羊补牢。

相关文章
|
3月前
|
安全 网络协议 NoSQL
Web渗透-常见的端口及对其的攻击思路
本文介绍了常见网络服务端口及其安全风险,涵盖FTP、SSH、Telnet、SMTP、DNS、HTTP、SMB、数据库及远程桌面等20余个端口,涉及弱口令爆破、信息泄露、未授权访问、缓冲区溢出等典型漏洞,适用于网络安全学习与渗透测试参考。
712 59
|
6月前
|
Web App开发 监控 安全
OSS客户端签名直传实践:Web端安全上传TB级文件方案(含STS临时授权)
本文深入解析了客户端直传技术,涵盖架构设计、安全机制、性能优化等方面。通过STS临时凭证与分片上传实现高效安全的文件传输,显著降低服务端负载与上传耗时,提升系统稳定性与用户体验。
601 2
|
2月前
|
安全 Linux iOS开发
Burp Suite Professional 2025.10 发布 - Web 应用安全、测试和扫描
Burp Suite Professional 2025.10 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
253 3
|
3月前
|
安全 程序员 数据库连接
web渗透-CSRF漏洞
CSRF(跨站请求伪造)是一种常见的Web安全漏洞,攻击者通过伪造用户请求,诱使其在已登录状态下执行非意愿操作。本文介绍CSRF原理、分类(站外与站内)、DVWA靶场搭建及防御措施,如同源策略与Token验证,提升安全防护意识。
452 0
web渗透-CSRF漏洞
|
3月前
|
安全 Linux iOS开发
Burp Suite Professional 2025.9 发布 - Web 应用安全、测试和扫描
Burp Suite Professional 2025.9 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
375 0
Burp Suite Professional 2025.9 发布 - Web 应用安全、测试和扫描
|
5月前
|
安全 Linux iOS开发
Burp Suite Professional 2025.7 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
Burp Suite Professional 2025.7 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
511 0
Burp Suite Professional 2025.7 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
|
5月前
|
存储 安全 JavaScript
Web Storage有哪些安全风险?
Web Storage有哪些安全风险?
|
8月前
|
安全 Linux API
Burp Suite Professional 2025.4 发布 - Web 应用安全、测试和扫描
Burp Suite Professional 2025.4 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
399 6
Burp Suite Professional 2025.4 发布 - Web 应用安全、测试和扫描
|
2月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
244 5
|
6月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!