首页 > 代码库 > XSS研究2-来自内部的XSS攻击的防范

XSS研究2-来自内部的XSS攻击的防范

引入:
前面我们分2篇文章分别探讨了来自外部的XSS攻击和来自内部的XSS攻击,现在我们来专门探讨如何防范来自内部的XSS攻击。
 
实践:
 http://www.cnblogs.com/crazylqy/p/4146740.html 文章中可以看出,主要的攻击手段无外乎是发送了一段恶意脚本到受害者机器上去运行,所以我们的思路就是,如何让这段脚本失效。
 
因为脚本的组成部分是<script>和</script>,而这其中最主要的是大于号和小于号字符,所以我们只要在请求中,把大于号字符,小于号字符处理下,让其没办法通过 Http发送到受害者机器上,当然就没办法在受害者机器上组成一段恶意脚本了。但是如果我们的内容中真的有大于号和小于号,并且作为内容的一部分而最终目的并不是入侵怎么办?我们只要吧大于号,小于号进行全角化处理就可以了,因为只有half-width的大于号和小于号能组成<script>,</script>,而full-width的大于号和小于号是不可以被对待成<script>,</script>的。
 
读者可能又问,现在就算你不让我输入大于号和小于号,但是我可以以unicode字符的形式输入这些字符啊,比如大于号叫 &#62; 小于号叫&#60; 所以我只要在恶意脚本中所有用到大于小于的地方全部用这些替换,一样可以达到入侵的目的。 所以,我们必须对于这种&,#还有其他特殊字符也进行处理。
 
综上所述,我们大概明白怎么做了,我们只要做一个过滤器,然后把这些特殊字符都过滤掉就可以了。
 
web.xml中定义一个过滤器:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <display-name>XSSDemo</display-name>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    <filter>
        <filter-name>XSS Filter</filter-name>
        <filter-class>com.charles.study.XSSFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>XSS Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
</web-app>

然后我们定义一个 XSSHttpServletRequestWrapper,它是HttpServletRequest的封装器,其中会吧一些特殊字符全部处理掉,被处 理的特殊字符就是那些会造成script的字符,吧他们全部“全角化”,来避免植入恶意代码,这是通过覆写getParameter()和 getHeader()方法来实现的:

package com.charles.study;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
 * This is the HTTPServletRequestWrapper class ,which overrides the default implementation
 * of getParameter() and getHeader() method ,it will handle the characters that may cause XSS
 * @author charles.wang
 *
 */
public class XSSHttpServletRequestWrapper extends HttpServletRequestWrapper {
    public XSSHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
     /**
     * Override the original getParameter() method ,
     * so that it can filter all the parameter name and parameter value
     * then use replace the special character that may cause XSS attack
     */
    @Override
    public String getParameter(String name) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
        String value = http://www.mamicode.com/super.getParameter(encodeXSS(name));
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
        //the following sentences will be replaced by logging sentence in actual project  
        System.out.println("The original value received from getParameter() is:"+value);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
        if (value != null) {
            value = http://www.mamicode.com/encodeXSS(value);
        }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
        //the following sentences will be replaced by logging sentence in actual project  
        System.out.println("After handling XSS ,the actual value is:"+value);
        System.out.println();
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
        return value;
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
    /**
     * Override the original getHeader() method ,
     * so that it can filter all the parameter name and parameter value
     * then use replace the special character that may cause XSS attack
     */
    @Override
    public String getHeader(String name) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
        String value = http://www.mamicode.com/super.getHeader(encodeXSS(name));
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
        //the following sentences will be replaced by logging sentence in actual project  
        System.out.println("The original value received from getHeader() is:"+value);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
        if (value != null) {
            value = http://www.mamicode.com/encodeXSS(value);
        }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
        //the following sentences will be replaced by logging sentence in actual project  
        System.out.println("After handling XSS ,the actual value is:"+value);
        System.out.println();
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
        return value;
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
    /**
     * replace all the characters that may cause XSS attack from half-width character
     * to full-width character
     *
     * @param s
     * @return
     */
    private String encodeXSS(String s) {
        if (s == null || "".equals(s)) {
            return s;
        }
        StringBuilder sb = new StringBuilder(s.length() + 16);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
            //handle the ‘<‘ and ‘>‘ which can be used for constructing <script> and </script>
            case ‘>‘:
                sb.append(‘>‘);
                break;
            case ‘<‘:
                sb.append(‘<‘);
                break;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
            //since the html can support the characters using $#number format
            //so here also need to escape ‘#‘,‘&‘ and quote symbol
            case ‘\‘‘:
                sb.append(‘‘‘);
                break;
            case ‘\"‘:
                sb.append(‘“‘);
                break;
            case ‘&‘:
                sb.append(‘&‘);
                break;
            case ‘\\‘:
                sb.append(‘\‘);
                break;
            case ‘#‘:
                sb.append(‘#‘);
                break;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
            //if not the special characters ,then output it directly  
            default:
                sb.append(c);
                break;
            }
        }
        return sb.toString();
    }
}

最后,我们来定义过滤器的实现,我们在doFilter()方法体中会吧所有的Http请求包装为我们自定义的包装器,这样所有当请求相关内容时候,我们包装器中就会对有可能引起XSS攻击的特殊字符处理,从而预防XSS 攻击。

package com.charles.study;
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;
/**
 * XSSFilter that can be used to filter invalid character which may cause XSS attack
 * @author charles.wang
 *
 */
public class XSSFilter implements Filter {
                                                                                                                                                                                                                                                                                                                                                                                      
    @Override
    public void destroy() {
    }
    /**
     * now the doFilter will filter the request ,using the Wrapper class to wrap the request
     * and in the wrapper class, it will handle the XSS issue
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
         XSSHttpServletRequestWrapper xssRequest = new XSSHttpServletRequestWrapper(
                    (HttpServletRequest) request);
            chain.doFilter(xssRequest, response);
    }
    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }
}

我们基于上面的实现来做例子,我们回忆以前文章,假如在恶意页面的输入框中输 入<script>alert("Attack");</script>时候,在受害者页面会弹出一个alert对话框,也就是 说这个恶意js在受害者的自己域上执行了从而达到内部XSS攻击的目的。
 
那么现在呢?假如我们在页面中输入同样的脚本:

因为我们启用了XSS的过滤器,所以他们会自动吧这些大于小于转为全角,从而破坏其形成一段脚本,我们来看下服务器日志:

因为做了半角到全角的转换,所以最终页面不会弹出那个alert了,而且正确的显示了:
 


 
总结:
其实这种解决方案很common,其思路就是类似于海关检查,大家都知道爆炸物和有威胁的物品(恶意脚本)大多数都是硫磺,金属(‘<‘,‘>‘)等等,那么我只要在过海关的时候(用户提交输入内容框)对这些危险物品进行清查,该扣留的扣留,该销毁的销毁(我们的encodeXSS方法,吧这些< ,>全部全角化)) ,这样就算这个人通过了检查,他因为没有了硫磺和金属物品,所以他没办法制作有威胁的炸弹了(到我们例子就是我们把特殊字符全部全角化了之后,它就不再能形成一个脚本了),于是达到防止内部XSS攻击的结果。

 

 

 

 

 


 

还可以换成HTML转义字符

package cn.richinfo.answerclub.filter;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 *
 * @author liyuan
 *
 */
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 = http://www.mamicode.com/super.getParameter(xssEncode(name));
        if (value != null) {
            value = http://www.mamicode.com/xssEncode(value);
        }
        return value;
    }

    /**
     * 覆盖getHeader方法,将参数名和参数值都做xss过滤。<br/>
     * 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/>
     * getHeaderNames 也可能需要覆盖
     */
    @Override
    public String getHeader(String name) {

        String value = http://www.mamicode.com/super.getHeader(xssEncode(name));
        if (value != null) {
            value = http://www.mamicode.com/xssEncode(value);
        }
        return value;
    }

    /**
     * 将容易引起xss漏洞的半角字符直接替换成全角字符
     *
     * @param s
     * @return
     */
    private static String xssEncode(String s) {
        if (s == null || s.isEmpty()) {
            return s;
        }

        s = s.replaceAll("&", "&amp;");
        s = s.replaceAll("\"", "&quot;");
        s = s.replaceAll("<", "&lt;");
        s = s.replaceAll(">", "&gt;");
        s = s.replaceAll("%3C", "&lt;");
        s = s.replaceAll("%3E", "&gt;");
        
        s = s.replaceAll("%27", "");
        s = s.replaceAll("%22", "");
        s = s.replaceAll("%3E", "");
        s = s.replaceAll("%3C", "");
        s = s.replaceAll("%3D", "");
        s = s.replaceAll("%2F", "");
        s = regexReplace("^>", "", s);

        s = regexReplace("<([^>]*?)(?=<|$)", "&lt;$1", s);
        s = regexReplace("(^|>)([^<]*?)(?=>)", "$1$2&gt;<", s);

        //s = s.replaceAll("\\|", "");
        s = s.replaceAll("alert", "");
        s = s.replaceAll("STYLE=", "");
        s = s.replaceAll("<iframe", "");
        s = s.replaceAll("<script", "");
        s = s.replaceAll("<IMG", "");
        return s;
    }

    private static String regexReplace(String regex_pattern,
            String replacement, String s) {
        Pattern p = Pattern.compile(regex_pattern);
        Matcher m = p.matcher(s);
        return m.replaceAll(replacement);
    }

    /**
     * 获取最原始的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;
    }
}

XSS研究2-来自内部的XSS攻击的防范