AngelaDan 2019-01-18
相信前端开发工程师对CSRF(Cross-site request forgery)跨站请求伪造这个概念都非常熟悉,有的时候也简写成XSRF,是一种对网站的恶意利用。
尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装成受信任用户的请求来利用受信任的网站。
CSRF攻击的防御方式有多种,最简单最易实现的一种思路就是在客户端向服务器发起的请求中放入攻击者无法伪造的信息,并且该信息没有存储于 cookie 之中。技术上来说,当客户端向服务器发起请求执行一些敏感操作之前(比如用HTTP post实现的转账,扣款等功能),服务器端随机产生一个token,返回给客户端。客户端接下来的操作,必须在HTTP请求中以参数的形式把这个服务器端颁发的token带上。同时服务器端在实现给客户端分配token的同时,也要加入一个token校验机制。如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这个token我们一般称为CSRF token。
讲了这么多,是为了引入本文想要讨论的话题。假设我想用jMeter测试一个OOdata服务创建Service Ticket的性能。因为创建功能不像读操作,执行之后会对系统产生持久化影响(Persistence side-effect), 因此服务器端的实现加入了CSRF token的校验。这就是说,如果我们直接用jMeter构造并发的HTTP post请求,是没有办法完成测试的,这些请求因为没有包含CSRF token,会被服务器端直接拒绝掉。
根据前面描述的CSRF攻防原理,CSRF token是服务器端随机生成的,客户端无法用任何技术进行伪造,因为为了测试接口HTTP post操作进行Service Ticket的创建,我们必须构造一个它的前置HTTP GET请求,专门用于得到服务器返回的CSRF token,然后再构造真正用于性能测试的HTTP POST请求,把第一步GET请求获得的CSRF token附到POST请求的头部中去。
本文介绍在jMeter里如何维护并配置这种具有依赖关系的一组请求。
当然如果您不喜欢用jMeter,想自己写代码实现,也是可以的。可以参考我放在github上的Java代码实现。
用jMeter的好处是不需要编程,通过简单的配置就能实现这个性能测试需求,一般没有开发背景的测试人员也能独立完成。
First let us have a look how JMeter could archive the same without even one line of programming.
My project in JMeter is displayed with the following hierarchy. I have configured with “Number of 5 threads” in my thread group, so once executed, the response time of these 5 threads are displayed in result table together with average response time.
从下图能看出,因为拿CSRF token的HTTP GET在逻辑上必须先于实际需要测试性能的HTTP POST请求,这实际上构成了一个Transaction-事务,所以我使用jMeter里提供的Transaction Controller来管理。
(1) Since now one thread should cover both XSRF token fetch via HTTP get and Service request creation via HTTP post, so a transaction controller is necessary to include both request.
(2) Create the first HTTP request to fetch XSRF token. The setting could be found below: adding a http header field with name as
x-csrf-token and value as “fetch”:
在HTTP GET请求的头部加上一个名为x-csrf-token的字段,值赋成fetch。这样服务器接到这个请求,就知道这是客户端发起的CSRF token请求,于是服务器响应这个请求,把创建好的随机CSRF token通过HTTP response头部字段的方式返回给客户端。
下一个问题就是,服务器返回给客户端合法的CSRF token后,jMeter如何读取到这个token,并用于接下来的请求?
幸运的是,jMeter提供了正则表达式提取式,可以让我们很方便地从HTTP响应结构中提取出token来。
Create a Regular Expression Extractor to parse the XSRF token from response header and stored it to a variable named “jerrycsrftoken”.
下图构造了一个jMeter正则表达式提取器,工作于HTTP响应的头部字段,解析出的token值存储于变量jerrycsrftoken中。
Before you continue, please make sure that the XSRF token is correctly parsed from request header, which could be confirmed by printing it out in a debug sample:
这个请求构造完之后,我们先试着运行一次,确保在变量jerrycsrftoken里确实看到解析好的CSRF token。
(3) Create another HTTP request with type POST.
这时万事俱备,我们可以开始构造真正要进行性能测试的HTTP post,即Service Ticket的创建请求了。
请求的报文正文:
Just paste the following text to the tab “Body Data”:
--batch_1 Content-Type: multipart/mixed; boundary=changeset_1 --changeset_1 Content-Type: application/http Content-Transfer-Encoding: binary POST ServiceRequestCollection HTTP/1.1 Content-Length: 5000 Accept: application/json Content-Type: application/json { "ServicePriorityCode": "2", "Name": {"content": "Jerry Testing ticket creation via JMeter ${uuid} "}, "ServiceRequestDescription": [ { "Text": "Piston Rattling 1 - Generic OData Test Create", "TypeCode": "10004" }, { "Text": "Piston Rattling 2 - Generic OData Test Create", "TypeCode": "10007" } ] } --changeset_1-- --batch_1--
In the body text I use a user-defined variable ${uuid} which we could create it in last step. And for this post request, use the XSRF token fetched from previous HTTP get request.
前面说过,POST请求的头部需要加上合法的CSRF token,此处我们使用前面GET请求已经拿到的并且存储于变量jerrycsrftoken中的token值:
我希望最后通过并发测试生成的Service Ticket的描述信息的后缀是1到100的随机正整数,因此我使用jMeter里自带的一个随机数发生器:
(4) As the last step, create a user variable by using JMeter built-in function __Random, to create a random number between 1 ~ 100 as a fragment of created Service Request description.
Now execute the Thread group, and the execution detail for these three HTTP request could be reviewed separately in tree view:
试着运行一下,发现这个POST操作确实按照我们期望的那样,在HTTP头部字段里加上了正确合法的CSRF token:
For example, the XSRF token is successfully fetched in the first request: rdPy7zNj_uKDYvQLgfQCFA==
And used as one header field in second HTTP Post request as expected:
And finally in UI we could find the created Service request with random number between 1 ~ 100 as postfix:
在UI上观测到我构造的5个并发请求创建的Service Ticket,说明CSRF token在服务器端的校验成功,同时发现描述信息都带上了随机数,说明我的jMeter随机数生成器的用法也正确。
希望本文对大家的工作有所帮助。
要获取更多Jerry的原创文章,请关注公众号"汪子熙":