更多關於代碼審計、WEB×××、網絡安全的運維的知識,請關注微信公衆號:發哥微課堂。
0x00:CSRF 簡述
CSRF(Cross Site Request Forgery,跨站請求僞造),字面理解意思就是在別的站點僞造了一個請求。專業術語來說就是在受害者訪問一個網站時,其 Cookie 還沒有過期的情況下,×××者僞造一個鏈接地址發送受害者並欺騙讓其點擊,從而形成 CSRF ×××。
0x01:CSRF 案例
受害者 (A) 登錄了某個銀行給朋友 (B) 轉賬,其轉賬操作發送的請求 URL 如下:
https://www.xxxx.com?account=A&money=10000&touser=B
account 代表受害者,money 是要轉賬的金額,touser 是被轉入的賬戶。發送這個鏈接請求後,A 給 B 轉賬的操作完成。這時×××者(C)僞造了一個鏈接,如下:
https://www.xxxx.com?account=A&money=10000&touser=C
這個鏈接的請求是 A 用戶給 C 用戶轉賬一萬元。當 A 沒有登錄不存在 Cookie 信息時,此鏈接是無法執行的。這時×××者通過一系列手段讓 A 執行此鏈接,當 A 登錄銀行沒有退出的時候,點擊此鏈接便會執行成功。
0x02:代碼示例
當程序對於類似此敏感信息操作提交時,沒有進行相應的防護,便會產生 CSRF ×××,例如一下代碼:
<form method="GET" action="/transferFunds">
轉賬金額:<input type="text" name="money">
轉入賬戶:<input type="text" name="touser">
<input type="submit" name="action" value="提交">
</form>
0x03:如何測試
在×××測試中,可以先看下網頁源代碼對於敏感信息提交有無防護措施,初步判斷是否存在 CSRF,隨後通過抓包確定提交的完整 URL 鏈接,並僞造另一個鏈接進行測試。
在代碼審計中,可以先查看網頁源代碼,是否有防護措施。隨後可查看 WEB 應用程序的配置文件中是否有相應的驗證措施,最後查看後臺的處理邏輯,對於發送過來的請求是否有過濾等措施。
0x04:防護方法
1,二次驗證,進行重要敏感操作時,要求用戶進行二次驗證。
2,驗證碼,進行重要敏感操作時,加入驗證碼。
3,驗證 HTTP 的 Referer 字段。
4,請求地址中添加 Token 並驗證。
5,HTTP 頭中自定義屬性並驗證。
0x05:防護代碼
1,對於二次驗證,可添加 JS,提交請求後詢問客戶是否提交,而不是直接發送請求給後臺。
<script>
function moneySub(){
if(confirm('確認進行轉賬操作?')){
renturn ture;
}else{
return false;
}
}
</script>
<form method="GET" action="/transferFunds">
轉賬金額:<input type="text" name="money">
轉入賬戶:<input type="text" name="touser">
<input type="submit" name="action" value="提交">
</form>
2,對於驗證碼,進行轉賬時,可輸入圖形驗證碼,也可以添加手機接收驗證碼等功能。
<script>
var code;
window.onload=function createCode(){
code = "";
var codeLength = 4;
var checkCode = document.getElementById("code");
var random = new Array(0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R', 'S','T','U','V','W','X','Y','Z');
for(var i = 0; i < codeLength; i++) {
var index = Math.floor(Math.random()*36);
code += random[index];
}
checkCode.value = code;
}
function validate(){
var inputCode = document.getElementById("yzm").value.toUpperCase();
if(inputCode.length <= 0) {
alert("請輸入驗證碼!");
return false;
}else if(inputCode != code ) {
alert("驗證碼輸入錯誤!");
return false;
}else {
return true;
}
}
以上代碼,當驗證碼爲空時或錯誤時則無法提交請求。
3,對於驗證 HTTP Referer 字段,請求到後臺時,判斷下請求是否來自自己的站點,如果不是 Referer 的值不是以自己域名開頭,則會請求失敗。
String referer = request.getHeader("Referer");
if((referer!=null) && (referer.trim().startsWith("xxxx.com"))){
chain.doFilter(request,response);
}else{
request.getRequestDispatcher("error.jsp").forward(request,response);
}
以上代碼,使用 Java 的過濾器 Filter 來攔截請求,當獲取請求的 Referer 的值不爲空時並且是以自己站點的域名開頭時,則放行。否則跳轉到 error 頁面。
4,對於請求地址中添加 Token 驗證,也是用的最多的一個方法,在表單中添加一個 hidden 隱藏字段,發送請求時一起發送,並在服務器驗證 Token 的值。Token 的值越複雜,則安全性越高。
HttpServletRequest req = (HttpServletRequest)request;
HttpSession s = req.getSession();
String sToken = (String)s.getAttribute("token");
if(sToken == null){
//如果token爲空,則認爲首次訪問,生成新的token
sToken = generateToken();
s.setAttribute("token",sToken);
chain.doFilter(request,response);
}else{
String token = req.getParameter("token");
//不是第一次訪問,如果token和服務器的相同,則放行
if(sToken != null && sToken.equals(token)){
chain.doFilter(request,response);
}else{
request.getRequestDispatcher("error.jsp").forward(request,response);
}
}
對於代碼中的 generateToken 生成 token 的方法,可以使用 Java 的 UUID,代碼如下:
for(int i=0;i<10;i++){
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
}
5,對於 HTTP 頭部自定義屬性驗證,和 token 機制類似。只不過是把 token 從表單放到了請求的頭重,如下代碼:
var plainXhr = dojo.xhr;
dojo.xhr = function(method,args,hasBody) { // 重寫 dojo.xhr 方法
args.headers = args.header || {}; // 確保 header 對象存在
tokenValue = '<%=request.getSession(false).getAttribute("token")%>';
var token = dojo.getObject("tokenValue");
args.headers["token"] = (token) ? token : " "; //把token 屬性放到頭中
return plainXhr(method,args,hasBody);
}
dojo.xhr 是用 JS 寫的一個工具包,重寫其中的方法,把從 session 中獲取的 token 值放到新添加的頭部字段中,然後發送給後臺。
0x06:CSRF 總結
對於 CSRF 其危害性比較大,不易防護,建議在開發過程中結合以上的多條防護措施進行防護,不建議只用某一點或某一條,多層防護更有利於系統的安全。
更多關於代碼審計、WEB×××、網絡安全的運維的知識,請關注微信公衆號:發哥微課堂。