【JAVA-WEB常見漏洞-XSS漏洞】

安全小哥發表於2020-11-23

XSS漏洞

攻擊者利用XSS(Cross-site scripting)漏洞攻擊可以在使用者的瀏覽器中執行JS惡意指令碼,XSS攻擊可以實現使用者會話劫持釣魚攻擊惡意重定向點選劫持掛馬XSS蠕蟲等,XSS攻擊型別分為:反射型儲存型DOM型

 

1. 反射型XSS攻擊

示例 - 存在反射型XSS的xss.jsp程式碼:

<%=request.getParameter("input")%>

攻擊者通過傳入惡意的input引數值可以在使用者瀏覽器中注入一段JavaScript指令碼。

示例 - 注入XSS程式碼:

<script>alert('xss');</script>

瀏覽器請求:http://localhost:8000/modules/servlet/xss.jsp?input=%3Cscript%3Ealert(%27xss%27)%3B%3C/script%3E;%3C/script%3E)。

 

2. 儲存型XSS攻擊

示例 - 存在儲存型XSS的guestbook.jsp程式碼:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="java.util.*" %>
<%
    String username = request.getParameter("username");
    String content = request.getParameter("content");

    String guestBookKey = "GUEST_BOOK";
    List<Map<String, String>> comments = new ArrayList<Map<String, String>>();

    if (content != null) {
        Object obj = application.getAttribute(guestBookKey);

        if (obj != null) {
            comments = (List<Map<String, String>>) obj;
        }

        Map<String, String> comment = new HashMap<String, String>();
        String              ip      = request.getHeader("x-real-ip");

        if (ip == null) {
            ip = request.getRemoteAddr();
        }

        comment.put("username", username);
        comment.put("content", content);
        comment.put("ip", ip);
        comment.put("date", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        comments.add(comment);

        application.setAttribute(guestBookKey, comments);
    }
%>
<html>
<head>
    <title>留言板</title>
</head>
<style>
    * {
        margin: 0;
        padding: 0;
    }
</style>
<body>
<div style="border: 1px solid #C6C6C6;">
    <div style="text-align: center;">
        <h2>線上留言板</h2>
    </div>
    <div>
        <dl>
            <%
                Object obj = application.getAttribute(guestBookKey);

                if (obj instanceof List) {
                    comments = (List<Map<String, String>>) obj;

                    for (Map<String, String> comment : comments) {
            %>
            <dd>
                <div style="min-height: 50px; margin: 20px; border-bottom: 1px solid #9F9F9F;">
                    <p><B><%=comment.get("username")%>
                    </B>[<%=comment.get("ip")%>] 於 <%=comment.get("date")%> 發表回覆:</p>
                    <p style="margin: 15px 0 5px 0; font-size: 12px;">
                    <pre><%=comment.get("content")%></pre>
                    </p>
                </div>
            </dd>
            <%
                    }
                }
            %>
        </dl>
    </div>
    <div style="background-color: #fff; border: 1px solid #C6C6C6;">
        <form action="#" method="POST" style="margin: 20px;">
            暱稱: <input type="text" name="username" style="width:250px; height: 28px;"/><br/><br/>
            <textarea name="content" style="overflow: auto;width: 100%; height: 250px;"></textarea>
            <input type="submit" value="提交留言" style="margin-top: 20px; width: 80px; height: 30px;"/>
        </form>
    </div>
</div>
</body>
</html>

訪問:http://10.10.99.2:8000/modules/servlet/guestbook.jsp,並在留言內容出填入xss測試程式碼,如下:

提交留言後頁面會重新整理,並執行留言的xss程式碼:

 

3. DOM XSS

示例 - dom.jsp程式碼:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Date: <span style="color: red;"></span>
<input type="hidden" value="<%=request.getParameter("date")%>" />
<script>
    var date = document.getElementsByTagName("input")[0].value;
    document.getElementsByTagName("span")[0].innerHTML = date;
</script>

正常請求測試:http://localhost:8000/modules/servlet/dom.jsp?date=2020-11-15%2015:57:22

XSS攻擊測試:http://localhost:8000/modules/servlet/dom.jsp?date=%3Cimg%20src=1%20οnerrοr=alert(/xss/)%20/%3E%20/%3E)

 

4. XSS防禦

XSS最為常見的處理方式是轉義特殊字元,後端程式在接受任何使用者輸入的引數時都應當優先考慮是否會存在XSS攻擊。

4.1 htmlspecialchars

在PHP中通常會使用htmlspecialchars函式會將一些可能有攻擊威脅的字串轉義為html實體編碼,這樣可以有效的避免XSS攻擊。

示例 - htmlspecialchars 轉義

 

字元替換後
& (& 符號)&amp;
" (雙引號)&quot;
' (單引號)&#039;或者&apos;
< (小於)&lt;
> (大於)&gt;

在Java中雖然沒有內建如此簡單方便的函式,但是我們可以通過字串替換的方式實現類似htmlspecialchars函式的功能。

/**
 * 實現htmlSpecialChars函式把一些預定義的字元轉換為HTML實體編碼
 *
 * @param content 輸入的字串內容
 * @return HTML實體化轉義後的字串
 */
public static String htmlSpecialChars(String content) {
  if (content == null) {
    return null;
  }

  char[]        charArray = content.toCharArray();
  StringBuilder sb        = new StringBuilder();

  for (char c : charArray) {
    switch (c) {
      case '&':
        sb.append("&amp;");
        break;
      case '"':
        sb.append("&quot;");
        break;
      case '\'':
        sb.append("&#039;");
        break;
      case '<':
        sb.append("&lt;");
        break;
      case '>':
        sb.append("&gt;");
        break;
      default:
        sb.append(c);
        break;
    }
  }

  return sb.toString();
}

在儲存或者輸出請求引數的時候使用該方法過濾即可實現XSS防禦。

4.2 全域性的XSSFilter

package com.anbai.sec.vuls.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;

public class XSSFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;

        // 建立HttpServletRequestWrapper,包裝原HttpServletRequest物件,示例程式只重寫了getParameter方法,
        // 應當考慮如何過濾:getParameter、getParameterValues、getParameterMap、getInputStream、getReader
        HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request) {
            public String getParameter(String name) {
                // 獲取引數值
                String value = super.getParameter(name);

                // 簡單轉義引數值中的特殊字元
                return value.replace("&", "&amp;").replace("<", "&lt;").replace("'", "&#039;");
            }
        };

        chain.doFilter(requestWrapper, resp);
    }

    @Override
    public void destroy() {

    }

}

web.xml新增XSSFilter過濾器:

<!-- XSS過濾器 -->
<filter>
  <filter-name>XSSFilter</filter-name>
  <filter-class>com.anbai.sec.vuls.filter.XSSFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>XSSFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

請求XSS示例程式:http://localhost:8000/modules/servlet/xss.jsp?input=%3Cscript%3Ealert(%27xss%27);%3C/script%3E;%3C/script%3E)

經過全域性過濾器轉義後的引數就不會再帶有XSS攻擊能力了。

4.3 RASP XSS攻擊防禦

RASP可以實現類似於全域性XSSFilter的請求引數過濾功能,比較穩定的一種方式是Hook到javax.servlet.ServletRequest介面的實現類的getParameter/getParameterValues/getParameterMap等核心方法,在該方法return之後插入RASP的檢測程式碼。這種實現方案雖然麻煩,但是可以避免觸發Http請求引數解析問題(Web應用無法獲取getInputStream和亂碼等問題)。

示例 - RASP對getParameter返回值Hook示例:

反射型的XSS防禦相對來說比較簡單,直接禁止GET引數中出現<>標籤,只要出現就理解攔截,如:

http://localhost:8000/modules/servlet/xss.jsp?input=<script>alert('xss');</script>

過濾或攔截掉<>input引數就不再具有攻擊性了。

但是POST請求的XSS引數就沒有那麼容易過濾了,為了兼顧業務,不能簡單的使用htmlSpecialChars的方式直接轉義特殊字元,因為很多時候應用程式是必須支援HTML標籤的(如:<img>、<h1>等)。RASP在防禦XSS攻擊的時候應當儘可能的保證使用者的正常業務不受影響,否則可能導致使用者無法業務流程阻塞或崩潰。

為了支援一些常用的HTML標籤和HTML標籤屬性,RASP可以通過詞法解析的方式,將傳入的字串引數值解析成HTML片段,然後分析其中的標籤和屬性是否合法即可。

4.4 RASP XSS防禦能力測試

4.4.1 惡意的HTML標籤屬性XSS測試

示例 - 提交帶有XSS攻擊的Payload:

<img src='1.jpg' width='10px' height='10px' onerror='alert(/xss/);' />

請求示例地址:http://localhost:8000/modules/servlet/guestbook.jsp,並填寫XSS攻擊程式碼,如下圖:

RASP能夠正確識別並攔截XSS攻擊:

4.4.2 XSS富文字檢測測試

RASP如果要實現精確的XSS檢測能力就必須能夠正確的識別出使用者傳入的資料到底是否合法,經過HTML詞法分析後RASP能夠正確認識使用者傳入的引數值是否是包含了惡意的HTML標籤或者屬性。

示例 - 使用者在留言板中帶圖片回覆:

示例 - 使用者在留言板中回覆被註釋的HTML片段:

 

 

經測試,RASP對XSS攻擊防禦能力正常,能夠識別合法的HTML和javascript程式碼(DOM類XSS佔不支援)。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相關文章