代码审计入门之XSS原理及分析

初识Java代码审计,所以分析比较稚嫩,大佬请绕行。本篇文章首先先来分析XSS,本篇文章的代码基于本人自己编写的留言板项目,项目未使用框架,框架还不太会,等学会了,再分析关于框架的。
需要了解Java代码审计前置,先看代码审计入门之前篇

0x00 概述

XSS漏洞是指对于和后端有交互的地方没有做参数的接收和输入输出过滤,导致恶意攻击者可以插入一些恶意的js语句来获取应用的敏感信息。
大多数通过白盒发现的XSS通常发生在将用户输入拼接构造页面元素,或直接输出在前端页面中。

0x01 挖掘技巧

XSS分为反射型,存储型和DOM型,挖掘的思路类似,注意前端构造的参数(元素或值)是否用户可控。
但白盒挖掘XSS不像之前的漏洞有明显的漏洞特征和危险函数,需要根据实际情况审计前端js、jsp、html文件。主要注意输入、输出的地方。
用户输入处用的比较多的是request.getParameter(param)、${param}直接获取用户输入,当然也有其他输入源,需根据代码实际情况分析判断;
输出的地方大部分用的是EL表达式,如果是DOM型的主要注意 location.、document.、window.*等关键字。

0x02 漏洞原理分析

XSS 是通过对网页插入可执行代码且成功地被浏览器 执行,达到攻击的目的,一般来说 XSS 的危害性没有 SQL 大,但是一次有效的 XSS 攻击可以做很多事情,比如获取 Cookies、获取用户的联系人列表、截屏、劫持等等。
根据服务端的后端代码不同,XSS 的种类也不相同,一般可以分为反射型、存储型以及和 DOM 型,接下来对这三种类型依次分析。

实验环境为自己编写的留言板(下载地址:http://www.sec00.cn/index.php/2019/12/04/850.html

1.反射型XSS

反射型XSS也被称为非持久性XSS,一般反射型XSS是用户直接在URL中提交一段可执行代码,最后直接在页面中输出,页面将提交的代码执行,形成XSS攻击,一般单纯的反射型XSS危害并不大,但是当XSS遇上跨站请求伪造(CSRF)就会形成可怕的蠕虫。

测试留言板:
搜索框--->搜索(script语句)--->语句生效,弹窗。此处存在反射型XSS

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

1.png

2.png

3.png

4.png

反射型XSS的执行条件及代码分析:

1.传递参数为用户可控,搜索页面如下,搜索框内参数为用户可控参数。
分析:搜索页面search.jsp,搜索框直接提交到本页面,get型传参。

2.1.png

2.2.png

2.3.png

2.参数未经过任何过滤或者过滤不完全,最终直接显示在前端页面,形式如下:
search.jsp页面,接受get传过来的参数word,直接就显示在页面,没有过滤,导致反射型XSS。

2.4.png

代码:
2.5.png

2.存储型XSS

存储型XSS又被称为持久性XSS,存储型XSS是恶意用户输入的可执行代码直接保存在数据库中,最后在某个页面被调用显示出来,最后执行。存储型XSS相对来说危害就非常大,一般恶意用户可通过存储型XSS去盗窃管理员的Cookie,从而获取管理员权限。

测试留言板:
登录--->留言(script语句)--->查看留言,插入的script语句生效,弹窗。此处存在存储型XSS。

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

3.1.png

3.2.png

3.3.png

存储型XSS的执行条件及代码分析:

1.传递参数为用户可控,如下图,这是一个简单的留言功能,文本框中的参数为用户可控:

3.4.png

分析:ly.jsp文件 --表单直接将留言内容提交至LyServlet。并未做过滤。

3.5.png

2.参数在传递过程中未经过任何有效过滤(或者存在过滤不全,会被绕过),并且能够存储在数据库(服务器)中。
分析:继续看代码,ly.jsp文件中form表单提交至LyServlet.java文件(处理留言的servlet),LyServlet获取传过来的参数content,直接就赋值给对象ly,然后ly对象调用ly()方法,全程无过滤。

3.6.png

分析:接着继续看ly()方法,LyDao.java文件,处理留言操作,操作数据库执行插入操作。
在ly()方法中,将刚刚接受到的值content ,直接拼接到SQL语句。没有对接受的参数做处理,导致XSS。

3.7.png

3.在前端页面显示留言内容,并且未做任何输出过滤(或者过滤不全),页面和代码如下:
分析:将从数据库获取的数据直接通过EL表达式的方式,显示在index.jsp页面。

3.8.png

3.9.png

后台查看数据库中存储的形式如下:

3.10.png

3.DOM型XSS

DOM型XSS的原理其实和反射型XSS差别并不大,只是可控参数拼接在DOM标签输出,页面如下:
url中word处为用户可控参数,当word是123时,DOM处显示123.

4.1.png

将word改成<img src=1 onerror=alert('xss')>,没有添加过滤,参数直接拼接在DOM中输出。

4.2.png

4.3.png

分析:dom.jsp中代码,获取get传过来的参数word,没有加锅炉,就拼接显示在id=a的div中,此处存在DOM型XSS。

4.4.png

挖掘DOM XSS可关注location.、document.、window.*等关键字。

0x03 漏洞防御

对于 XSS 漏洞,导致其产生的根本原因是对于输入和输出功能的过滤不完善,因此可以采用过滤的方法来防御 XSS 漏洞。
许多语言都有提供对HTML的过滤:

  • PHP的htmlentities()或是htmlspecialchars()。
  • Python的cgi.escape()。
  • ASP的Server.HTMLEncode()。
  • ASP.NET的Server.HtmlEncode()或功能更强的Microsoft Anti-Cross Site Scripting
    Library
  • Java的xssprotect(Open Source Library)。
  • Node.js的node-validator。

1、全局过滤器过滤

说全局过滤器前需要说明一下web.xml这个配置文件的作用。web.xml是java web 项目的一个重要的配置文件,但是web.xml文件并不是Java web工程必须的,web.xml文件的主要作用用来配置:欢迎页、servlet、filter等。

添加过滤器:

step1:做全局过滤器需要要用到 filter,因此首先要做的是来配置web.xml文件,直接添加内容如下:

<filter>  
        <filter-name>XssSafe</filter-name>  
        <filter-class>XssFilter</filter-class>  
</filter>  
<filter-mapping>  
        <filter-name>XssSafe</filter-name>  
        <url-pattern>/*</url-pattern>  
 </filter-mapping>

注意:我们的配置是/*而不是/< url-pattern>/</url-pattern> 会匹配到/login这样的路径型url,不会匹配到模式为*.jsp这样的后缀型url,而< url-pattern>/*</url-pattern>会匹配所有url:路径型的和后缀型的url(包括/login,*.jsp,*.js和*.html等)。

step2:编写过滤器的内容,这个网上有写好的,可以直接拿来用,如下:

step3:首先创建Filter文件,代码如下:
step3.png

//XssFilter实现:
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);
    }
}

step4:编写XssHttpServletRequestWrapper文件
创建文件XssHttpServletRequestWrapper,代码如下:
step4.png

//XssHttpServletRequestWrapper
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @SuppressWarnings("rawtypes")
    public Map<String,String[]> getParameterMap(){
        Map<String,String[]> request_map = super.getParameterMap();
        Iterator iterator = request_map.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry me = (Map.Entry)iterator.next();
            String[] values = (String[])me.getValue();
            for(int i = 0 ; i < values.length ; i++){
                values[i] = xssClean(values[i]);
            }
        }

        return request_map;
    }
     public String[] getParameterValues(String paramString)
      {
        String[] arrayOfString1 = super.getParameterValues(paramString);
        if (arrayOfString1 == null)
          return null;
        int i = arrayOfString1.length;
        String[] arrayOfString2 = new String[i];
        for (int j = 0; j < i; j++){
            arrayOfString2[j] = xssClean(arrayOfString1[j]);
        }
        return arrayOfString2;
      }

      public String getParameter(String paramString)
      {
        String str = super.getParameter(paramString);
        if (str == null)
          return null;
        return xssClean(str);
      }

      public String getHeader(String paramString)
      {
        String str = super.getHeader(paramString);
        if (str == null)
          return null;
        str = str.replaceAll("\r|\n", "");
        return xssClean(str);
      }


      private String xssClean(String value) {
        //ClassLoaderUtils.getResourceAsStream("classpath:antisamy-slashdot.xml", XssHttpServletRequestWrapper.class)
        if (value != null) {
            // NOTE: It's highly recommended to use the ESAPI library and
            // uncomment the following line to
            // avoid encoded attacks.
            // value = encoder.canonicalize(value);
            value = value.replaceAll("\0", "");

            // Avoid anything between script tags
            Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>",
                    Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid anything in a src='...' type of expression
            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'",
                    Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
                            | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid anything in a href='...' type of expression
            scriptPattern = Pattern.compile("href[\r\n]*=[\r\n]*\\\"(.*?)\\\"",
                                Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
                                        | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");


            // Remove any lonesome </script> tag
            scriptPattern = Pattern.compile("</script>",
                    Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Remove any lonesome <script ...> tag
            scriptPattern = Pattern.compile("<script(.*?)>",
                    Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
                            | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid eval(...) expressions
            scriptPattern = Pattern.compile("eval\\((.*?)\\)",
                    Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
                            | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid expression(...) expressions
            scriptPattern = Pattern.compile("expression\\((.*?)\\)",
                    Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
                            | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid javascript:... expressions
            scriptPattern = Pattern.compile("javascript:",
                    Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid vbscript:... expressions
            scriptPattern = Pattern.compile("vbscript:",
                    Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid onload= expressions
            scriptPattern = Pattern.compile("onload(.*?)=",
                    Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
                            | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
        }  
          return value; 
          }
}

step5:都创建完成后直接运行项目就可以

坑位提醒:
注意: <filter-class>XssFilter</filter-class> 里面路径一定要写对,我就在这里踩坑了,添加上Filter后运行tomcat报错,找不到XssFilter

5.1.png

查看报错,大概理解,就是没找到XssFilter。 如下图,我的XssFilter是放在Filter包下

5.2.png

所以修改web.xml成:<filter-class>Filter/XssFilter</filter-class>,再运行就没事了
<filter-class>的值取决与你的XssFilter文件的位置。

测试添加的过滤器效果:

添加留言:

payload:aaa<script>alert(1)</script>

6.1.png

查看留言,没有弹窗,查看发现留言内容只剩下aaa了,script语句被过滤了。
6.2.png

查看数据库中存储的:
也是只有aaa

6.3.png

添加的过滤器生效了,具体的过滤规则(XssHttpServletRequestWrapper)也可以自己修改。

2、使用工具类xssProtect

这是谷歌提供的一个用于过滤来自用户输入字段的XSS攻击的Java库
https://code.google.com/archive/p/xssprotect/
项目中需要引入 xssProtect-0.1.jar、antlr-3.0.1.jar、antlr-runtime-3.0.1.jar等3个 jar 包
具体的使用方式可以参考:https://www.iteye.com/blog/liuzidong-1744023
其实和第一种差不多就是过滤规则不太一样。

3、commons.lang包

在这个包中有个StringUtils 类,该类主要提供对字符串的操作,对null是安全的,主要提供了字符串查找、替换、分割、去空白、去掉非法字符等等操作。

StringEscapeUtils.escapeHtml(string)
使用HTML实体,转义字符串中的字符。对于不可信的输入可以采用 apache.commons.lang3.StringEscapeUtils 对输入字符串进行过滤,将’<’ ‘>’ ‘*’ 三个字符转换成html编码格式 < & &gt. 防止注入攻击:

0x04 参考文章:

https://www.cnblogs.com/hackerping/p/7552400.html
https://cloud.tencent.com/developer/article/1550807
https://xz.aliyun.com/t/6937#toc-2
https://www.iteye.com/blog/liuzidong-1744023

标签: none

添加新评论