环境配置 https://github.com/cn-panda/JavaCodeAudit/tree/master/%E3%80%9003%E3%80%91XSS%20%E6%BC%8F%E6%B4%9E%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E9%99%85%E6%A1%88%E4%BE%8B%E4%BB%8B%E7%BB%8D
下载xss文件夹后,创建数据库添加数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 create database sec_xss charset utf8;SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0 ;DROP TABLE IF EXISTS `message` ;CREATE TABLE `message` (`id` int (11 ) NOT NULL AUTO_INCREMENT,`name` varchar (255 ) DEFAULT NULL ,`mail` varchar (255 ) DEFAULT NULL ,`message` varchar (255 ) DEFAULT NULL ,PRIMARY KEY (`id` ) ) ENGINE =InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET =utf8; BEGIN ;INSERT INTO `message` VALUES (1 , 'panda' , 'panda@cnpanda.net' , '这是⼀个测试储存型 XSS 的项⽬' );INSERT INTO `message` VALUES (2 , 'test' , 'test@test.com' , '测试数据 2。测试功能是 否正确' );INSERT INTO `message` VALUES (3 , 'test_last' , 'last@cnpanda.net' , '最后⼀次测试, 测试⽆误,则完成' );INSERT INTO `message` VALUES (4 , '熊猫' , 'admin@cnpanda.net' , '你好!这⾥有⼀个新的 短消息请注意查收!' );INSERT INTO `message` VALUES (5 , 'lalala' , 'lalala@qq.com' , '啦啦啦啦啦啦啦啦绿绿 \r\n啦啦啦啦啦啦啦啦绿绿' );INSERT INTO `message` VALUES (6 , 'xss' , 'xss@xss.xss' , ' \' test '); COMMIT; SET FOREIGN_KEY_CHECKS = 1;
在src/main/java/com/sec/dao/impl/MessageInfoDaoImpl.java
23行和69行修改数据库连接账号密码
主页如下:
漏洞原理 页面提供了插入字符串的功能时,如果字符串含有javascript代码且特殊字符如尖括号没有被转义,就会被页面执行,造成XSS漏洞。危害有窃取其他用户的Cookies、截屏、劫持等等,javascript能做的事情都可以做。分为反射型、存储型和DOM型XSS。
EL表达式 EL(Expression Language,表达式语言),为了使JSP写起来更简单,提供了在JSP中简化表达式的方法。JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能。
<c:out>标签 <c:out>
标签用来显示一个表达式的结果,与<%=%>
相似,它们的区别就是<c:out>
标签可以直接通过”.”操作符来访问属性。
1 <c:out value="customer.address.street" >
<c:if> 标签 <c:if>
标签判断表达式的值,如果表达式的值为 true 则执行其主体内容。
1 2 <c:if test="${user.salary > 2000}" <p>我的工资为:value="${user.salary}"</p>
<c:forEach>标签 这些标签封装了Java中的for,while,do-while循环。
相比而言,<c:forEach>
标签是更加通用的标签,因为它迭代一个集合中的对象。
<c:forTokens>
标签通过指定分隔符将字符串分隔为一个数组然后迭代它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>c:forEach 标签实例</title> </head> <body> <c:forEach var ="i" begin="1" end="5" > Item <c:out value="${i}" /><p> </c:forEach> </body> </html>
模型类的使用 ModelAndView 1 2 3 4 5 6 7 8 9 10 11 @RequestMapping("mvc") @Controller public class EL { @RequestMapping(value="/getMessage") public ModelAndView getMessage () { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("hello" ); modelAndView.addObject("message" ,"HelloWorld" ); return modelAndView; } }
ModelMap的使用 使用java.util.Map
实现,可以根据模型属性的具体类型自动生成模型属性的名称
1 2 3 4 5 public String testmethod (String someparam,ModelMap model) { Model.addAttribute("key" ,somparam); return "success" ; }
Model类的使用 Model类是一个接口类,通过attribute
添加数据,存储的数据域范围是requestScope
1 2 3 4 public String index1 (Model model) { Model.addAttribute("result" ,"后台返回" ); return "success" ; }
反射型XSS
此处提供了将用户输入显示在页面中的功能,对应源码如下
在search.jsp页面中通过msg获取用户输入并传给/search路由
经过service方法最终将请求传递到Message方法,此处的
1 resp.getWriter().print(message)
将用户输入直接回显到页面上,直接输入payload
1 <script>alert("HacerQWQ");</script>
就能被浏览器执行
存储型XSS 通过servlet将用户的输入作为字符串插入到数据库,然后查询记录的时候回显到页面上,造成存储型XSS。一般存在的地方是博客或者业务数据记录中。
前端是个插入数据的框,在Message中输入payload
1 <script>alert(1 );</script>
对应后端代码src/main/java/com/sec/dao/impl/MessageInfoDaoImpl.java
处的MessageInfoStoreDao
方法
这里虽然经过预编译,但是预编译只能防止sql注入,对于xss的payload没有作用,本质上还是将数据本身插入到数据库当中
同样在MessageInforShowDao
中将记录取出回显到message.jsp中
最终还是会触发存储型XSS攻击
修复方案 产生漏洞的主要原因是javascript标签被解析,所以可以通过使标签失效来达到防御的目的,主要有以下几种
保留语意,将输⼊的特殊字符转译存储到数据库,缺点是可能会对数据库或⽂件系统产⽣⼀些不必要的垃圾信息
过滤掉特殊字符,只保留正常数据,缺点是有些时候⽤户需要输⼊特殊字符,不能保证数据原始性
输⼊限制,含有特殊字符的数据不能够输⼊
全局过滤器 主要是借助了web.xml中的filter配置项进行过滤,添加如下内容
1 2 3 4 5 6 7 8 <filter > <filter-name > XssSafe</filter-name > <filter-class > com.sec.filter.XssFilter</filter-class > </filter > <filter-mapping > <filter-name > XssSafe</filter-name > <url-pattern > /*</url-pattern > </filter-mapping >
这里的url-pattern
配置/
的话只会匹配类似/login
,如果是/*
的话就能匹配如/login.jsp
的路径
XSSFilter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.sec.filter;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import java.io.IOException;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); } }
XSSHttpServletRequestWrapper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 package com.sec.filter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.util.Iterator;import java.util.Map;import java.util.regex.Pattern;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) { if (value != null ) { value = value.replaceAll("\0" , "" ); Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>" , Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll("" ); scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'" , Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("" ); scriptPattern = Pattern.compile("href[\r\n]*=[\r\n]*\\\"(.*?)\\\"" , Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("" ); scriptPattern = Pattern.compile("</script>" , Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll("" ); scriptPattern = Pattern.compile("<script(.*?)>" , Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("" ); scriptPattern = Pattern.compile("eval\\((.*?)\\)" , Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("" ); scriptPattern = Pattern.compile("expression\\((.*?)\\)" , Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("" ); scriptPattern = Pattern.compile("javascript:" , Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll("" ); scriptPattern = Pattern.compile("vbscript:" , Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll("" ); scriptPattern = Pattern.compile("onload(.*?)=" , Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("" ); } return value; } }
规则设置不全的话就容易被绕过,例如:这里只是替换了javascript为空,如果是双写的的话还是能直接执行的
payload:
1 <img src=x onerror="javasjavascriptcript:alert('HackerQWQ')">
使⽤打包好的java库 这里使用的是owasp的Encode插件
maven插件下载地址:https://mvnrepository.com/artifact/org.owasp.encoder/encoder/1.2.3
用法:https://javadoc.io/doc/org.owasp.encoder/encoder/latest/index.html
配置上跟上面的差不多,这里使用注解@WebFilter
效果一样
XssFilter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.sec.filter;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import java.io.IOException;@WebFilter(filterName = "XssFilter",urlPatterns = "/*") 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); } }
XSSHttpServletRequestWrapper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.sec.filter;import org.owasp.encoder.Encode;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.util.Iterator;import java.util.Map;import java.util.regex.Pattern;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) { if (value != null ) { value= Encode.forHtml(value); } return value; } }
拦截成功
commons.lang包的StringEscapeUtils类 在这个包中有个StringUtils 类,该类主要提供对字符串的操作,对null是安全的,主要提供了字符串查 找、替换、分割、去空⽩、去掉⾮法字符等等操作。存在两个函数可以供我们过滤使⽤。
在maven中添加依赖
1 2 3 4 5 <dependency > <groupId > commons-lang</groupId > <artifactId > commons-lang</artifactId > <version > 2.6</version > </dependency >
用法:http://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/StringEscapeUtils.html
StringEscapeUtils.escapeHtml(string)
1 2 3 4 5 6 7 使⽤HTML实体,转义字符串中的字符。 如`"`转换为`"`等 2. ``` StringEscapeUtils.escapeJavaScript(string)
使⽤JavaScript字符串规则转义字符串中的字符。
如对`'`进行转义=>`\'`
效果也是不错的
案例分析(CVE-2018-19178) 漏洞概述 描述:
1 In JEESNS 1.3, com/lxinet/jeesns/core/utils/XssHttpServletRequestWrapper.java allows stored XSS via an HTML EMBED element, a different vulnerability than CVE-2018-17886.
在com/lxinet/jeesns/core/utils/XssHttpServletRequestWrapper.java
中的xss过滤可以用embed标签绕过
环境搭建 项目地址:https://gitee.com/root8088/jeesns1.3
创建数据库
1 create database jeesns charset utf8mb4;
执行数据库脚本。数据库脚本在jeesns-web/database/jeesns.sql
目录下。
1 source /path/to/project/jeesns-web/database/jeesns.sql
导入到maven项目
设置项目编码为utf-8,选择jdk1.7版本或以上,不要选择jre。
然后在jeesns-web/src/main/resources/jeesns.propertis
⽂件中修改数据库的账号密码以及后台路径
编译项目。在eclipse中,右键点击项目名,选择Run as
- Maven build...
,Goals
填入clean package
,然后点击Run
,第一次运行需要下载jar包,请耐心等待。
部署项目。将项目部署到Tomcat7或以上版本,启动Tomcat。
访问系统。前台地址:http://localhost:8080/;用户名:admin,密码:jeesns,登录成功之后,在右上角展开有个'管理',点击即可进入后台管理。
本地没搭建成功,不费这个时间了
漏洞分析 问题出现在com/lxinet/jeesns/core/utils/XssHttpServletRequestWrapper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private String cleanXSS (String value) { value = value.replaceAll("(?i)<style>" , "<style>" ).replaceAll("(? i)</style>" , "</style>" ); value = value.replaceAll("(?i)<script>" , "<script>" ).replaceAll(" (?i)</script>" , "</script>" ); value = value.replaceAll("(?i)<script" , "<script" ); value = value.replaceAll("(?i)eval\\((.*)\\)" , "" ); value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']" , "\"\"" ); String[] eventKeywords = { "onmouseover" , "onmouseout" , "onmousedown" , "onmouseup" , "onmousemove" , "onclick" , "ondblclick" , "onkeypress" , "onkeydown" , "onkeyup" , "ondragstart" , "onerrorupdate" , "onhelp" , "onreadystatechange" , "onrowenter" , "onrowexit" , "onselectstart" , "onload" , "onunload" , "onbeforeunload" , "onblur" , "onerror" , "onfocus" , "onresize" , "onscroll" , "oncontextmenu" , "alert" }; for (int i = 0 ; i < eventKeywords.length; i++) { value = value.replaceAll(eventKeywords[i],"_" + eventKeywords[i]); } return value; } }
使用黑名单的方式对数据进行过滤,由于标签数量大,属性也多,因此一旦不能完全对敏感字符进行过滤,就会造成XSS注入
payload
1 2 3 4 <img src="x" ONERROR=confirm(0 )> <svg/onLoad=confirm(1 )> <object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=" >
修复漏洞 采用StringEscapeUtils
或者OWASP的Encode插件