最近項目涉及到安全方面,自己特意了解了一下,記錄在此,共同學習。
常見的web安全有以下幾個方面
- 同源策略(Same Origin Policy)
- 跨站腳本攻擊XSS(Cross Site Scripting)
- 跨站請求偽造CSRF(Cross-site Request Forgery)
- 點擊劫持(Click Jacking)
- SQL注入(SQL Injection)
同源策略
含義
所謂同源策略,指的是瀏覽器對不同源的腳本或者文本的訪問方式進行的限制。比如源a的js不能讀取或設置引入的源b的元素屬性。
所謂"同源"指的是"三個相同"
- 協議相同
- 域名相同
- 端口相同
舉例來說,http://www.example.com/dir/page.html 這個網址,協議是 http:// ,域名是 www.example.com ,端口是 80(默認端口可以省略)。它的同源情況如下
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
目的
同源政策的目的,是為了保證用戶信息的安全,防止惡意的網站竊取數據。
設想這樣一種情況:A網站是一家銀行,用戶登錄以后,又去瀏覽其他網站。如果其他網站可以讀取A網站的 Cookie,Cookie 包含的信息(比如存款總額)就會泄漏。而Cookie 往往用來保存用戶的登錄狀態,如果用戶沒有退出登錄,其他網站就可以冒充用戶,為所欲為。
由此可見,"同源政策"是必需的,否則 Cookie 可以共享,互聯網就毫無安全可言了。
限制
隨着互聯網的發展,"同源政策"越來越嚴格。目前,如果非同源,共有三種行為受到限制
- Cookie、LocalStorage 和 IndexDB 無法讀取
- DOM 無法獲得
- AJAX 請求不能發送
Ajax應用
我們可以在本地模擬非同源的ajax請求。
先寫一個簡單的web應用作為服務方,提供一個登陸服務接口,部署運行在tomcat上,此tomcat端口為8080。
再寫一個小工程,只有一個簡單的登陸頁面。部署在另一個tomcat上,此tomcat端口設定為8081。
如此一來,兩個工程就不是同源了,因為端口不同。我們在登陸頁面發送ajax請求,出現錯誤:
XMLHttpRequest cannot load http://192.168.2.82:8080/Demo/user/login. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8081' is therefore not allowed access. The response had HTTP status code 403.
要解決這個問題,可以在服務器方設定
response.setHeader("Access-Control-Allow-Origin", "*");
服務方就可以允許其他域名訪問。
要設定允許某個域名或某幾個域名如下
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081");
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081,http://localhost:8082");
在spring mvc中,有一個注解@CrossOrigin,在需要提供跨域訪問的方法上添加即可
@CrossOrigin(origins = {"http://localhost:8081", "http://192.168.2.99:8080"})
這樣就可以成功跨域登陸了。
實際應用中,也可以讓ajax訪問同源服務接口,再由此服務去訪問其他服務。
跨站腳本攻擊
簡介
跨站腳本攻擊(Cross Site Scripting),為了不和層疊樣式表(Cascading Style Sheets,CSS)縮寫混淆,所以在安全領域叫做XSS。
XSS攻擊,是指黑客通過"HTML注入"篡改了網頁,插入了惡意的腳本,在用戶瀏覽網頁時,代碼執行,從而實現用戶瀏覽器。對受害用戶可能采取Cookie資料竊取、會話劫持、釣魚欺騙等各種攻擊。
XSS有如下幾種類型
-
反射型XSS
反射型XSS只是簡單地把用戶輸入的數據"反射"給瀏覽器。也就是說,黑客需要誘使用戶點擊一個惡意鏈接,才能攻擊成功。反射型XSS也叫非持久型XSS。
比如
<div>${message}</div>正常情況用戶提交信息
http://xxx.com/test.html?param=hello那么瀏覽器正常輸出"hello"信息
但是如果用戶輸入了一段HTML代碼
http://xxx.com/test.html?param=<script>alert("xss")</script>那么頁面彈框,顯示"XSS"
-
存儲型XSS
存儲型XSS會把用戶輸入的數據"存儲"在服務器。這種XSS具有很強的穩定性。
比較常見的場景是,黑客寫了一遍包含有惡意JavaScript的博客文章,文章發表后,所有訪問該博客文章的用戶,都會在他們的瀏覽器中執行這段惡意的JavaScript代碼。黑客把惡意腳本保存到服務器端,所以這種XSS攻擊就叫做"存儲型XSS"。
-
DOM Based XSS
通過修改頁面的DOM節點形成的XSS稱之為DOM Based XSS。實際上,這種類型的XSS並非按照"數據是否保存在服務器端"來划分,DOM Based XSS從效果上來說也是反射型XSS。
假設xss.html頁面代碼如下
<!DOCTYPE HTML> <html> <head> <title>原頁面</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style> button{ position:absolute; top: 315px; left: 462px; z-index: 1; width: 72px; height: 26px; } </style> </head> <body> <button onclick="func()">加關注</button> </body> <script> function func(){ alert("關注成功!"); } </script> </html>文本框輸入內容並點擊write按鈕后,會在當前頁面插入一個超鏈接,其地址為輸入的內容。

假設用戶輸入為' onclick=alert(/xss/) //那么頁面代碼變為
<a href'' onclick=alert(/xss/)//'>testLink</a>首先用第一個單引號閉合掉href的第一個單引號,然后插入一個onclick事件,最后在用//注釋掉第二個單引號。
點擊鏈接,腳本執行

防御
XSS攻擊成功的根本原因是將輸入的數據當成了代碼來執行,從而違背了原來的語義。所以要在輸入的時候要嚴格過濾,輸出的時候也要進行檢查。
輸入檢查
常見的XSS攻擊,SQL注入,都要求攻擊者構造一些特殊字符,這些特殊字符是正常用戶不會用到的,所以輸入檢查很有必要。
只接受指定長度范圍和期望格式的的內容提交,阻止或者忽略除此外的其他任何數據。比如:用戶名只能是字母加數字,手機不長於16位,且大陸手機必須以13x,15x開頭,否則非法。過濾一些些常見的敏感字符,例如:
< > ‘ “ & # \ javascript expression "onclick=" "onfocus"
過濾或移除特殊的Html標簽, 例如:
<script>, <iframe> , < for <, > for >, " for
過濾JavaScript 事件的標簽,例如 "onclick=", "onfocus" 等等。
輸出檢查
一般來說,除了富文本的輸出外,在變量輸出到HTML頁面時,可以使用編碼(HtmlEncode)或轉義的方式來防御XSS攻擊。
DOM型的防御
把變量輸出到頁面時要做好相關的編碼轉義工作,如要輸出到 <script>中,可以進行JS編碼;要輸出到HTML內容或屬性,則進行HTML編碼處理。根據不同的語境采用不同的編碼處理方式。
HttpOnly
將重要的cookie標記為http only, 這樣的話當瀏覽器向Web服務器發起請求的時就會帶上cookie字段,但是在腳本中卻不能訪問這個cookie,這樣就避免了XSS攻擊利用JavaScript的document.cookie獲取cookie:
跨站請求偽造
原理
CSRF原理比較簡單,如圖

-
用戶C打開瀏覽器,訪問受信任網站A,輸入用戶名和密碼請求登錄網站A;
-
在用戶信息通過驗證后,網站A產生Cookie信息並返回給瀏覽器,此時用戶登錄網站A成功,可以正常發送請求到網站A;
-
用戶未退出網站A之前,在同一瀏覽器中,打開一個TAB頁訪問網站B;
-
網站B接收到用戶請求后,返回一些攻擊性代碼,並發出一個請求要求訪問第三方站點A;
-
瀏覽器在接收到這些攻擊性代碼后,根據網站B的請求,在用戶不知情的情況下攜帶Cookie信息,向網站A發出請求。網站A並不知道該請求其實是由B發起的,所以會根據用戶C的Cookie信息以C的權限處理該請求,導致來自網站B的惡意代碼被執行。
假設用戶在A網站可以刪除自己的文章,其請求地址類似http://xxx.com/del.do?id=101。並且已經登錄。
攻擊者先構造一個自己的網頁csrf.html,其內容為
<img src="http://xxx.com/del.do?id=102">
攻擊者誘使目標用戶C訪問該頁面,之后再回去查看自己的文章,發現id為102的文章被刪除了。
原來在剛才訪問csfr.html時,圖片標簽發送了一次請求,導致該文章被刪除。
防御
驗證碼
因為csrf攻擊是在用戶不知情的情況下發起請求。驗證碼則強制用戶與應用交互。
但是出於用戶體驗考慮,網站不能給所有操作都加驗證碼。所以驗證碼只能作為一種輔助手段。
Referer Check
根據HTTP協議,在HTTP頭中有一個字段叫Referer,它記錄了該HTTP請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求必須來自於同一個網站。
在互聯網中,Referer Check的常見用法是防止圖片盜鏈。同理也可以檢查請求是否來自合法的源。
某銀行的轉賬是通過用戶訪問 http://bank.test/test?page=10&userID=101&money=10000 頁面完成,用戶必須先登錄bank.test,然后通過點擊頁面上的按鈕來觸發轉賬事件。當用戶提交請求時,該轉賬請求的Referer值就會是轉賬按鈕所在頁面的URL(本例中,通常是以bank.test域名開頭的地址)。而如果攻擊者要對銀行網站實施CSRF攻擊,他只能在自己的網站構造請求,當用戶通過攻擊者的網站發送請求到銀行時,該請求的Referer是指向攻擊者的網站,則銀行網站拒絕該請求。
添加Token
CSRF攻擊之所以能夠成功,是因為攻擊者可以偽造用戶的請求,該請求中所有的用戶驗證信息都存在於Cookie中,因此攻擊者可以在不知道這些驗證信息的情況下直接利用用戶自己的Cookie來通過安全驗證。
由此可知,抵御CSRF攻擊的關鍵在於:在請求中放入攻擊者所不能偽造的信息,並且該信息不存在於Cookie之中。鑒於此,系統開發者可以在HTTP請求中以參數的形式加入一個隨機產生的token,並在服務器端建立一個攔截器來驗證這個token,如果請求中沒有token或者token內容不正確,則認為可能是CSRF攻擊而拒絕該請求。
點擊劫持
點擊劫持,這個術語是Robert Hansen 和 Jeremiah Grossman這2位安全研究專家給出的;其實在2008年9月,Adobe公司就發表了一份公開演講,關於點擊劫持問題的,因為其Flash產品的缺陷可以被嚴重的惡意利用。
點擊劫持/UI重定向,是指惡意網站偽造一個看似可信的元素(如PayPal的donate按鈕,或一個Send按鈕-by Gmail等你可能使用的郵箱網站),根據RSnake and Jeremiah的調查證明,用戶點擊這些貌似可信的Sites的任何元素都可能觸發你的話筒或者攝像頭,遠程攻擊者可同時立即對你進行監控。
更常用的方法是,攻擊者在他控制的網站用框架包含一個可信Sites,剝除掉上下文或者透明化這個Sites,這樣他就可以輕易的操控你,而你最后可能就是給他發送轉賬,或者給他任何特權,而這些操作用戶是完全不知曉的,后台進行;更甚的,如果這個惡意站點允許使用JS,那么攻擊者可以及其輕松的把隱藏的元素精確地放置在鼠標指針下,這樣的話不管用戶點哪兒,攻擊者都贏了;更更有甚的,攻擊者可以在JS被禁用的情況下進行欺騙,只需要騙取用戶點擊一個鏈接或者按鈕。
注意:點擊劫持在任何瀏覽器上都存在,因為點擊劫持不是瀏覽器漏洞或者Bug造成的,不能一夜就打上補丁;相反,點擊劫持是利用最基本的標准Web特點,在任何位置都能實現,而這種天然缺陷是無法在短時間內改善的。
點擊劫持是一種視覺上的欺騙手段。大概有兩種方式,一是攻擊者使用一個透明的,不可見的iframe,覆蓋在一個網頁上,然后誘使用戶在該頁面上進行操作,此時用戶將在不知情的情況下點擊透明的iframe頁面,可以誘使用戶恰好點擊在iframe頁面的一些功能性按鈕上;二是攻擊者使用一張圖片覆蓋在網頁,遮擋網頁原有位置的含義。
iframe覆蓋
先寫一個src.html,模擬源網頁。
<!DOCTYPE HTML>
<html>
<head>
<title>原頁面</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
button{
position:absolute;
top: 315px;
left: 462px;
z-index: 1;
width: 72px;
height: 26px;
}
</style>
</head>
<body>
<button onclick="func()">加關注</button>
</body>
<script>
function func(){
alert("關注成功!");
}
</script>
</html>
這個頁面有一個按鈕,點擊后觸發事件。

再寫一個jack.html,劫持頁面。
<!DOCTYPE HTML>
<html>
<head>
<title>點擊劫持</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
html,body,iframe{
display: block;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
border:none;
}
iframe{
opacity:0;
filter:alpha(opacity=0);
position:absolute;
z-index:2;
}
button{
position:absolute;
top: 335px;
left: 462px;
z-index: 1;
width: 72px;
height: 26px;
}
</style>
</head>
<body>
那些不能說的秘密
<button>查看詳情</button>
<iframe src="src.html"></iframe>
</body>
</html>
這個頁面用一個按鈕覆蓋了原網頁的按鈕,用戶點擊后,實際就點擊到了原網頁的按鈕。

將透明度調一下查看按鈕布局

圖片覆蓋
先在之前的src.html中加入一個logo圖片

在jack.html中同樣添加一個圖片

調解透明度可以看到背后實際情況

假如LOGO圖片對應的鏈接是某個網站,那么用戶點擊該圖片,就會被鏈接到假的網站上。
圖片也可以偽裝得像正常的鏈接,按鈕;或者圖片中構造文字,覆蓋在關鍵位置,可能會改變原有的意思。這種情況下,不需要用戶點擊,也能達到欺騙的目的,比如覆蓋了頁面的聯系電話。
防御
iframe防御
通常可以寫一段JavaScript代碼,以禁止iframe的嵌套。
if(top.location != location){
top.location = self.location;
}
在src中加入該代碼,再訪問jack.html,發現瀏覽器自動跳轉到了src.html。
由於是用JavaScript寫的,這樣的控制能力不是特別強,有許多方法可以繞過它。一個更好的方案是使用一個HTTP頭——X-Frame-Options。
X-Frame-Options可以說是為了解決ClickJacking而生的。
它有三個可選值
- DENY:瀏覽器拒絕當前頁面加載任何frame頁面
- SAMEORIGIN:frame頁面的地址只能為同源域名下的頁面
- ALLOW-FROM origin:定義允許frame加載的頁面地址
圖片防御
由於<img>標簽在很多系統中是對用戶開放的,因此在現實中有非常多的站點存在被圖片覆蓋攻擊的可能。在防御時,需要檢查用戶提交的html代碼中,<img>標簽的style屬性是否可能導致浮出。
SQL注入
簡介
通過把SQL命令插入到Web表單遞交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行惡意的SQL命令。
具體來說,它是利用現有應用程序,將(惡意)的SQL命令注入到后台數據庫引擎執行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網站上的數據庫,而不是按照設計者意圖去執行SQL語句。
場景
比如我們開發了一個登陸模塊,查詢語句是
select * from user where name = ${name} and password = ${password};
正常情況下,我們拿到用戶輸入的用戶名和密碼拼接出來的語句是
select * from user where name = "admin" and password = "123456";
如果用戶輸入用戶名為 "xxx" or 1 #,密碼隨便。那么拼接的語句就變成了
select * from user where username = "xxx" or 1 # and password = "123456";
"#"會使后面的and password = "123456"變成注釋,這個語句會查詢出所有用戶,所以驗證通過,成功登陸了系統。
防范
一般來說,從以下幾點防范sql注入。
-
校驗用戶的輸入
拿到用戶數據后,可以通過正則表達式,限制長度,對引號和"-","#"進行轉換或過濾等。
-
使用預處理語句
不要使用動態拼裝SQL,而是使用參數化的SQL或者直接使用存儲過程進行數據查詢存取。
比如之前的例子,在mybatis中,如果使用#{}代替${},編寫的sql如下
select * from user where name = #{name} and password = #{password};這句話會在程序運行時會先編輯成帶參數的sql語句
select * from user where name = ? and password = ?;當用戶輸入 "xxx" or 1 # 時,實際語句
select * from user where name = "'xxx' or 1 #" and password = "123456";這樣是查不到用戶的,所以登陸失敗。
當然有些地方必須使用$,比如order by name desc,這里的name desc都是傳參進來的,直接按照參數字符串本身的含義。
select * from gooods order by ${param};這種情況下,我們必須嚴格控制傳進來的參數。后台最好寫方法確保參數正常。比如
//檢驗排序字段 private String orderColumn(String orderColumn){ //定義或獲取可用排序字段集 //判定外部傳進來的參數字段是否存在於字段集中 //如果存在,返回正確結果字段,不存在可拋出錯誤。 } //檢驗排序順序 private String orderStringtoOrder(String orderString){ String order = "asc"; if(!orderString.isEmpty() && orderString.equals("desc")){ order = "desc"; } return order; }嚴格控制排序參數的正確性。
-
最小權限策略
不要使用管理員權限的數據庫連接,為每個應用使用單獨的權限有限的數據庫連接,保證其正常使用即可。
這么做的好處是,即使當前用戶被攻破了,入侵者只能獲取極小一部分權限,防止危害擴大。
-
加密信息
不要把密碼等機密信息明文存放,加密或者hash掉密碼和敏感的信息。
-
異常提醒
有些應用直接返回了異常信息給用戶頁面,這是很不安全的,通過這些異常信息,使用者可以知道程序棧,數據庫類型甚至版本號等信息,這將有利於其進一步攻擊。
應用的異常信息應該給出盡可能少的提示,最好使用自定義的錯誤信息對原始錯誤信息進行包裝,把異常信息存放在獨立的表中。
參考
參考書籍: 白帽子講Web安全
參考鏈接: http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
http://blog.csdn.net/baidu_24024601/article/details/51957270
http://www.cnblogs.com/lovesong/p/5248483.html
http://blog.csdn.net/stilling2006/article/details/8526458
http://blog.csdn.net/baochao95/article/details/52025180
