Laravel - CSRF - 学习/实践

1.应用场景

了解Laravel csrf的使用以及工作原理,以及更多的web攻击方式以及相应的防护措施。

2.学习/操作

1. 文档阅读

CSRF Protection - Laravel - The PHP Framework For Web Artisans

https://xueyuanjun.com/post/21464

http://ningxiaofa.top/blog/pjwgtbue5f336e767a8da-0  //安全 - 常见web攻击

网络/Network - 网络安全 - 常见web攻击与防护_穿素白衫的中少年的博客-CSDN博客

2. 整理输出

2.1 介绍

跨站请求伪造(CSRF)是一种通过伪装授权用户的请求来攻击授信网站的恶意漏洞。

2.2 实践

问题: csrf token是每次请求都会变的吗?

另外,如果不是web应用,而是App应用,或者Restful Api还有必要使用csrf吗?

为什么App / Resful Api 如果不需要csrf token,为什么不需要?

重点:先完全搞明白CSRF是什么和防护手段

第一个问题:「csrf token是每次请求都会变的吗」

首先, 不一定,csrf根据会话不同而不同,而且value是随机产生的.

有些场景下,为了安全,必须携带才可以.

表单 -- 因为本身支持跨域,携带不安全因素

如: 表单的POST/PUT请求「写请求」请求体「请求头中携带不行」中必须带上

_token:

sBNSS85yhIaopQdy8yk9zSQBk6GFO22YAclKTvRG

, 至于读请求,GET / OPTIONS是不需要的  「其实这还是存在风险,建议严格按照Restful 风格进行开发

Laravel官方文档写的

CSRF Protection - Laravel - The PHP Framework For Web Artisans

Anytime you define a "POST", "PUT", "PATCH", or "DELETE" HTML form in your application, you should include a hidden CSRF _token field in the form so that the CSRF protection middleware can validate the request. For convenience, you may use the @csrf Blade directive to generate the hidden token input field:

Postman测试如下

如果隔一段时间还是用之前的_token的value是通不过csrf验证的

因为是新的会话

深究如下

结合yii / laravel / thnikphp / raw php

Yii:

vendor/yiisoft/yii2/web/Request.php

Laravel:

注:CSRF 中间件只只作用于 routes/web.php 中定义的路由,因为该文件下的路由分配了 web 中间件组,而 VerifyCsrfToken 位于 web 中间件组中。

对于表单

http://laravel7.test:8080/form_without_csrf_token

http://laravel7.test:8080/form_with_csrf_token

前者点击 提交 按钮会返回报错

后者点击 提交 按钮正常响应

认真看就会知道,前者表单的请求体中没有携带参数「

_token:

sBNSS85yhIaopQdy8yk9zSQBk6GFO22YAclKTvRG

」,后者表单的请求体中携带了参数。

但是返回的响应体中并没有表单字段上没有_token字段,也就导致点击提交按钮,发出的POST请求,请求体中没有携带_token,那么app/Http/Middleware/VerifyCsrfToken.php中间件不会通过,就会返回报错.

插入

增加了疑问

在上一次请求中,获取表单的请求中,后端在响应头中都返回了

Set-Cookie: XSRF-TOKEN=eyJpdiI6ImhDUExEaTgvT0ZPUkYyc1VVaDA4bVE9PSIsInZhbHVlIjoiSlVGYUxqV

kNNWDlWc2g0Zlp5Z3ZNcExRaHFqR3o0SEg3QWRNMEVtWFNVd0owQkkwUHY3VUt4OG95Y

mxYUlVxdHhlVldtVDFEdURiYzY5WCtRN01iUU5IbXI1Y2NuYnBETUhMNHRyT0NlZjkwZG9PMT

VERk91eTA4Y2tzcXF4dEYiLCJtYWMiOiI0Yjg5OTQ4ZDQzOTAzZjIwYzE3NTc5NTc4M2ExYTE4

M2U4Yjg0ZTU2YTBjNzBiM2YwYmMwMjIyNTNiNWQwNzQ2In0%3D;


http://laravel.test:8080/

刷新三次,都会变。

上面是base64编码的value

解码之后 --- 发现依然没有共同的部分

{"iv":"CT1kbOPlve2vAspSkTvHtQ==","value":"dIz4e3wB+IHHU83847ZS1RoHlZAVSoTyF8f+

eXmJVFTcvPh0AG+yR2mer3hd0I+DF+iOAaAd6uBwlXf6gkDGbbVE4pI8JzZ6O51wMrHtmD

jno6QEaEiRlL8fDSWzo3ph","mac":"adbd184bb8d416c278fc1674ff538c35f34bd020af5e595

1d712feee5b761543"}


{"iv":"mkmU2PULMTJrXRjkhaI0zg==","value":"IkTSnlakUGpdcLELRtRMBn1eCRb/0Srnr/z

GDMbH7DDPuohZaQtVKXHUbuxZp/yKw7UvROzr32GL2wAOCtEH6sQHh8EVfoUgf83lV

JIiH08sngAi4XQvrFZviaMeTZCv","mac":"9f1faf30af972b42c1e44d8fdaa4db8acb572975b

305bc3f12fa40e11d6c8eac"}

{"iv":"1LyjR7ImE/ng2mNYiSQzDw==","value":"O/MIqnq4eRVrFd1A6Y9Qje5d1g+YMGml

G3DizfI3jpCdSrgdwIY5XyNWDMyQ3bO2VhWoasBhvceDzl43pK2kVahgrgP2OK4gN0ZS

57HQk1Kome/uQ7vY+L2rohlMc297","mac":"25b8b5d6d11d4925b8d8fe4e59db38bf212b

f532d0b7e4716233886ec8222f7e"}

格式化

这是干嘛的?跟表单请求体中的_token的关系?TBD

看过laravel的文档可以初步知道,这是Laravel额外添加的方式.

X-XSRF-Token

Laravel 还会将 CSRF 令牌保存到名为 XSRF-TOKEN 的 Cookie 中,你可以使用该 Cookie 值来设置 X-XSRF-TOKEN 请求头。

一些 JavaScript 框架,比如 Angular 和 Axios,会为你自动进行上述设置,基本上你不太需要手动设置这个值。

更多,见下面的 发现

发现

form表单中的_csrf 的value存放在session文件中,也就是session中

Laravel默认采用file存储session,而且是使用自己实现session的方式

存储位置: storage/framework/sessions

开始实践

实践之前

先删除laravel所有已有的session文件

1. 刷新网址, 得到一个form表单的_csrf 字段和value

http://laravel7.test:8080/form_with_csrf_token

2. 查看laravel生成的session文件

可以看到session文件内容如下,其中_token 和 url都存储了下来 -- 是先存储到session,然后返回给前端响应

a:3:{s:6:"_token";s:40:"oOF6LmWFsj70KV0SN4TBjlUIy6RV7fLbQYfLHPr6";s:9:"_previous";a:1:{s:3:"url";s:46:"http://laravel7.test:8080/form_with_csrf_token";}s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}

然后多次刷新url,form表单中的_csrf 的value和session文件都保持不变。

但是,在新开一个无痕窗口,输入上面的url,发现,form表单中的_csrf 的value改变了,session文件增加了一个。

同时发现:

如果先是访问

http://laravel7.test:8080/hello_from_form

后端新产生session文件,文件内容为:

a:2:{s:6:"_token";s:40:"e7GZEBwy7QQ2cwevZbTwRqzYioK8MEnBJoGV7xtU";s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}

后继续访问

http://laravel7.test:8080/form_without_csrf_token

后端对应的session文件,文件内容更为:

a:3:{s:6:"_token";s:40:"e7GZEBwy7QQ2cwevZbTwRqzYioK8MEnBJoGV7xtU";s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}s:9:"_previous";a:1:{s:3:"url";s:49:"http://laravel7.test:8080/form_without_csrf_token";}}

后继续访问

http://laravel7.test:8080/form_with_csrf_token

后端对应的session文件,文件内容更为:

a:3:{s:6:"_token";s:40:"e7GZEBwy7QQ2cwevZbTwRqzYioK8MEnBJoGV7xtU";s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}s:9:"_previous";a:1:{s:3:"url";s:46:"http://laravel7.test:8080/form_with_csrf_token";}}

所以,可以知道,form表单中的_csrf 的value 是和 session挂钩的,存储在session中,后端根据前端传递过来的session_id和_csrf, 找到session文件,取出_csrf的value和前端传递过来的_csrf token的value进行比对,如果缺失或者比对不上,后端就报错,避免安全问题产生,而且session文件中的url应是只记录上一次/最后一次的网址.

经过实践得到结果: 必须同时要携带laravel_session,才可以通过csrf验证,但是通常情况下,浏览器同时携带所有的cookies信息,当然这可以设置.

代码层面分析确认:TBD

下面是实践的截图,当不携带laravel_session时,会出现报错.

问题2: 如果不是web应用,而是App应用,或者Restful Api还有必要使用csrf吗?原因呢

请教大家 app 接口需要防护 csrf 吗? · Ruby China

上面有大家的讨论

首先,指明这里的App应用特指Mobil App

简洁说下答案和原因

移动App应用,不需要防护csrf。--- 接口完全不能做、没办法做 CSRF 防护

首先原生移动App跟浏览器是不同的,前者不支持Cookie,而且csrf是借助于cookies 验证进行未授权访问,发起的攻击。

csrf是跨域安全问题,关键字是跨域, 是浏览器方面的问题。

csrf 是一种依赖 web 浏览器的、被混淆过的代理人攻击(deputy attack)。

至于Restful Api

同样如果不是浏览器方面的问题,就跟移动App应用一样

如果是web应用,而且没有基于cookie进行认证/验证,也不需要防护csrf攻击。

python - Do CSRF attacks apply to API's? - Stack Overflow 有类似的提问, 结果是 Rest 风格的 api 项目 如果不依赖 cookies 做安全验证的话, 则不需要预防 CSRF.

CSRF attacks rely on cookies being implicitly sent with all requests to a particular domain. If your API endpoints do not allow cookie-based authentication, you should be good.

CSRF 攻击关键在于 cookie,如果 cookie 里不含登陆令牌,你把登录令牌放到其它 header 里就没问题。因为 CSRF 所利用的 form 和四个特殊 tag 都无法添加 header。现代应用的 API 不接受 form 提交,都是 json 风格的,现代的 web 浏览器都具备 CSP, samesite 等防范机制。

CSRF 攻击排名过去十年下降了两位。

具体看这个解释,如何不用 CSRF Token:Web 安全指导 - XSS and CSRF

至于

移动应用认证的方式,是另外的话题

最简单的方式

app 用 token 认证身份就够了。也就是实现一个完整的或者简化的 oauth2 认证。

access_token 放参数或者头部

实现思路 -- 待思考

1、APP和服务器接口先约定好一个密钥;
2、登录之后服务器接口返回一个Token;
3、APP在之后需要验证的请求中都要加上这个Token,以及进行签名。

签名方式其实跟很多支付接口一样,把所有的参数加上约定好的密钥进行Sha1或者MD5加密,得到签名字符串,传到服务器接口后再进行验证。

安全问题:
我们会在参数里边加一个timestamp字段,获取当前的时间戳,类似:1485159939.454,一并放进参数列表并签名。


服务器接口会检测时间戳不能误差超出2分钟,然后会在redis记录这个时间戳(2分钟后自动删除),用来判断重复请求。
这样就算不是https,被抓包了,重复请求就会报错。

仅供参考。

2.3 额外测试

1). 使用postman/浏览器进行表单请求

code:

Route::get('/form', function(){

    return csrf_field();

    return '<form method="POST" action="/csrf">

            ' . csrf_field() . '

            ...

            </form>';

});


 

Route::post('/csrf', function(Request $request){

    return $request->all();

});

截图: 

Postman:

Postman 连续请求三次
<input type="hidden" name="_token" value="xtf41lrJQsoAPr7j62EIhB8mZVvHMNckrZuNyZI8">
<input type="hidden" name="_token" value="xtf41lrJQsoAPr7j62EIhB8mZVvHMNckrZuNyZI8">
<input type="hidden" name="_token" value="xtf41lrJQsoAPr7j62EIhB8mZVvHMNckrZuNyZI8">

Chrome:

Chrome 连续请求三次
<input type="hidden" name="_token" value="Q6Tx1KHWwwpyK4AJKRgo28GIcL1FaidsXXN03q5e">
<input type="hidden" name="_token" value="Q6Tx1KHWwwpyK4AJKRgo28GIcL1FaidsXXN03q5e">
<input type="hidden" name="_token" value="Q6Tx1KHWwwpyK4AJKRgo28GIcL1FaidsXXN03q5e">

综上可以看到:

不论使用Postman还是Chrome, 连续请求三次, 返回的结果是相同的 「即value值是不变的, 但是至于是否是一段时间内不变,TBD

但是postman与Chrome返回的value并不是相同的, 即csrf_token跟访问会话有关. 

但是, 在经过一段时间后, 就会变化为新的一个csrf_token

关键点:

个人理解

会话的定义: 不同的设备「mac/windows/iOS/Android/ipad等」,用户代理「chrome/firefox/postman」, 

或者,相同的设备,但是cookie/session不同,也是不同的会话.

上面说的有点表面,因为他们有个前提是默认会话信息「cookie/session」不会共享「-- 限制多设备登陆,或许就是利用这个原理,TBD」

其实本质上,就是一句话,http本身无状态,状态通过cookie/session来维护,

如果请求/响应没有cookie/session的信息,那么laravel或者其他开发语言/框架,就会认为这是一个新的会话/新生产一个会话。反过来,如果cookie/session信息共享,在不同设备上,浏览器上,使用那么不会产生新的会话。

laravel中应是这样来做代码定义的。

2). 如果post 请求,想正常请求,不通过后端屏蔽,仅仅为了一时间的测试.

可以先使用get请求获取csrf_token值

然后添加quey stringf方式 _token=pk57g76kuM3Toce8HFqpmRB6auTB9Q1b458ZpTFI

或者

请求体中form-data 或者 x-www-form-urlencoded「这个才是平时使用的form表单」 中添加:

_token:pk57g76kuM3Toce8HFqpmRB6auTB9Q1b458ZpTFI

正常响应.

后续补充

...

3.问题/补充

1. 关于前端传递重复相同的参数,网络是否会真的全部传输,后端是怎么处理的?

curl --location --request POST 'http://laravel7.test:8080/hello_from_form' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: XSRF-TOKEN=eyJpdiI6InpVb0JRamlrVlBXK3IrVlZpSkxmanc9PSIsInZhbHVlIjo

iMEExbGVRV05qVW5PTWFsVUsxSjF4bUJwRUdweTl0eU5ZaXB6QTBNTXM1SkhXelhNSURsU

TErYWJJamxlNkdWRGR1N2VOdVFTVGRPWFpYd0hIU0poQmtJY0RiUUs3M1p3a3FCRFdjMHB

0ZDlnazZKTW5lWHM3aStJWmc1T3FjYWQiLCJtYWMiOiJjZTZiYzNkNGEyMmFkYmMyMDE0ZT

Q4OGQ0OGJmYWNhOTY5MjgyZjYxMGVmZjUyM2VhNWFjNDk0NzkyZmExODdmIn0%3D;

laravel_session=eyJpdiI6ImlWdHpLVjhsUGRWcFErY08zSHhTYWc9PSIsInZhbHVlIjoiRXZaUHo2

VlBLdStWNmVUNU1pWU5jdUtIcHBpZ002QjJtY2F6b2hyWkpxV0NUclFkOXEwa2Vtd1B2eUFLN

3FZRSt4Y1lzREpPOE44TktQQW1rY3VKYzUyMG1VL2NhbS9hNnd2eEplM1A4Z3VsMkdKVlVZZ

HpZdjk5ZlpsTG1PY3oiLCJtYWMiOiI1M2Q3NDllNWQ2MzVkMWQ2MDIwOTdjZDcyODg0YTlhNz

c2NjdmNzM4ZGRiYTdkMjMxZGRlZDAwNzkwODQwN2RhIn0%3D' \
--data-urlencode '_token=bjJiyxJfwQppeHHMVPDdSbiA5iO4RCXbTLGPSxCO' \
--data-urlencode '_token=2wArUYbWzVzTu7FSHo4I1AFZJ6eyXwkdISYGIJLA'

可以看到,上面你是重复发送_token

两个_token

一个_token

可以看到请求体中的大小减少到原来的一半,前端是传输到了后端。

只不过后端语言会你进行覆盖,如,PHP会获取后者的value。

4.参考

参见阅读文档列表

后续补充

...

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值