什么是xss
xss:跨站脚本攻击(Cross Site Scripting),因为跟样式css混淆,所以习惯缩写为xss。通过一些方法注入恶意指令代码到网页,使其加载并执行攻击者恶意的网页程序。
xss类型
1、反射型xss:通过get或者post等方式,向服务端输入数据。如果服务端不进行处理(过滤,验证,编码等),直接将信息呈现出来,可能会造成反射型xss。 2、存储型xss:服务端对注入的恶意脚本没有经过验证存入数据库,每次调用数据库都会将其渲染在浏览器上。则可能为存储型xss。
AntiSamy防御
主要思路为:对用户输入的脚本,提交的数据进行转义,编码。 AntiSamy提供了对恶意指令的过滤,各个标签、属性的处理方法。主要通过定义策略文件来达到防御的效果。
1、导入jar
<dependency>
<groupId>org
.owasp
.antisamy
</groupId
>
<artifactId>antisamy
</artifactId
>
<version>1.5.5</version
>
</dependency
>
2、定义策略文件antisamy-slashdot.xml
<?xml version
="1.0" encoding
="ISO-8859-1"?>
<anti
-samy
-rules xmlns
:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi
:noNamespaceSchemaLocation
="antisamy.xsd">
<!-- 全局配置,对AntiSamy的过滤验证规则、输入、输出的格式进行控制
-->
<directives>
<directive name
="omitXmlDeclaration" value
="true"/>
<directive name
="omitDoctypeDeclaration" value
="true"/>
<directive name
="maxInputSize" value
="500000"/>
<directive name
="useXHTML" value
="true"/>
<directive name
="formatOutput" value
="false"/>
<directive name
="preserveComments" value
="true"/>
<directive name
="onUnknownTag" value
="encode"/>
<directive name
="nofollowAnchors" value
="true" />
<directive name
="embedStyleSheets" value
="false"/>
</directives
>
<!-- 公用的正则表达式
-->
<common
-regexps
>
<regexp name
="htmlTitle"
value
="[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*"/> <!-- force non
-empty with a
'+' at the end instead of
'*' -->
<regexp name
="onsiteURL" value
="([\p{L}\p{N}\\/\.\?=\#&;\-_~]+|\#(\w)+)"/>
<regexp name
="offsiteURL"
value
="(\s)*((ht|f)tp(s?)://|mailto:)[\p{L}\p{N}]+[~\p{L}\p{N}\p{Zs}\-_\.@\#\$%&;:,\?=/\+!\(\)]*(\s)*"/>
</common
-regexps
>
<!-- 通用属性需要满足的输入规则
-->
<common
-attributes
>
<attribute name
="lang"
description
="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
<regexp
-list
>
<regexp value
="[a-zA-Z]{2,20}"/>
</regexp
-list
>
</attribute
>
<attribute name
="title"
description
="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
<regexp
-list
>
<regexp name
="htmlTitle"/>
</regexp
-list
>
</attribute
>
<attribute name
="href" onInvalid
="filterTag">
<regexp
-list
>
<regexp name
="onsiteURL"/>
<regexp name
="offsiteURL"/>
</regexp
-list
>
</attribute
>
<attribute name
="align"
description
="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
<literal
-list
>
<literal value
="center"/>
<literal value
="left"/>
<literal value
="right"/>
<literal value
="justify"/>
<literal value
="char"/>
</literal
-list
>
</attribute
>
</common
-attributes
>
<!-- 标签默认属性遵守的规则
-->
<global
-tag
-attributes
>
<attribute name
="title"/>
<attribute name
="lang"/>
</global
-tag
-attributes
>
<!-- 需要进行编码处理的标签
-->
<tags
-to
-encode
>
<tag>g
</tag
>
<tag>grin
</tag
>
</tags
-to
-encode
>
<!-- 标签的处理规则:
1、remove:对应标签直接删除
2、truncate:对应标签进行缩短处理,删除所有属性,只保留标签和值
3、validate:对应标签的属性进行验证,如果tag中有定义的验证规则,则执行该规则,如果没有定义,则按照
<global
-tag
-attributes
>定义的处理
-->
<tag
-rules
>
<!-- Tags related to JavaScript
-->
<tag name
="script" action
="remove"/>
<tag name
="noscript" action
="remove"/>
<!-- Frame
& related tags
-->
<tag name
="iframe" action
="remove"/>
<tag name
="frameset" action
="remove"/>
<tag name
="frame" action
="remove"/>
<tag name
="noframes" action
="remove"/>
<!-- CSS related tags
-->
<tag name
="style" action
="remove"/>
<!-- All reasonable formatting tags
-->
<tag name
="p" action
="validate">
<attribute name
="align"/>
</tag
>
<tag name
="div" action
="validate"/>
<tag name
="i" action
="validate"/>
<tag name
="b" action
="validate"/>
<tag name
="em" action
="validate"/>
<tag name
="blockquote" action
="validate"/>
<tag name
="tt" action
="validate"/>
<tag name
="strong" action
="validate"/>
<tag name
="br" action
="truncate"/>
<!-- Custom Slashdot tags
, though we're trimming the idea of having a possible mismatching end tag with the endtag
="" attribute
-->
<tag name
="quote" action
="validate"/>
<tag name
="ecode" action
="validate"/>
<!-- Anchor and anchor related tags
-->
<tag name
="a" action
="validate">
<attribute name
="href" onInvalid
="filterTag"/>
<attribute name
="nohref">
<literal
-list
>
<literal value
="nohref"/>
<literal value
=""/>
</literal
-list
>
</attribute
>
<attribute name
="rel">
<literal
-list
>
<literal value
="nofollow"/>
</literal
-list
>
</attribute
>
</tag
>
<!-- List tags
-->
<tag name
="ul" action
="validate"/>
<tag name
="ol" action
="validate"/>
<tag name
="li" action
="validate"/>
</tag
-rules
>
<!-- No CSS on Slashdot posts
-->
<!-- css处理规则
-->
<css
-rules
>
</css
-rules
>
</anti
-samy
-rules
>
AntiSamy的策略文件类型有如下: **antisamy-anythinggoes.xml:**允许所有有效的html和css的输入(但能拒绝JavaScript或跟CSS相关的网络钓鱼攻击)。一般不建议使用。 **antisamy-ebay.xml:**允许任何人发布一系列富html的内容。适用于电商网站。 **antisamy-myspace.xml:**允许提交除了javascript之外的几乎所有的html和css。不建议使用。 **antisamy-slashdot.xml:**只允许提交b、u、i、a、blockquote的标签,不支持css。适用于新闻网站的评论过滤。 **antisamy-tinymce.xml:**只允许文本格式通过。 **antisamy.xml:**默认规则,允许大部分html通过。
3、定义xss过滤器
package com
.yllt
.common
.filter
.front
;
import com
.alibaba
.fastjson
.JSON
;
import com
.yllt
.common
.util
.CollectionUtil
;
import org
.apache
.commons
.lang
.StringUtils
;
import org
.slf4j
.Logger
;
import org
.slf4j
.LoggerFactory
;
import javax
.servlet
.*
;
import javax
.servlet
.http
.HttpServletRequest
;
import java
.io
.IOException
;
import java
.util
.ArrayList
;
import java
.util
.Arrays
;
import java
.util
.List
;
public class XssFilter implements Filter {
private static Logger log
= LoggerFactory
.getLogger(XssFilter
.class);
private static final String IGNORE_PATH
= "ignorePath";
private static final String IGNORE_PARAM_VALUE
= "ignoreParamValue";
private static final String CAS_LOGOUT_RESPONSE_TAG
= "samlp:LogoutRequest";
private List
<String> ignorePathList
;
private List
<String> ignoreParamValueList
;
@Override
public void init(FilterConfig filterConfig
) throws ServletException
{
log
.info("XSS fiter [XSSFilter] init start ...");
String ignorePaths
= filterConfig
.getInitParameter(IGNORE_PATH
);
String ignoreParamValues
= filterConfig
.getInitParameter(IGNORE_PARAM_VALUE
);
if (!StringUtils
.isBlank(ignorePaths
)) {
String
[] ignorePathArr
= ignorePaths
.split(",");
ignorePathList
= Arrays
.asList(ignorePathArr
);
}
if (!StringUtils
.isBlank(ignoreParamValues
)) {
String
[] ignoreParamValueArr
= ignoreParamValues
.split(",");
ignoreParamValueList
= Arrays
.asList(ignoreParamValueArr
);
if (!ignoreParamValueList
.contains(CAS_LOGOUT_RESPONSE_TAG
)) {
ignoreParamValueList
.add(CAS_LOGOUT_RESPONSE_TAG
);
}
} else {
ignoreParamValueList
= new ArrayList<String>();
ignoreParamValueList
.add(CAS_LOGOUT_RESPONSE_TAG
);
}
log
.info("ignorePathList=" + JSON
.toJSONString(ignorePathList
));
log
.info("ignoreParamValueList=" + JSON
.toJSONString(ignoreParamValueList
));
log
.info("XSS fiter [XSSFilter] init end");
}
@Override
public void doFilter(ServletRequest request
, ServletResponse response
, FilterChain chain
)
throws IOException
, ServletException
{
log
.info("XSS fiter [XSSFilter] starting");
String uriPath
= ((HttpServletRequest
) request
).getRequestURI();
if (isIgnorePath(uriPath
)) {
log
.info("ignore xssfilter,path[" + uriPath
+ "] pass through XssFilter, go ahead...");
chain
.doFilter(request
, response
);
return;
} else {
log
.info("has xssfiter path[" + uriPath
+ "] need XssFilter, go to XssRequestWrapper");
chain
.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest
) request
, ignoreParamValueList
), response
);
}
log
.info("XSS fiter [XSSFilter] stop");
}
@Override
public void destroy() {
log
.info("XSS fiter [XSSFilter] destroy");
}
private boolean isIgnorePath(String servletPath
) {
if (StringUtils
.isBlank(servletPath
)) {
return true;
}
if (CollectionUtil
.isListNULL(ignorePathList
)) {
return false;
} else {
for (String ignorePath
: ignorePathList
) {
if (!StringUtils
.isBlank(ignorePath
) && servletPath
.contains(ignorePath
.trim())) {
return true;
}
}
}
return false;
}
}
做一个装饰器类,来处理request的参数
package com
.yllt
.common
.filter
.front
;
import org
.slf4j
.Logger
;
import org
.slf4j
.LoggerFactory
;
import javax
.servlet
.http
.HttpServletRequest
;
import javax
.servlet
.http
.HttpServletRequestWrapper
;
import java
.util
.List
;
import java
.util
.Map
;
import static com
.yllt
.common
.filter
.front
.XssUtils
.xssClean
;
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static Logger log
= LoggerFactory
.getLogger(XssHttpServletRequestWrapper
.class);
private List
<String> ignoreParamValueList
;
public XssHttpServletRequestWrapper(HttpServletRequest request
, List
<String> ignoreParamValueList
) {
super(request
);
this.ignoreParamValueList
= ignoreParamValueList
;
}
@Override
public Map
<String
, String
[]> getParameterMap() {
Map
<String
, String
[]> requestMap
= super.getParameterMap();
for (Map
.Entry
<String
, String
[]> me
: requestMap
.entrySet()) {
log
.info(me
.getKey() + ":");
String
[] values
= me
.getValue();
for (int i
= 0; i
< values
.length
; i
++) {
log
.info(values
[i
]);
values
[i
] = xssClean(values
[i
], this.ignoreParamValueList
);
}
}
return requestMap
;
}
@Override
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
], this.ignoreParamValueList
);
}
return arrayOfString2
;
}
@Override
public String
getParameter(String paramString
) {
String str
= super.getParameter(paramString
);
if (str
== null
) {
return null
;
}
return xssClean(str
, this.ignoreParamValueList
);
}
@Override
public String
getHeader(String paramString
) {
String str
= super.getHeader(paramString
);
if (str
== null
) {
return null
;
}
return xssClean(str
, this.ignoreParamValueList
);
}
}
xss工具类,用来进行过滤处理
package com
.yllt
.common
.filter
.front
;
import com
.yllt
.common
.util
.CollectionUtil
;
import com
.yllt
.common
.util
.StringUtil
;
import org
.owasp
.validator
.html
.*
;
import org
.slf4j
.Logger
;
import org
.slf4j
.LoggerFactory
;
import java
.io
.IOException
;
import java
.io
.InputStream
;
import java
.util
.List
;
public class XssUtils {
private static Logger log
= LoggerFactory
.getLogger(XssUtils
.class);
private static final String ANTISAMY_SLASHDOT_XML
= "antisamy-slashdot-1.4.4.xml";
private static Policy policy
= null
;
static {
log
.info(" start read XSS configfile [" + ANTISAMY_SLASHDOT_XML
+ "]");
InputStream inputStream
= XssUtils
.class.getClassLoader().getResourceAsStream(ANTISAMY_SLASHDOT_XML
);
try {
policy
= Policy
.getInstance(inputStream
);
log
.info("read XSS configfile [" + ANTISAMY_SLASHDOT_XML
+ "] success");
} catch (PolicyException e
) {
log
.error("read XSS configfile [" + ANTISAMY_SLASHDOT_XML
+ "] fail , reason:", e
);
} finally {
if (inputStream
!= null
) {
try {
inputStream
.close();
} catch (IOException e
) {
log
.error("close XSS configfile [" + ANTISAMY_SLASHDOT_XML
+ "] fail , reason:", e
);
}
}
}
}
public static String
xssClean(String paramValue
, List
<String> ignoreParamValueList
) {
AntiSamy antiSamy
= new AntiSamy();
try {
log
.info("raw value before xssClean: " + paramValue
);
if (isIgnoreParamValue(paramValue
, ignoreParamValueList
)) {
log
.info("ignore the xssClean,keep the raw paramValue: " + paramValue
);
return paramValue
;
} else {
final CleanResults cr
= antiSamy
.scan(paramValue
, policy
);
for(String msg
: cr
.getErrorMessages()){
log
.info(msg
);
}
String str
= cr
.getCleanHTML();
str
= str
.replaceAll(""", "\"");
str
= str
.replaceAll("&", "&");
str
= str
.replaceAll("'", "'");
str
= str
.replaceAll("'", "'");
str
= str
.replaceAll("<", "<");
str
= str
.replaceAll(">", ">");
log
.info("xssfilter value after xssClean" + str
);
return str
;
}
} catch (ScanException e
) {
log
.error("scan failed armter is [" + paramValue
+ "]", e
);
} catch (PolicyException e
) {
log
.error("antisamy convert failed armter is [" + paramValue
+ "]", e
);
}
return paramValue
;
}
private static boolean isIgnoreParamValue(String paramValue
, List
<String> ignoreParamValueList
) {
if (StringUtil
.isBlank(paramValue
)) {
return true;
}
if (CollectionUtil
.isListNULL(ignoreParamValueList
)) {
return false;
} else {
for (String ignoreParamValue
: ignoreParamValueList
) {
if (paramValue
.contains(ignoreParamValue
)) {
return true;
}
}
}
return false;
}
}
4、配置过滤器
<filter>
<filter-name>xssFilter
</filter-name>
<filter-class>com.yllt.common.filter.front.XssFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>xssFilter
</filter-name>
<url-pattern>/front/*
</url-pattern>
</filter-mapping>
验证
网页端提交如下数据:
<script
>alert
(666
)</script
>
查看xss过滤日志:
2020-10-20 14:42:39,863 INFO
[com.yllt.common.filter.front.XssFilter
] -
<XSS fiter
[XSSFilter
] starting
>
2020-10-20 14:42:39,864 INFO
[com.yllt.common.filter.front.XssFilter
] -
<has xssfiter path
[/search.jhtml
] need XssFilter, go to XssRequestWrapper
>
2020-10-20 14:42:39,866 INFO
[com.yllt.common.filter.front.XssUtils
] -
<raw value before xssClean:
<script
>123
>
2020-10-20 14:42:39,867 INFO
[com.yllt.common.filter.front.XssUtils
] -
<出于安全的原因,标记script不被允许。此标记不应该影响输入的显示。
>
已被过滤,同时因为配置规则script标签为remove,网页端直接删除该标签。