相信前端開發工程師對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來管理。
Some key points for this JMeter project creation
(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随機數生成器的用法也正确。
希望本文對大家的工作有所幫助。
本文來自雲栖社群合作夥伴“汪子熙”,了解相關資訊可以關注微信公衆号"汪子熙"。