XSS攻击与防御

XSS attack and defense

Posted by Static on March 24, 2018

什么是XSS攻击?

XSS攻击是Web攻击中最常见的攻击方法之一,它是通过对网页注入可执行代码且成功地被浏览器执行,达到攻击的目的,形成了一次有效XSS攻击,一旦攻击成功,它可以获取用户的联系人列表,然后向联系人发送虚假诈骗信息,可以删除用户的日志等等,有时候还和其他攻击方式同时实施比如SQL注入攻击服务器和数据库、Click劫持、相对链接劫持等实施钓鱼,它带来的危害是巨大的,是web安全的头号大敌。

XSS有什么危害?

盗取用户 Cookie、破坏页面结构、导航到恶意网站、获取浏览器信息、携带木马等。

重点来了!!!

XSS漏洞按照攻击手段可以分为以下三种:

1. Reflect XSS(非持久型)

用户点击攻击链接,服务器解析后响应,在返回的响应内容中出现攻击者的XSS代码,被浏览器执行。一来一去,XSS攻击脚本被web server反射回来给浏览器执行,所以称为反射型XSS。 特点:

  1. XSS攻击代码非持久性,也就是没有保存在webserver中,而是出现在URL地址中。
  2. 非持久性,那么攻击方式就不同了。一般是攻击者通过邮件,聊天软件等等方式发送攻击URL,然后用户点击来达到攻击的。

比如:目录结构

2. Stored XSS(持久型)

Stored XSS是存储式XSS漏洞,由于其攻击代码已经存储到服务器上或者数据库中,比如发布一篇文章包含恶意代码,其他用户浏览时将执行恶意脚本,所以受害者是很多人。 特点:

  1. XSS攻击代码存储于web server上。
  2. 攻击者,一般是通过网站的留言、评论、博客、日志等等功能(所有能够向web server输入内容的地方),将攻击代码存储到web server上的。

3. DOM based XSS

基于DOM的XSS,也就是webserver不参与,仅仅涉及到浏览器的XSS。比如根据用户的输入来动态构造一个DOM节点,如果没有对用户的输入进行过滤,那么也就导致XSS攻击的产生。

为何防御?

XSS防御的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码。 也就是对提交的所有内容进行过滤,对url中的参数进行过滤,过滤掉会导致脚本执行的相关内容;然后对动态输出到页面的内容进行html编码,使脚本无法在浏览器中执行。虽然对输入过滤可以被绕过,但是也还是会拦截很大一部分的XSS攻击。

前端

1. 在javascript中加入多个tab键,比如:
<IMG SRC="javascript:alert('XSS');">

得到:

< IMG SRC="jav ascript:alert('XSS');" >
2. 在javascript中的每个字符间加入回车换行符,得到:
<IMG SRC="j\r\na\r\nv\r\n\r\na\r\ns\r\nc\r\nr\r\ni\r\np\r\nt\r\n:alert('XSS');">
3. 采用编码的方式,也是现在大部分网站防御的方法,比如:
<script>alert('XSS');</script>

编码得到:

&lt;script&gt;alert&quot;XSS&quot;;&lt;/script&gt;

主要对6个特殊字符进行编码:

before after
& &amp;
< &lt;
> &gt;
" &quot;
' &#x27;
/ &#x2f;
4. 检测输入内容,发现有恶意脚本提示,比如淘宝搜索:

目录结构

发现XSS,提示:

目录结构

5. 使用VueJS、ReactJS、AngularJS中的SCE来防止XSS攻击的方法

后端

1. 在pom.xml里添加
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4<version>
</dependency>

使用StringEscapeUtils.escapeHtml4(…);

2. 在web.xml加一个filter
<filter>  
    <filter-name>XssEscape</filter-name>  
    <filter-class>top.qilixiang.util.filter.XssFilter</filter-class>  
</filter>  
<filter-mapping>  
    <filter-name>XssEscape</filter-name>  
    <url-pattern>/*</url-pattern>  
    <dispatcher>REQUEST</dispatcher>  
</filter-mapping>  
3. 添加XssFilter.java和XssHttpServletRequestWrapper.java文件

XssFilter.java

package top.qilixiang.util.filter;  
  
import java.io.IOException;  
  
import javax.servlet.Filter;  
import javax.servlet.FilterChain;  
import javax.servlet.FilterConfig;  
import javax.servlet.ServletException;  
import javax.servlet.ServletRequest;  
import javax.servlet.ServletResponse;  
import javax.servlet.http.HttpServletRequest;  
  
public class XssFilter implements Filter {  
      
    @Override  
    public void init(FilterConfig filterConfig) throws ServletException {  
    }  
  
    @Override  
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException,ServletException {  
        chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);  
    }  
  
    @Override  
    public void destroy() {  
    }  
}

XssHttpServletRequestWrapper.java

package top.qilixiang.util.filter;  
  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletRequestWrapper;  
  
import org.apache.commons.lang3.StringEscapeUtils;  
  
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {  
  
    public XssHttpServletRequestWrapper(HttpServletRequest request) {  
        super(request);  
    }  
  
    @Override  
    public String getHeader(String name) {  
        return StringEscapeUtils.escapeHtml4(super.getHeader(name));  
    }  
  
    @Override  
    public String getQueryString() {  
        return StringEscapeUtils.escapeHtml4(super.getQueryString());  
    }  
  
    @Override  
    public String getParameter(String name) {  
        return StringEscapeUtils.escapeHtml4(super.getParameter(name));  
    }  
  
    @Override  
    public String[] getParameterValues(String name) {  
        String[] values = super.getParameterValues(name);  
        if(values != null) {  
            int length = values.length;  
            String[] escapseValues = new String[length];  
            for(int i = 0; i < length; i++){  		
                escapseValues[i] = StringEscapeUtils.escapeHtml4(values[i]);  
            }  
            return escapseValues;  
        }  
        return super.getParameterValues(name);  
    }  
      
} 

扩展:也可以将半角字符替换成全角字符(不推荐)

package top.qilixiang.util;

/**
 * Created by wangzhx on 2018/3/23 18:34.
 */
public class StringUtils {
    /**
     * 半角字符替换成全角字符
     *
     * @param input
     * @return
     */
    public static String halfAngleChar2WideChar(String input) {
        if (input == null || input.isEmpty()) {
            return input;
        }
        StringBuilder sb = new StringBuilder(input.length() + 16);
        for (int i = 0; i < input.length(); i++) {
            char c = input.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, input, 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));
    }

}