怎样过滤跨站恶意脚本攻击(XSS)

时间:2022-10-28 23:33:37


什么是XSS?

XSS(Cross Site Scripting),即跨站脚本攻击,是一种常见于web application中的计算机安全漏洞。XSS通过在用户端注入恶意的可运行脚本,若服务器端对用户输入不进行处理,直接将用户输入输出到浏览器,则浏览器将会执行用户注入的脚本。

例如:有一个input输入框,要求用户输入名字,用户输入lily<script>alert("hello world")</script>,服务器接收到用户的输入,并且直接将用户输入输出到页面上,则会有hello world 的alert对话框出现。如果说这是XSS黑客的自娱自乐,那么盗取用户信息的XSS就不那么友好了。如果用户通过注入脚本,盗取你的用户信息如cookie的内容,仿冒正常用户,进行相应的操作,则危害会更大。获取用户的cookie信息非常简单,只需要使用js获取document.cookie,即可以得到。为了便于分析,我们将在用户输入名字框中输入:lily<script>alert(document.cookie)</script>,则你就可以看到你的cookie信息显示在alert对话框中了。

XSS的分类

根据XSS造成的影响,可以将XSS分为非持久型和持久型。

1.非持久型,也叫反射型XSS。通过GET和POST方法,向服务器端输入数据。用户输入的数据通常被放置在URL的query string中,或者是form 数据中。如果服务器端对输入的数据不进行过滤,验证或编码,就直接将用户输入的信息直接呈现给客户,则可能会造成反射型XSS。反射型XSS是比较普遍的XSS,其危害程度通常被认为较小。但是某些反射型XSS造成的后果会很严重,如在输入框的name中输入<meta http-equiv="refresh" content="5" />,服务器不加处理,将name的值直接送到浏览器,则浏览器会每5秒自动刷新一次。严重者会导致服务器崩溃。

2.持久型,也叫存储型XSS。通常是因为服务器端将用户输入的恶意脚本没有通过验证就直接存储在数据库,并且每次通过调用数据库的方式,将数据呈现在浏览器上。则该XSS跨站脚本攻击将一直存在。若其他用户访问该页面,则恶意脚本就会被触发,用于盗取其他用户的私人信息。

常用XSS方式分为以下几种:
1. 输入框中直接输入恶意脚本,如:

     ><script>alert(document.cookie)</script>

2. 输入框中输入html标签,在标签中嵌入恶意脚本,如src,href,css style等。

<IMG SRC="javascript:alert('XSS');">;
<BODY BACKGROUND="javascript:alert('XSS')">
<STYLE>li {list-style-image: url("javascript:alert('XSS')");}</STYLE><UL><LI>XSS</br>

3.将恶意脚本注入在event事件中,如onClick,onBlur,onMouseOver等事件。

<a onmouseover="alert(document.cookie)">xxs link</a>

4. 在remote style sheet,javascript中,如

<LINK REL="stylesheet" HREF="javascript:alert('XSS');">
<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT>

5. META 标签,如

<meta http-equiv="refresh" content="5" />
<META HTTP-EQUIV="Set-Cookie" Content="USERID=<SCRIPT>alert('XSS')</SCRIPT>">

如何预防以上两种XSS攻击?
1. 在输入流中截住form data中的恶意脚本
     研究两种XSS攻击,如反射型和存储型XSS攻击,其恶意脚本都是来自用户的输入。因此,可以使用过滤用户输入的方法对恶意脚本进行过滤。对简单的HTTP请求,一般使用GET和POST方法。当使用GET时,用户输入的数据将被放入地址栏的URL中,如:http://xxxx?name1=value1&name2=value2…..。其中,URL之后会放入键值对,对应name1的值为value1,name2的值为value2,等等。而对于POST方法,则用户输入数据仍然以name1=value1&name2=value2.。。。,但是数据键值对不会放进URL之后,而是放进了request的body中。因此,我们得到一个结论,所有的数据都存储在request中,我们可以截下进入server的每一个request,对用户的输入数据进行恶意脚本清理。其中有效的一个方法就是使用spring的filter。代码示例如下,在web.xml中加入以下代码段:
<filter>
        <filter-name>XSS</filter-name>
        <filter-class>com.springapp.domain.XssFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>XSS</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
其中XssFilter为自定义的class,该类必须实现Filter类,在XssFilter类中实现doFilter函数。

public class XssFilter implements Filter {
    private FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(new RequestWrapper((HttpServletRequest) request), new ResponseWrapper((HttpServletResponse) response));

    }

    @Override
    public void destroy() {
        this.filterConfig = null;
    }
}
     那么我们怎样对每个request都进行过滤操作呢?怎么样对用户输入的数据进行操作呢?form数据都存储在什么地方传给servlet的呢?
     通常情况下,可以通过调用request.getParameter来获得单个对应name对应的value。若form的传入参数中,一个name值对应多个value,则可以使用request.getParameterValues来获得name值的多个对应值。少数情况下,当你需要raw request的适合,可以使用getReader和getInputStream。
     但是我们可以发现,直接对httpServletRequest的parameter进行set操作会报错,因此需要使用HttpServletRequestWrapper。HttpServletRequestWrapper允许我们重写servletRequest的各种方法,以便对request进行直接操作。
     因此,我们可以在自定义的RequestWrapper中,对getParameter和getParameterValues进行重写,从而达到对各个用户输入的form参数的值进行过滤,滤掉form data中的恶意脚本。以下为示例代码:
public String[] getParameterValues(String parameter) {
        String[] values = super.getParameterValues(parameter);
        if (values==null) {
            return null;
        }
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = xssClean(values[i]);
        }
        return encodedValues;
    }

public String getParameter(String parameter) {
        String value = super.getParameter(parameter);
        if (value == null) {
            return null;
        }
        return xssClean(value);
    }

2. 在输入流中检测滤掉来自其他网站的URL中的恶意脚本
     当用户不小心点击了被其他黑客提供的假冒URL,则可能在该URL中注入恶意脚本。因此,也需要对这种情况进行处理。因此为确保其他在header中的恶意脚本,需要对request.getHeader进行重写。以下为例子:
public String getHeader(String name) {
        String value = super.getHeader(name);
        if (value == null)
            return null;
        return xssClean(value);

    }

3. xssClean函数怎样实现才可以过滤掉恶意脚本呢?
如果是java语言,推荐使用antisamy。使用antisamy进行XSS清理非常简单,只需要简单的几个步骤即可达到目的。
     1‘. 在pom.xml文件中加入antisamy的dependency,
     <dependency>
            <groupId>org.owasp.antisamy</groupId>
            <artifactId>antisamy</artifactId>
            <version>1.5.3</version>
        </dependency>
     2’. 加入了dependency之后,就可以在xssClean中加入antisamy对恶意脚本进行清理。其中policy.xml是白名单,policy.xml中规定了各个html元素所必须满足的条件。antisamy的精髓之处在于,使用policy文件来规定你的过滤条件,若输入字符串不满足policy文件中的条件,则会过滤掉字符中的恶意脚本,返回过滤后的结果。具体代码如下:
private String xssClean(String value) {
        AntiSamy antiSamy = new AntiSamy();
        try {
            final CleanResults cr = antiSamy.scan(value, Policy.getInstance("policy.xml"), AntiSamy.SAX);
            return cr.getCleanHTML();
        } catch (ScanException e) {
            e.printStackTrace();
        } catch (PolicyException e) {
            e.printStackTrace();
        }
        return value;
    }
这样,我们就将client端用户输入的request,在server端进行了拦截,并且进行了过滤。对于输出端,XSS的防御将在下一篇文章中进行详细介绍。
转载请注明出处,谢谢。