web安全中有不少種攻擊手段,除了SQL注入外,比較常見的還有 XSS 和 CSRF等javascript
XSS其實就是Html的注入問題,攻擊者的輸入沒有通過嚴格的控制進入了數據庫,最終顯示給來訪的用戶,致使能夠在來訪用戶的瀏覽器裏以瀏覽用戶的身份執行Html代碼。php
數據流程爲:攻擊者的Html輸入—>web程序—>進入數據庫—>web程序—>用戶瀏覽器。html
跨站腳本,顧名思義,更多的狀況下是注入一些js代碼,實現站點影響或竊取用戶信息等目的。java
通常的攻擊方式:node
><script>alert(document.cookie)</script> ='><script>alert(document.cookie)</script> "><script>alert(document.cookie)</script> <script>alert(document.cookie)</script> <script>alert(vulnerable)</script> %3Cscript%3Ealert('XSS')%3C/script%3E <script>alert('XSS')</script> <img src="javascript:alert('XSS')"> <img src="http://xxx.com/yyy.png" onerror="alert('XSS')"> <div style="height:expression(alert('XSS'),1)" />(這個僅限 IE 有效)
經過在站點中可輸入的文本區域,輸入相似上述提到的js代碼,若站點未對數據進行驗證處理,腳本就會存入數據庫,進而顯示給其餘用戶,則其餘用戶將會受到影響。web
而影響方式主要有幾個:ajax
1. 若是是這種無聊惡意的數據庫
<script>alert(哈哈哈你關不掉個人~)</script>
用戶打開相應站點則..關不掉..express
或者惡意更改站點原數據瀏覽器
<script> window.onload = function() { var links=document.getElementsByTagName("a"); for(var i=0,j=links.length;i<j;i++){ links[i].href="http://ad.com/"; } }; </script>
2.竊取cookie,或者更直接的是拿到sessionId(拿到該用戶的登陸憑證)
若是須要收集來自被攻擊者的數據(如cookie或其餘敏感信息),能夠自行架設一個網站,讓被攻擊者經過JavaScript等方式把收集好的數據做爲參數提交,隨後以數據庫等形式記錄在攻擊者本身的服務器上。
而使用方式能夠是暴力地直接跳轉到惡意站點並附帶參數,軟暴力地則可使用 img link script 標籤src屬性直接加載某個惡意站點,或者使用ajax暗地操刀。
3.利用可被攻擊的域受到其餘域信任的特色,以受信任來源的身份請求一些平時不容許的操做(這個已經屬於csrf範疇了)
通常的防護措施:
1.永遠不相信用戶的輸入。須要對用戶的輸入進行處理,只容許輸入合法的值,其它值一律過濾掉。
某些狀況下,咱們不能對用戶數據進行嚴格的過濾,那咱們也須要對標籤進行轉換。
less-than character (<) | < |
greater-than character (>) | > |
ampersand character (&) | & |
double-quote character (") | " |
space character( ) | |
Any ASCII code character whose code is greater-than or equal to 0x80 | &#<number>, where <number> is the ASCII character value. |
好比用戶輸入:
<script>window.location.href=」http://www.baidu.com」;</script>,
保存後最終存儲的會是:
<script>window.location.href="http://www.baidu.com"</script>
在展示時瀏覽器會對這些字符轉換成文本內容顯示,而不是一段可執行的代碼。
許多語言都有提供對HTML的過濾:
PHP的htmlentities()或是htmlspecialchars()。 Python的cgi.escape()。 ASP的Server.HTMLEncode()。 ASP.NET的Server.HtmlEncode()或功能更強的Microsoft Anti-Cross Site Scripting Library Java的xssprotect(Open Source Library)。 Node.js的node-validator。
2.HttpOnly防止劫取Cookie
HttpOnly最先由微軟提出,至今已經成爲一個標準。瀏覽器將禁止頁面的Javascript訪問帶有HttpOnly屬性的Cookie。
目前主流瀏覽器都支持,HttpOnly解決是XSS後的Cookie支持攻擊。
好比php代碼的使用
<?php header("Set-Cookie: cookie1=test1;"); header("Set-Cookie: cookie2=test2;httponly",false); setcookie('cookie3','test3',NULL,NULL,NULL,NULL,false); setcookie('cookie4','test4',NULL,NULL,NULL,NULL,true); ?> <script> alert(document.cookie); </script>
js只能讀到沒有HttpOnly標識的Cookie
XSS 是實現 CSRF 的諸多途徑中的一條,但絕對不是惟一的一條。通常習慣上把經過 XSS 來實現的 CSRF 稱爲 XSRF。
CSRF 顧名思義,是僞造請求,冒充用戶在站內的正常操做。咱們知道,絕大多數網站是經過 cookie 等方式辨識用戶身份(包括使用服務器端 Session 的網站,由於 Session ID 也是大多保存在 cookie 裏面的),再予以受權的。因此要僞造用戶的正常操做,最好的方法是經過 XSS 或連接欺騙等途徑,讓用戶在本機(即擁有身份 cookie 的瀏覽器端)發起用戶所不知道的請求。
要完成一次CSRF攻擊,受害者必須依次完成兩個步驟:
1.登陸受信任網站A,並在本地生成Cookie。
2.在不登出A的狀況下,訪問危險網站B。
看到這裏,你也許會說:「若是我不知足以上兩個條件中的一個,我就不會受到CSRF的攻擊」。
是的,確實如此,但你不能保證如下狀況不會發生:
1.你不能保證你登陸了一個網站後,再也不打開一個tab頁面並訪問另外的網站。
2.你不能保證你關閉瀏覽器了後,你本地的Cookie馬上過時,你上次的會話已經結束。(事實上,關閉瀏覽器不能結束一個會話,但大多數人都會錯誤的認爲關閉瀏覽器就等於退出登陸/結束會話了……)
3.上圖中所謂的攻擊網站,多是一個存在其餘漏洞的可信任的常常被人訪問的網站。
上面大概地講了一下CSRF攻擊的思想,下面我將用幾個例子詳細說說具體的CSRF攻擊,這裏我以一個銀行轉帳的操做做爲例子(僅僅是例子,真實的銀行網站沒這麼傻:>)
示例1:
銀行網站A,它以GET請求來完成銀行轉帳的操做,如:
http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危險網站B,它裏面有一段HTML的代碼以下:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
首先,你登陸了銀行網站A,而後訪問危險網站B,噢,這時你會發現你的銀行帳戶少了1000塊……
爲何會這樣呢?緣由是銀行網站A違反了HTTP規範,使用GET請求更新資源。在訪問危險網站B的以前,你已經登陸了銀行網站A,而B中的<img>以GET的方式請求第三方資源(這裏的第三方就是指銀行網站了,本來這是一個合法的請求,但這裏被不法分子利用了),因此你的瀏覽器會帶上你的銀行網站A的Cookie發出Get請求,去獲取資源「http://www.mybank.com/Transfer.php?toBankId=11&money=1000」,
結果銀行網站服務器收到請求後,認爲這是一個更新資源操做(轉帳操做),因此就馬上進行轉帳操做……
示例2:
爲了杜絕上面的問題,銀行決定改用POST請求完成轉帳操做。
銀行網站A的WEB表單以下:
<form action="Transfer.php" method="POST"> <p>ToBankId: <input type="text" name="toBankId" /></p> <p>Money: <input type="text" name="money" /></p> <p><input type="submit" value="Transfer" /></p> </form>
後臺處理頁面Transfer.php以下:
<?php session_start(); if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money'])) { buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']); } ?>
危險網站B,仍然只是包含那句HTML代碼:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
和示例1中的操做同樣,你首先登陸了銀行網站A,而後訪問危險網站B,結果…..和示例1同樣,你再次沒了1000塊~T_T,此次事故的緣由是:銀行後臺使用了$_REQUEST去獲取請求的數據,而$_REQUEST既能夠獲取GET請求的數據,也能夠獲取POST請求的數據,這就形成了在後臺處理程序沒法區分這究竟是GET請求的數據仍是POST請求的數據。在PHP中,可使用$_GET和$_POST分別獲取GET請求和POST請求的數據。在JAVA中,用於獲取請求數據request同樣存在不能區分GET請求數據和POST數據的問題。
示例3:
通過前面2個慘痛的教訓,銀行決定把獲取請求數據的方法也改了,改用$_POST,只獲取POST請求的數據,後臺處理頁面Transfer.php代碼以下:
<?php session_start(); if (isset($_POST['toBankId'] && isset($_POST['money'])) { buy_stocks($_POST['toBankId'], $_POST['money']); } ?>
然而,危險網站B與時俱進,它改了一下代碼:
<html> <head> <script type="text/javascript"> function steal() { iframe = document.frames["steal"]; iframe.document.Submit("transfer"); } </script> </head> <body onload="steal()"> <iframe name="steal" display="none"> <form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php"> <input type="hidden" name="toBankId" value="11"> <input type="hidden" name="money" value="1000"> </form> </iframe> </body> </html>
若是用戶還是繼續上面的操做,很不幸,結果將會是再次不見1000塊……由於這裏危險網站B暗地裏發送了POST請求到銀行
總結一下上面3個例子,CSRF主要的攻擊模式基本上是以上的3種,其中以第1,2種最爲嚴重,由於觸發條件很簡單,一個<img>就能夠了,而第3種比較麻煩,須要使用JavaScript,因此使用的機會會比前面的少不少,但不管是哪一種狀況,只要觸發了CSRF攻擊,後果都有可能很嚴重。
理解上面的3種攻擊模式,其實能夠看出,CSRF攻擊是源於WEB的隱式身份驗證機制!WEB的身份驗證機制雖然能夠保證一個請求是來自於某個用戶的瀏覽器,但卻沒法保證該請求是用戶批准發送的.
那有什麼防護方法?
通常防護CSRF有三種方法,判斷referer、驗證碼、token。
1.判斷 referer
根據HTTP協議,在HTTP頭中有一個字段叫Referer,它記錄了該HTTP請求的來源地址。
在一般狀況下,訪問一個安全受限頁面的請求必須來自於同一個網站。
好比某銀行的轉帳是經過用戶訪問http://bank.test/test?page=10&userID=101&money=10000頁面完成,用戶必須先登陸bank.test,而後經過點擊頁面上的按鈕來觸發轉帳事件。當用戶提交請求時,該轉帳請求的Referer值就會是轉帳按鈕所在頁面的URL(本例中,一般是以bank. test域名開頭的地址)。
而若是攻擊者要對銀行網站實施CSRF攻擊,他只能在本身的網站構造請求,當用戶經過攻擊者的網站發送請求到銀行時,該請求的Referer是指向攻擊者的網站。
所以,要防護CSRF攻擊,銀行網站只須要對於每個轉帳請求驗證其Referer值,若是是以bank. test開頭的域名,則說明該請求是來自銀行網站本身的請求,是合法的。若是Referer是其餘網站的話,就有多是CSRF攻擊,則拒絕該請求。
好比php的:
<?php if(eregi(」bank.test」, $_SERVER[’HTTP_REFERER’])) { do_something(); } else { echo 「Malicious Request!」; } ?>
這個檢測則會輕易的忽略掉來自某個攻擊者僞造的HTTP Referer欺騙,
因爲HTTP Referer是由客戶端瀏覽器發送的,或者其餘在惡意腳本中僞造HTTP頭併發送的方法。攻擊者可使用以下代碼是僞造無效的。
header(」Referer: bank.test」);
但缺點是並非全部瀏覽器都支持referer頭,或者一些flash的提交也不支持,因此存在着缺陷。
2.驗證碼
另一個解決這類問題的思路則是在用戶提交的每個表單中使用一個隨機驗證碼,讓用戶在文本框中填寫圖片上的隨機字符串,而且在提交表單後對其進行檢測。
這個方法曾經在以前被人們放棄,這是因爲驗證碼圖片的使用涉及了一個被稱爲MHTML的Bug,可能在某些版本的微軟IE中受影響。
而驗證碼的過分使用也會影響到用戶體驗。
3.token
1)在請求地址中添加token並驗證
CSRF攻擊之因此可以成功,是由於攻擊者能夠僞造用戶的請求,該請求中全部的用戶驗證信息都存在於Cookie中,所以攻擊者能夠在不知道這些驗證信息的狀況下直接利用用戶本身的Cookie來經過安全驗證。
由此可知,抵禦CSRF攻擊的關鍵在於:在請求中放入攻擊者所不能僞造的信息,而且該信息不存在於Cookie之中。
鑑於此,系統開發者能夠在HTTP請求中以參數的形式加入一個隨機產生的token,並在服務器端創建一個攔截器來驗證這個token,若是請求中沒有token或者token內容不正確,則認爲多是CSRF攻擊而拒絕該請求。
仍是用php舉例:
讓咱們從令牌值的生成開始:
<?php function gen_token() { // Generate the md5 hash of a randomized uniq id $hash = md5(uniqid(rand(), true)); // Select a random number between 1 and 24 (32-8) $n = rand(1, 24); // Generate the token retrieving a part of the hash starting from // the random N number with 8 of lenght $token = substr($hash, $n, 8); return $token; } ?>
PHP函數uniqid()容許web開發者根據當前的時間(毫秒數)得到一個惟一的ID,這個惟一ID有利於生成一個不重複的數值。
咱們檢索相應ID值的MD5散列,然後咱們從該散列中以一個小於24的數字爲開始位置,選取8位字母、
返回的$token變量將檢索一個8位長的隨機令牌。
如今讓咱們生成一個Session令牌,在稍後的檢查中咱們會用到它。
<?php function gen_stoken() { // Call the function to generate the token $token = gen_token(); // Destroy any eventually Session Token variable destroy_stoken(); // Create the Session Token variable session_register(STOKEN_NAME); $_SESSION[STOKEN_NAME] = $token; } ?>
在這個函數中咱們調用gen_token()函數,而且使用返回的令牌將其值複製到一個新的$_SESSION變量。
如今讓咱們來看啓動完整機制中爲咱們的表單生成隱藏輸入域的函數:
<?php function gen_input() { // Call the function to generate the Session Token variable gen_stoken(); // Generate the form input code echo 「<input type=\」hidden\」 name=\」" . FTOKEN_NAME . 「\」 value=\」" . $_SESSION[STOKEN_NAME] . 「\」> 「; } ?>
咱們能夠看到,這個函數調用了gen_stoken()函數而且生成在WEB表單中包含隱藏域的HTML代碼。
接下來讓咱們來看實現對隱藏域中提交的Session令牌的檢測的函數:
<?php function token_check() { // Check if the Session Token exists if(is_stoken()) { // Check if the request has been sent if(isset($_REQUEST[FTOKEN_NAME])) { // If the Form Token is different from Session Token // it’s a malicious request if($_REQUEST[FTOKEN_NAME] != $_SESSION[STOKEN_NAME]) { gen_error(1); destroy_stoken(); exit(); } else { destroy_stoken(); } // If it isn’t then it’s a malicious request } else { gen_error(2); destroy_stoken(); exit(); } // If it isn’t then it’s a malicious request } else { gen_error(3); destroy_stoken(); exit(); } } ?>
這個函數檢測了$_SESSION[STOKEN_NAME]和$_REQUEST[FTOKEN_NAME]的存在性(我使用了$ _REQUEST方法來使得GET和POST兩種方式提交的表單變量均可以被接受),然後檢測他們的值是否相同,所以判斷當前表單提交是不是通過認證受權的。
這個函數的重點在於:在每次檢測步驟結束後,令牌都會被銷燬,而且僅僅在下一次表單頁面時纔會從新生成。
這些函數的使用方法很是簡單,咱們只須要加入一些PHP代碼結構。
下面是Web表單:
<?php session_start(); include(」functions.php」); ?> <form method=」POST」 action=」resolve.php」> <input type=」text」 name=」first_name」> <input type=」text」 name=」last_name」> <!– Call the function to generate the hidden input –> <? gen_input(); ?> <input type=」submit」 name=」submit」 value=」Submit」> </form>
下面是解決的腳本代碼:
<?php session_start(); include(」functions.php」); // Call the function to make the check token_check(); // Your code … ?>
2)在HTTP頭中自定義屬性並驗證
自定義屬性的方法也是使用token並進行驗證,和前一種方法不一樣的是,這裏並非把token以參數的形式置於HTTP請求之中,而是把它放到HTTP頭中自定義的屬性裏。
經過XMLHttpRequest這個類,能夠一次性給全部該類請求加上csrftoken這個HTTP頭屬性,並把token值放入其中。
這樣解決了前一種方法在請求中加入token的不便,同時,經過這個類請求的地址不會被記錄到瀏覽器的地址欄,也不用擔憂token會經過Referer泄露到其餘網站。