1.XSS攻击概述
XSS(Cross Site Scripting)攻击全称跨站脚本攻击,
是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。
1.1 主要危害
1、盗取各类用户帐号,如机器登录帐号、用户网银帐号、各类管理员帐号
2、控制企业数据,包括读取、篡改、添加、删除企业敏感数据的能力
3、盗窃企业重要的具有商业价值的资料
4、非法转账
5、强制发送电子邮件
6、网站挂马
7、控制受害者机器向其它网站发起攻击
2.攻击方式
2.1 反射型
反射型XSS,也叫非持久型XSS,
是指发生请求时,XSS代码出现在请求URL中,作为参数提交到服务器,服务器解析并响应。
响应结果中包含XSS代码,最后浏览器解析并执行。
这个过程像一次反射,故叫反射型XSS。
从概念上可以看出,反射型XSS代码是首先出现在URL中的,
然后需要服务端解析,最后需要浏览器解析之后XSS代码才能够攻击。
#这类攻击通常使用URL,具体流程:
1、Alice给Bob发送一个恶意构造了Web的URL。
2、Bob点击并查看了这个URL。
3、恶意页面中的js打开一个具有漏洞的HTML页面并将其安装在Bob电脑上。
4、具有漏洞的HTML页面包含了在Bob电脑本地域执行的js。
5、Alice的恶意脚本可以在Bob的电脑上执行Bob所持有的权限下的命令。
#举个列子, 访问下文所示链接:
>> http://localhost:8001/reflectXSS?name=<script>alert("hey!")</script>
>> http://localhost:8001/reflectXSS?name=<img src='w.123' onerror='alert("hey!")'>
>> http://localhost:8001/reflectXSS?name=<a onclick='alert("hey!")'>点我一下会有弹框哦</a>
反射型XSS.png
2.2 存储型
存储型XSS,也叫持久型XSS,主要是将XSS代码发送到服务器(DB、内存或文件系统等),
然后在下次请求页面的时候就不用带上XSS代码了。
#最典型的就是留言板XSS。
用户提交了一条包含XSS代码的留言到数据库。
当目标用户查询留言时,那些留言的内容会从服务器解析之后加载出来。
浏览器发现有XSS代码,就当做正常的HTML和JS解析执行。XSS攻击就发生了。
1.窃取用户信息,如cookie,token,账号密码等。
例如:tom发了一篇帖子,jerry进行回复:但内容却是一段js脚本。
这篇帖子被他人浏览的时候就会中招,例子中的只是一个alert(),
但脚本可以写的比较复杂一点盗用用户cookie等操作。
模拟用户留言输入的xss攻击1.png
被攻击时的情形1.png
除了这种hacker还有个很惯用的伎俩,例如存储型XSS生成一些诱人的图片,文字(你懂的!),
然后用户去点击的时候就可以执行某些坏事,窃取信息或者诱导到钓鱼网站。
点击图片后,就会进入到目标网站了。
模拟用户留言输入的xss攻击2.png
被攻击时的情形2.png
2.劫持流量实现恶意跳转
用户打开的网址,会默认跳转至指定网站,脚本如下:
<script>window.location.href="http://www.baidu.com";</script>
2.3 DOM XSS
DOM XSS和反射型XSS、存储型XSS的差别在于DOM XSS的代码并不需要服务器参与,
触发XSS靠的是浏览器端的DOM解析,完全是客户端的事情。
3.应对措施
3.1 服务端方案 (也可以是网关层[非nginx]的统一处理方案)
3.1.1 入参字符过滤
在源头控制,把输入的一些不合法的东西都过滤掉,从而保证安全性。
如移除用户提交的的DOM属性如onerror,移除用户上传的Style节点,<iframe>, <script>,<a>节点等
3.1.2 出参进行编码
如果源头没控制好,就得后期补救了:
像一些常见的符号,如<>在输出的时候要对其进行转换编码,
这样做浏览器是不会对该标签进行解释执行的,同时也不影响显示效果。
例如:对<>做编码如:"<"用:"<",">"用:">"来代替。
3.1.3 入参长度限制
通过以上的案例我们不难发现xss攻击要能达成往往需要较长的字符串,
因此对于一些可以预期的输入可以通过限制长度强制截断来进行防御。
3.1.4 cookie安全策略
在服务器端设置cookie的时候设置 http-only, 这样就可以防止用户通过JS获取cookie。
对cookie的读写或发送一般有如下字段进行设置:
>> http-only: 只允许http或https请求读取cookie、JS代码是无法读取cookie的(document.cookie会显示http-only的cookie项被自动过滤掉)。发送请求时自动发送cookie.
>> secure-only: 只允许https请求读取,发送请求时自动发送cookie。
>> host-only: 只允许主机域名与domain设置完成一致的网站才能访问该cookie。
设置cookie httponly为true.png
通过命令发现只能读取到httponly=false的cookie.png
3.1.5 代码实现
package com.zy.commons.lang.filter;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 使用方案:
* @Configuration
* public class ServletConfig {
* @Bean
* public FilterRegistrationBean<XSSFilter> filterRegistrationBean() {
* FilterRegistrationBean<XSSFilter> xssFilter = new FilterRegistrationBean<>();
* xssFilter.setFilter(new XSSFilter());
* xssFilter.setName("xssFilter");
* xssFilter.addUrlPatterns("/*");
* return xssFilter;
* }
* }
*
*/
public class XSSFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(new XSSHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
}
private static class XSSHttpServletRequestWrapper extends HttpServletRequestWrapper {
XSSHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> parameterMap = super.getParameterMap();
if (MapUtils.isNotEmpty(parameterMap)) {
Map<String, String[]> escapeMap = new HashMap<>();
parameterMap.forEach((k, v) -> {
if (StringUtils.isNotBlank(k)) {
String k1 = StringEscapeUtils.escapeHtml4(k);
if (Objects.isNull(v)) {
escapeMap.put(k1, null);
} else {
String[] v1 = new String[v.length];
for (int i = 0; i < v.length; i ++) {
v1[i] = StringEscapeUtils.escapeHtml4(v[i]);
}
escapeMap.put(k1, v1);
}
}
});
return escapeMap;
}
return parameterMap;
}
@Override
public String getHeader(String name) {
return StringEscapeUtils.escapeHtml4(super.getHeader(name));
}
@Override
public String getQueryString() {
return StringEscapeUtils.escapeHtml4(super.getQueryString());
}
@Override
public String getParameter(String name) {
return StringEscapeUtils.escapeHtml4(super.getParameter(name));
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (Objects.nonNull(values) && values.length > 0) {
int length = values.length;
String[] escapeValues = new String[length];
for (int i = 0; i < length; i ++) {
escapeValues[i] = StringEscapeUtils.escapeHtml4(values[i]);
}
return escapeValues;
}
return values;
}
}
}
3.2 nginx 层方案
3.2.1 方案1
nginx.conf 里的 server { listen:80 ... } 大括号中配置下述内容
if ($request_method !~* GET|POST) { return 444; }
#使用444错误代码可以更加减轻服务器负载压力。
#防止SQL注入
if ($query_string ~* (\$|'|--|[+|(%20)]union[+|(%20)]|[+|(%20)]insert[+|(%20)]|[+|(%20)]drop[+|(%20)]|[+|(%20)]truncate[+|(%20)]|[+|(%20)]update[+|(%20)]|[+|(%20)]from[+|(%20)]|[+|(%20)]grant[+|(%20)]|[+|(%20)]exec[+|(%20)]|[+|(%20)]where[+|(%20)]|[+|(%20)]select[+|(%20)]|[+|(%20)]and[+|(%20)]|[+|(%20)]or[+|(%20)]|[+|(%20)]count[+|(%20)]|[+|(%20)]exec[+|(%20)]|[+|(%20)]chr[+|(%20)]|[+|(%20)]mid[+|(%20)]|[+|(%20)]like[+|(%20)]|[+|(%20)]iframe[+|(%20)]|[\<|%3c]script[\>|%3e]|javascript|alert|webscan|dbappsecurity|style|confirm\(|innerhtml|innertext)(.*)$) { return 555; }
if ($uri ~* (/~).*) { return 501; }
if ($uri ~* (\\x.)) { return 501; }
#防止SQL注入
if ($query_string ~* "[;'<>].*") { return 509; }
if ($request_uri ~ " ") { return 509; }
if ($request_uri ~ (\/\.+)) { return 509; }
if ($request_uri ~ (\.+\/)) { return 509; }
#if ($uri ~* (insert|select|delete|update|count|master|truncate|declare|exec|\*|\')(.*)$ ) { return 503; }
#防止SQL注入
if ($request_uri ~* "(cost\()|(concat\()") { return 504; }
if ($request_uri ~* "[+|(%20)]union[+|(%20)]") { return 504; }
if ($request_uri ~* "[+|(%20)]and[+|(%20)]") { return 504; }
if ($request_uri ~* "[+|(%20)]select[+|(%20)]") { return 504; }
if ($request_uri ~* "[+|(%20)]or[+|(%20)]") { return 504; }
if ($request_uri ~* "[+|(%20)]delete[+|(%20)]") { return 504; }
if ($request_uri ~* "[+|(%20)]update[+|(%20)]") { return 504; }
if ($request_uri ~* "[+|(%20)]insert[+|(%20)]") { return 504; }
if ($query_string ~ "(<|%3C).*script.*(>|%3E)") { return 505; }
if ($query_string ~ "GLOBALS(=|\[|\%[0-9A-Z]{0,2})") { return 505; }
if ($query_string ~ "_REQUEST(=|\[|\%[0-9A-Z]{0,2})") { return 505; }
if ($query_string ~ "proc/self/environ") { return 505; }
if ($query_string ~ "mosConfig_[a-zA-Z_]{1,21}(=|\%3D)") { return 505; }
if ($query_string ~ "base64_(en|de)code\(.*\)") { return 505; }
if ($query_string ~ "[a-zA-Z0-9_]=http://") { return 506; }
if ($query_string ~ "[a-zA-Z0-9_]=(\.\.//?)+") { return 506; }
if ($query_string ~ "[a-zA-Z0-9_]=/([a-z0-9_.]//?)+") { return 506; }
if ($query_string ~ "b(ultram|unicauca|valium|viagra|vicodin|xanax|ypxaieo)b") { return 507; }
if ($query_string ~ "b(erections|hoodia|huronriveracres|impotence|levitra|libido)b") {return 507; }
if ($query_string ~ "b(ambien|bluespill|cialis|cocaine|ejaculation|erectile)b") { return 507; }
if ($query_string ~ "b(lipitor|phentermin|pro[sz]ac|sandyauer|tramadol|troyhamby)b") { return 507; }
#这里大家根据自己情况添加删减上述判断参数,cURL、wget这类的屏蔽有点儿极端了,但要“宁可错杀一千,不可放过一个”。
if ($http_user_agent ~* YisouSpider|ApacheBench|WebBench|Jmeter|JoeDog|Havij|GetRight|TurnitinBot|GrabNet|masscan|mail2000|github|wget|curl|Java|python) { return 508; }
#同上,大家根据自己站点实际情况来添加删减下面的屏蔽拦截参数。
if ($http_user_agent ~* "Go-Ahead-Got-It") { return 508; }
if ($http_user_agent ~* "GetWeb!") { return 508; }
if ($http_user_agent ~* "Go!Zilla") { return 508; }
if ($http_user_agent ~* "Download Demon") { return 508; }
if ($http_user_agent ~* "Indy Library") { return 508; }
if ($http_user_agent ~* "libwww-perl") { return 508; }
if ($http_user_agent ~* "Nmap Scripting Engine") { return 508; }
if ($http_user_agent ~* "~17ce.com") { return 508; }
if ($http_user_agent ~* "WebBench*") { return 508; }
if ($http_user_agent ~* "spider") { return 508; } #这个会影响国内某些搜索引擎爬虫,比如:搜狗
#拦截各恶意请求的UA,可以通过分析站点日志文件或者waf日志作为参考配置。
if ($http_referer ~* 17ce.com) { return 509; }
#拦截17ce.com站点测速节点的请求,所以明月一直都说这些测速网站的数据仅供参考不能当真的。
if ($http_referer ~* WebBench*") { return 509; }
#拦截WebBench或者类似压力测试工具,其他工具只需要更换名称即可。
3.2.2 方案2
借助 lua 脚本及相关XSS模块来实现
参考资料
https://www.jianshu.com/p/22d726179ecb
https://www.cnblogs.com/mao2080/p/9460397.html (XSS攻击)
https://www.jianshu.com/p/36b3578a6629 (常见的XSS攻击脚本汇总)
https://www.cnblogs.com/tugenhua0707/p/10909284.html (XSS防御)
https://www.imydl.tech/lnmp/762.html (nginx防止XSS攻击)
网友评论