XSS攻击及AntiSamy防御

it2023-05-19  78

什么是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\-_',:\[\]!\./\\\(\)&amp;]*"/> <!-- force non-empty with a '+' at the end instead of '*' --> <regexp name="onsiteURL" value="([\p{L}\p{N}\\/\.\?=\#&amp;;\-_~]+|\#(\w)+)"/> <regexp name="offsiteURL" value="(\s)*((ht|f)tp(s?)://|mailto:)[\p{L}\p{N}]+[~\p{L}\p{N}\p{Zs}\-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\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"; /** * 默认放行单点登录的登出响应(响应中包含samlp:LogoutRequest标签,直接放行) */ 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); //默认放行单点登录的登出响应(响应中包含samlp:LogoutRequest标签,直接放行) if (!ignoreParamValueList.contains(CAS_LOGOUT_RESPONSE_TAG)) { ignoreParamValueList.add(CAS_LOGOUT_RESPONSE_TAG); } } else { //默认放行单点登录的登出响应(响应中包含samlp:LogoutRequest标签,直接放行) 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"); // 判断uri是否包含项目名称 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; /** * @author asus */ 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; /** * XSS 工具类, 用于过滤特殊字符 * * @author zuihou * @date 2019/07/02 */ 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); } } } } /** * 跨站攻击语句过滤 方法 * * @param paramValue 待过滤的参数 * @param ignoreParamValueList 忽略过滤的参数列表 * @return */ 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); } // cr.getErrorMessages().forEach(log::debug); String str = cr.getCleanHTML(); /*String str = StringEscapeUtils.escapeHtml(cr.getCleanHTML()); str = str.replaceAll((antiSamy.scan("&nbsp;", policy)).getCleanHTML(), ""); str = StringEscapeUtils.unescapeHtml(str);*/ str = str.replaceAll("&quot;", "\""); str = str.replaceAll("&amp;", "&"); str = str.replaceAll("'", "'"); str = str.replaceAll("'", "'"); str = str.replaceAll("&lt;", "<"); str = str.replaceAll("&gt;", ">"); 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,网页端直接删除该标签。

最新回复(0)