XSS问题描述
跨站脚本(Cross site script,简称xss)是一种“HTML注入”,由于攻击的脚本多数时候是跨域的,所以称之为“跨域脚本”。
我们常常听到“注入”(Injection),如SQL注入,那么到底“注入”是什么?注入本质上就是把输入的数据变成可执行的程序语句。SQL注入是如此,XSS也如此,只不过XSS一般注入的是恶意的脚本代码,这些脚本代码可以用来获取合法用户的数据,如Cookie信息。
或者当访问者浏览网页时恶意代码会被执行或者通过给管理员发信息的方式诱使管理员浏览,从而获得管理员权限,控制整个网站
攻击者利用跨站请求伪造能够轻松地强迫用户的浏览器发出非故意的HTTP请求,如诈骗性的电汇请求、修改口令和下载非法的内容等请求。
风险等级:高危
XSS风险分析
该漏洞可能被攻击者利用窃取或操纵客户会话和Cookie,它们可能用于模仿合法用户,从而使黑客能够以合法用户身份登录系统进行渗透。可执行攻击者恶意脚本。
XSS 注入方法
在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
在标签的 href、src 等属性中,包含
javascript:
等可执行代码。在 onload、onerror、onclick 等事件中,注入不受控制代码。
在 style 属性和标签中,包含类似
background-image:url("javascript:…");
的代码(新版本浏览器已经可以防范)。在 style 属性和标签中,包含类似
expression(…)
的 CSS 表达式代码(新版本浏览器已经可以防范)。
XSS 防御
本文基于JavaWeb后端考虑,前端可参考: 前端安全系列:如何防止XSS攻击?:
依赖jar
<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>3.0-alpha-1</version> <scope>provided</scope> </dependency>
web.xml添加过滤器
<!-- 解决xss漏洞 --> <filter> <filter-name>xssFilter</filter-name> <filter-class>com.newcapec.cloudpay.filter.XssFilter</filter-class> </filter> <!-- 解决xss漏洞 --> <filter-mapping> <filter-name>xssFilter</filter-name> <!--过滤路径--> <url-pattern>*</url-pattern> </filter-mapping>
过滤器
自定义过滤器Filter拦截请求,并对请求参数进行xss过滤处理
package com.newcapec.cloudpay.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * @Title: XSS过滤器 * @ClassName: com.newcapec.cloudpay.filter.XssFilter.java * @Description: * * @Copyright 2016-2018 Powered By 研发中心 * @author: 王延飞 * @date: 2019-01-24 16:22 * @version V1.0 */ public class XssFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //使用包装器 XSSRequestWrapper XSSRequestWrapper=new XSSRequestWrapper((HttpServletRequest) servletRequest); filterChain.doFilter(XSSRequestWrapper,servletResponse); } @Override public void destroy() { } }
过滤器包装器
package com.newcapec.cloudpay.filter; import org.apache.commons.lang.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * @version V1.0 * @Title: Request的包装类 * @ClassName: com.newcapec.cloudpay.filter.XSSRequestWrapper.java * @Description: XSSRequestWrapper是Request的包装类, 用于修改Request请求,这是拦截器Interceptor所不能做到的 * @Copyright 2016-2018 Powered By 研发中心 * @author: 王延飞 * @date: 2019-01-25 9:15 */ public class XSSRequestWrapper extends HttpServletRequestWrapper { public XSSRequestWrapper(HttpServletRequest request) { super(request); } /** * @param name * @return java.lang.String[] * @Title: 对数组参数进行特殊字符过滤 * @methodName: getParameterValues * @Description: * @author: 王延飞 * @date: 2019-01-25 10:23 */ @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values == null) { return null; } int count = values.length; String[] encodedValues = new String[count]; for (int i = 0; i < count; i++) { encodedValues[i] = clearXss(values[i]); } return encodedValues; } /** * @param name * @return java.lang.String * @Title: 对参数中特殊字符进行过滤 * @methodName: getParameter * @Description: * @author: 王延飞 * @date: 2019-01-25 10:23 */ @Override public String getParameter(String name) { String value = super.getParameter(name); if (value == null) { return null; } return clearXss(value); } /** * @Title: 覆盖getParameterMap方法,将参数名和参数值都做xss & sql过滤 * @methodName: getParameterMap * @param * @return java.util.Map * @Description: 覆盖getParameterMap方法,将参数名和参数值都做xss & sql过滤 * 【一般post表单请求,或者前台接收为实体需要这样处理】 * * @author: 王延飞 * @date: 2019-02-20 11:12 */ @Override public Map getParameterMap() { Map<String, Object> paramMap = new HashMap<String, Object>(); Map<String, String[]> requestMap = super.getParameterMap(); Iterator<Entry<String, String[]>> it = requestMap.entrySet().iterator(); while (it.hasNext()) { Entry<String, String[]> entry = it.next(); if(entry.getValue().length==1){ paramMap.put(xssEncode(entry.getKey()), xssEncode(entry.getValue()[0])); } else { String[] values = entry.getValue(); String value = ""; for(int i=0; i<values.length; i++){ value = values[i] + ","; } value = value.substring(0, value.length()-1); paramMap.put(xssEncode(entry.getKey()), xssEncode(entry.getValue()[0])); } } return paramMap; } /** * @param name * @return java.lang.Object * @Title: 获取attribute, 特殊字符过滤 * @methodName: getAttribute * @Description: * @author: 王延飞 * @date: 2019-01-25 10:24 */ @Override public Object getAttribute(String name) { Object value = super.getAttribute(name); if (value != null && value instanceof String) { clearXss((String) value); } return value; } /** * @Title: 对请求头部进行特殊字符过滤 * @methodName: getHeader * @param name * @return java.lang.String * @Description: * * @author: 王延飞 * @date: 2019-01-25 10:34 */ /*@Override public String getHeader(String name) { String value = super.getHeader(name); if (value == null) { return null; } return clearXss(value); }*/ /** * @param value * @return java.lang.String * @Title: 特殊字符处理(转义或删除) * @methodName: clearXss * @Description: * @author: 王延飞 * @date: 2019-01-25 9:16 */ private String clearXss(String value) { if (StringUtils.isEmpty(value)) { return value; } // 字符编码不一致,需要转换。我们系统是UTF-8编码,这里不需要 /*try { value = new String(value.getBytes("ISO8859-1"), "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return null; }*/ return XssFilterUtil.stripXss(value); } /** * 将容易引起xss漏洞的半角字符直接替换成全角字符 在保证不删除数据的情况下保存 * * @param s * @return 过滤后的值 */ private static String xssEncode(String value) { if (value == null || value.isEmpty()) { return value; } value = value.replaceAll("eval\\((.*)\\)", ""); value = value.replaceAll("<","<"); value = value.replaceAll(">",">"); value = value.replaceAll("'","'"); value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\""); value = value.replaceAll("(?i)<script.*?>.*?<script.*?>", ""); value = value.replaceAll("(?i)<script.*?>.*?</script.*?>", ""); value = value.replaceAll("(?i)<.*?javascript:.*?>.*?</.*?>", ""); value = value.replaceAll("(?i)<.*?\\s+on.*?>.*?</.*?>", ""); // value = value.replaceAll("[<>{}\\[\\];\\&]",""); return value; } }
过滤器工具类
package com.newcapec.cloudpay.filter; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @version V1.0 * @Title: * @ClassName: com.newcapec.cloudpay.filter.XssFilterUtil.java * @Description: * @Copyright 2016-2018 Powered By 研发中心 * @author: 王延飞 * @date: 2019-01-25 9:11 */ public class XssFilterUtil { private static final Logger log = LoggerFactory.getLogger(XssFilterUtil.class); private static List<Pattern> patterns = null; /** * @param * @return java.util.List<java.lang.Object [ ]> * @Title: XSS常见攻击 * @methodName: getXssPatternList * @Description: Pattern.MULTILINE(? m):在这种模式下,'^'和'$'分别匹配一行的开始和结束。此外,'^'仍然匹配字符串的开始,'$'也匹配字符串的结束。 * 默认情况下,这两个表达式仅仅匹配字符串的开始和结束。 * <p> * Pattern.DOTALL(?s) :在这种模式下,表达式'.'可以匹配任意字符,包括表示一行的结束符。 * 默认情况下,表达式'.'不匹配行的结束符。 * @author: 王延飞 * @date: 2019-01-25 9:11 */ private static List<Object[]> getXssPatternList() { List<Object[]> ret = new ArrayList<Object[]>(); ret.add(new Object[]{"<(no)?script[^>]*>.*?</(no)?script>", Pattern.CASE_INSENSITIVE}); ret.add(new Object[]{"</script>", Pattern.CASE_INSENSITIVE}); ret.add(new Object[]{"<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL}); ret.add(new Object[]{"eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL}); ret.add(new Object[]{"expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL}); ret.add(new Object[]{"(javascript:|vbscript:|view-source:)*", Pattern.CASE_INSENSITIVE}); ret.add(new Object[]{"<(\"[^\"]*\"|\'[^\']*\'|[^\'\">])*>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL}); ret.add(new Object[]{"(window\\.location|window\\.|\\.location|document\\.cookie|document\\.|alert\\(.*?\\)|window\\.open\\()*", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL}); ret.add(new Object[]{"<+\\s*\\w*\\s*(oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|onerror=|onerroupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmousout|onmouseover|onmouseup|onmousewheel|onmove|onmoveend|onmovestart|onabort|onactivate|onafterprint|onafterupdate|onbefore|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditocus|onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|onpaste|onpropertychange|onreadystatechange|onreset|onresize|onresizend|onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|onselectstart|onstart|onstop|onsubmit|onunload)+\\s*=+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL}); return ret; } /** * @param * @return java.util.List<java.util.regex.Pattern> * @Title: XSS常见攻击-正则表达式 * @methodName: getPatterns * @Description: * @author: 王延飞 * @date: 2019-01-25 9:11 */ private static List<Pattern> getPatterns() { if (patterns == null) { List<Pattern> list = new ArrayList<Pattern>(); String regex = null; Integer flag = null; int arrLength = 0; for (Object[] arr : getXssPatternList()) { arrLength = arr.length; for (int i = 0; i < arrLength; i++) { regex = (String) arr[0]; flag = (Integer) arr[1]; list.add(Pattern.compile(regex, flag)); } } patterns = list; } return patterns; } /** * @param value * @return java.lang.String * @Title: 处理特殊字符 * @methodName: stripXss * @Description: 如果是特殊字符,策略有两种:转义或删除 * @author: 王延飞 * @date: 2019-01-25 9:12 */ public static String stripXss(String value) { if (StringUtils.isNotBlank(value)) { log.info("【XSS攻击防御】,接收字符是:{}", value); // Matcher matcher = null; for (Pattern pattern : getPatterns()) { matcher = pattern.matcher(value); // 匹配 if (matcher.find()) { // 删除相关字符串 value = matcher.replaceAll(""); } } log.info("【XSS攻击防御】,匹配正则是:{},处理后是:{}", matcher, value); /** * 替换为转移字符,类似HtmlUtils.htmlEscape */ //value = value.replaceAll("<", "<").replaceAll(">", ">"); //删除特殊符号 //String specialStr = "%20|=|!=|-|--|;|'|\"|%|#|+|//|/| |\\|<|>|(|)|{|}"; if (StringUtils.isNotBlank(value)) { String specialStr = "%20|=|!=|-|--|;|'|\"|%|#|[+]|//|/| |\\|<|>|(|)|{|}"; for (String str : specialStr.split("\\|")) { if (value.indexOf(str) > -1) { value = value.replaceAll(str, ""); } } log.info("【XSS攻击防御】,特殊符号处理后是:{}", value); } } return value; } }
测试类
package com.newcapec.cloudpay.controller.test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; /** * @Title: Controller--测试 * @ClassName:RsaControllerTest.java * @Description: * * @Copyright 2016-2017 Powered By 研发中心 * @author: 王延飞 * @version V1.0 */ @Controller @RequestMapping("/test") public class RsaControllerTest { private static final Logger log = LoggerFactory.getLogger(RsaControllerTest.class); @RequestMapping("/testXSS") @ResponseBody public String testXSS(HttpServletRequest request){ /*String queryString = request.getQueryString(); log.info("【防御XSS】,客户端端请求参数是:{}", queryString);*/ String name = request.getParameter("name"); log.info("【防御XSS】,客户端端name是:{}", name); return "【防御XSS】,客户端端请求参数:" +name; } }
测试结果
访问:http://localhost:8080/CloudPayment/test/testXSS.do?name=<script>alert('你被攻击了!')</script>
- 控制台输出信息:
2019-01-25 11:50:37:512 com.newcapec.cloudpay.filter.XssFilterUtil.stripXss(XssFilterUtil.java:104) 【XSS攻击防御】,接收字符是:<script>alert('你被攻击了!')</script>
2019-01-25 11:50:37:513 com.newcapec.cloudpay.filter.XssFilterUtil.stripXss(XssFilterUtil.java:118) 【XSS攻击防御】,匹配正则是:java.util.regex.Matcher[pattern=<+\s*\w*\s*(oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|οnerrοr=|onerroupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmousout|onmouseover|onmouseup|onmousewheel|onmove|onmoveend|onmovestart|onabort|onactivate|onafterprint|onafterupdate|onbefore|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditocus|onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|onpaste|onpropertychange|onreadystatechange|onreset|onresize|onresizend|onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|onselectstart|onstart|onstop|onsubmit|onunload)+\s*=+ region=0,0 lastmatch=],处理后是:
2019-01-25 11:50:37:513 com.newcapec.cloudpay.controller.test.RsaControllerTest.testXSS(RsaControllerTest.java:64) 【防御XSS】,客户端端name是: