標籤:
1. XSS攻擊原理
XSS原稱為CSS(Cross-Site Scripting),因為和層疊樣式表(Cascading Style Sheets)重名,所以改稱為XSS(X有未知的含義,還有擴充的含義)。XSS攻擊涉及到三方:攻擊者,使用者,web server。使用者通過瀏覽器來訪問web server上的網頁,XSS攻擊就是攻擊者通過各種辦法,在使用者訪問的網頁中插入自己的指令碼,讓其在使用者訪問網頁時在瀏覽器中進行執行。攻擊者通過插入的指令碼的執行,來獲得使用者的資訊,比如cookie,發送到攻擊者自己的網站。所以稱為跨站指令碼攻擊。XSS可以分為反射型XSS和持久性XSS。(一句話,XSS就是在使用者的瀏覽器中執行攻擊者自己定製的指令碼。)
1.1 反射型XSS
反射性XSS,也就是非持久性XSS。使用者點擊攻擊連結,伺服器解析後響應,在返回的響應內容中出現攻擊者的XSS代碼,被瀏覽器執行。一來一去,XSS攻擊指令碼被web server反射回來給瀏覽器執行,所以稱為反射型XSS。
特點:
1> XSS攻擊代碼非持久性,也就是沒有儲存在web server中,而是出現在URL地址中;
2> 非持久性,那麼攻擊方式就不同了。一般是攻擊者通過郵件,聊天軟體等等方式發送攻擊URL,然後使用者點擊來達到攻擊的;
1.2 持久型XSS
區別就是XSS惡意代碼儲存在web server中,這樣,每一個訪問特定網頁的使用者,都會被攻擊。
特點:
1> XSS攻擊代碼儲存於web server上;
2> 攻擊者,一般是通過網站的留言、評論、部落格、日誌等等功能(所有能夠向web server輸入內容的地方),將攻擊代碼儲存到web server上的;
2. XSS 存在的原因
XSS 存在的根本原因是,對URL中的參數,對使用者輸入提交給web server的內容,沒有進行充分的過濾。如果我們能夠在web程式中,對使用者提交的URL中的參數,和提交的所有內容,進行充分的過濾,將所有的不合法的參數和輸入內容過濾掉,那麼就不會導致“在使用者的瀏覽器中執行攻擊者自己定製的指令碼”。
但是,其實充分而完全的過濾,實際上是無法實現的。因為攻擊者有各種各樣的神奇的,你完全想象不到的方式來繞過伺服器端的過濾,最典型的就是對URL和參數進行各種的編碼,比如escape, encodeURI, encodeURIComponent, 16進位,10進位,來繞過XSS過濾。那麼我們如何來防禦XSS呢?
3. XSS 攻擊的防禦
XSS防禦的總體思路是:對輸入(和URL參數)進行過濾,對輸出進行編碼。
也就是對提交的所有內容進行過濾,對url中的參數進行過濾,過濾掉會導致指令碼執行的相關內容;然後對動態輸出到頁面的內容進行html編碼,使指令碼無法在瀏覽器中執行。雖然對輸入過濾可以被繞過,但是也還是會攔截很大一部分的XSS攻擊。
3.1 對輸入和URL參數進行過濾(白名單和黑名單)
下面貼出一個常用的XSS filter的實現代碼:
public class XssFilter implements Filter { public void init(FilterConfig config) throws ServletException {} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest)request); chain.doFilter(xssRequest, response); } public void destroy() {}}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { HttpServletRequest orgRequest = null; public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); orgRequest = request; } /** * 覆蓋getParameter方法,將參數名和參數值都做xss過濾。<br/> * 如果需要獲得原始的值,則通過super.getParameterValues(name)來擷取<br/> * getParameterNames,getParameterValues和getParameterMap也可能需要覆蓋 */ @Override public String getParameter(String name) { String value = super.getParameter(xssEncode(name)); if (value != null) { value = xssEncode(value); } return value; } /** * 覆蓋getHeader方法,將參數名和參數值都做xss過濾。<br/> * 如果需要獲得原始的值,則通過super.getHeaders(name)來擷取<br/> * getHeaderNames 也可能需要覆蓋 */ @Override public String getHeader(String name) { String value = super.getHeader(xssEncode(name)); if (value != null) { value = xssEncode(value); } return value; } /** * 將容易引起xss漏洞的半形字元直接替換成全形字元 * * @param s * @return */ private static String xssEncode(String s) { if (s == null || s.isEmpty()) { return s; } StringBuilder sb = new StringBuilder(s.length() + 16); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case ‘>‘: sb.append(‘>‘);// 全形大於符號 break; case ‘<‘: sb.append(‘<‘);// 全形小於符號 break; case ‘\‘‘: sb.append(‘‘‘);// 全形單引號 break; case ‘\"‘: sb.append(‘“‘);// 全形雙引號 break; case ‘&‘: sb.append(‘&‘);// 全形 break; case ‘\\‘: sb.append(‘\‘);// 全形斜線 break; case ‘#‘: sb.append(‘#‘);// 全形井號 break; case ‘%‘: // < 字元的 URL 編碼形式表示的 ASCII 字元(十六進位格式) 是: %3c processUrlEncoder(sb, s, i); break; default: sb.append(c); break; } } return sb.toString(); } public static void processUrlEncoder(StringBuilder sb, String s, int index){ if(s.length() >= index + 2){ if(s.charAt(index+1) == ‘3‘ && (s.charAt(index+2) == ‘c‘ || s.charAt(index+2) == ‘C‘)){ // %3c, %3C sb.append(‘<‘); return; } if(s.charAt(index+1) == ‘6‘ && s.charAt(index+2) == ‘0‘){ // %3c (0x3c=60) sb.append(‘<‘); return; } if(s.charAt(index+1) == ‘3‘ && (s.charAt(index+2) == ‘e‘ || s.charAt(index+2) == ‘E‘)){ // %3e, %3E sb.append(‘>‘); return; } if(s.charAt(index+1) == ‘6‘ && s.charAt(index+2) == ‘2‘){ // %3e (0x3e=62) sb.append(‘>‘); return; } } sb.append(s.charAt(index)); } /** * 擷取最原始的request * * @return */ public HttpServletRequest getOrgRequest() { return orgRequest; } /** * 擷取最原始的request的靜態方法 * * @return */ public static HttpServletRequest getOrgRequest(HttpServletRequest req) { if (req instanceof XssHttpServletRequestWrapper) { return ((XssHttpServletRequestWrapper) req).getOrgRequest(); } return req; }}
然後在web.xml中配置該filter:
<filter> <filter-name>xssFilter</filter-name> <filter-class>com.xxxxxx.filter.XssFilter</filter-class> </filter> <filter-mapping> <filter-name>xssFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
主要的思路就是將容易導致XSS攻擊的邊角字元替換成全形字元。< 和 > 是指令碼執行和各種html標籤需要的,比如 <script>,& 和 # 以及 % 在對URL編碼試圖繞過XSS filter時,會出現。我們說對輸入的過濾分為白名單和黑名單。上面的XSS filter就是一種黑名單的過濾,黑名單就是列出不能出現的對象的清單,一旦出現就進行處理。還有一種白名單的過濾,白名單就是列出可被接受的內容,比如規定所有的輸入只能是“大小寫26個英文字母和10個數字,還有-和_”,所有其他的輸入都是非法的,會被拋棄掉。很顯然如此嚴格的白名單是可以100%攔截所有的XSS攻擊的。但是現實情況一般是不能進行如此嚴格的白名單過濾的。
對於輸入,處理使用XSS filter之外,對於每一個輸入,在用戶端和伺服器端還要進行各種驗證,驗證是否合法字元,長度是否合法,格式是否正確。在用戶端和服務端都要進行驗證,因為用戶端的驗證很容易被繞過。其實這種驗證也分為了黑名單和白名單。黑名單的驗證就是不能出現某些字元,白名單的驗證就是只能出現某些字元。盡量使用白名單。
3.2 對輸出進行編碼
在輸出資料之前對潛在的威脅的字元進行編碼、轉義是防禦XSS攻擊十分有效措施。如果使用好的話,理論上是可以防禦住所有的XSS攻擊的。
對所有要動態輸出到頁面的內容,通通進行相關的編碼和轉義。當然轉義是按照其輸出的上下文環境來決定如何轉義的。
1> 作為body文本輸出,作為html標籤的屬性輸出:
比如:<span>${username}</span>, <p><c:out value="${username}"></c:out></p>
<input type="text" value="${username}" />
此時的轉義規則如下:
< 轉成 <
> 轉成 >
& 轉成 &
" 轉成 "
‘ 轉成 '
2> javascript事件
<input type="button" onclick=‘go_to_url("${myUrl}");‘ />
除了上面的那些轉義之外,還要附加上下面的轉義:
\ 轉成 \\
/ 轉成 \/
; 轉成 ;(全形;)
3> URL屬性
如果 <script>, <style>, <imt> 等標籤的 src 和 href 屬性值為動態內容,那麼要確保這些url沒有執行惡意串連。
確保:href 和 src 的值必須以 http://開頭,白名單方式;不能有10進位和16進位編碼字元。
4. 總結下
XSS攻擊存取方法:對輸入(和URL參數)進行過濾,對輸出進行編碼;白名單和黑名單結合;
XSS 防禦方法總結