随着互联网的高速发展,各种网络黑产的技术使用门槛也在逐步降低,这些年来,针对用户的个人信息被盗用,密码泄露等网络安全问题层出不穷,随着问题的暴露,人们对个人信息的安全问题愈发的重视。
点击阅读浅谈跨站脚本攻击(XSS)那些事儿
随着互联网的高速发展,各种网络黑产的技术使用门槛也在逐步降低,这些年来,针对用户的个人信息被盗用,密码泄露等网络安全问题层出不穷,随着问题的暴露,人们对个人信息的安全问题愈发的重视。
开发者不仅要面对传统的XSS,CSRF攻击手段,还要面对网络劫持,中间人攻击,非法调用API等等各种新型的攻击方式挑战。
本文就浅谈Java服务端中XSS相关的那点事情。
什么是XSS?
跨站脚本攻击(Cross-site scripting,XSS)是一种安全漏洞,攻击者可以利用这种漏洞在网站上注入恶意的客户端代码。若受害者运行这些恶意代码,攻击者就可以突破网站的访问限制并冒充受害者。根据开放式 Web 应用安全项目(OWASP),XSS在2017年被认为7种最常见的Web应用程序漏洞之一。
如果Web应用程序没有部署足够的安全验证,那么,这些攻击很容易成功。浏览器无法探测到这些恶意脚本是不可信的,所以,这些脚本可以任意读取 cookie,session tokens,或者其它敏感的网站信息,或者让恶意脚本重写HTML内容。
XSS 攻击可以分为 3 类:存储型(持久型)、反射型(非持久型)、DOM 型。
存储型 XSS
一般是指注入恶意内容,且服务器由于没有过滤或拦截,导致恶意内容存储到数据库。再经由其他用户访问下发给其他访问的用户执行。例如:论天的聊天,微博的回复等等。存储型也是危害影响最严重的一种XSS
反射型 XSS
应用程序或 API 包括未经验证和未经转义的用户输入作为 HTML 输出的一部分。成功的攻击可以让攻击者在受害者的浏览器中执行任意 HTML 和 JavaScript。通常,用户需要与一些指向攻击者控制页面的恶意链接进行交互,例如论坛、微博评论,访问日志等。
基于 DOM 的 XSS
通过劫持或注入等手段修改原始的客户端代码(HTML, JavaScript, CSS, URL),页面本身可能并没有变化,实际一串包含恶意的代码已经被意外执行。 相对来说,最难避免,同时也是危害相对较轻的一种XSS
XSS检测
1
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
更多更详细的使用这段 恶意代码 的攻击策略和演示,请查看 Unleashing-an-Ultimate-XSS-Polyglot。
XSS预防
反射型XSS和存储型XSS的预防
主要是输入数据校验和HTML属性,HTML正文, Javascript,CSS,URL上下文的输出编码, 避免将不可信数据当作代码下发给浏览器,被浏览器错误的执行。
详细的XSS防护规则总结 OWASP
基于DOM的XSS预防
主要是前端的一些防御措施
- 避免直接用 .innerHTML, .outerHTML, .write, .innerHTML, 使用 .innerText, .textContent和 element.setAttribute()(对部分属性(e.g. width, height, ref .etc)是安全的,对href,onclick等属性也是不安全的)
- 避免将不可信数据拼接字符串传入给那些能将字符串当代码执行的API。e.g. onlick, onload,eval(), setTimeout(), setInterval(), href.etc
- 避免不可信数据对对象属性的访问, 仅将不可信数据当作右值使用。例如:object[x]
- 避免直接使用eval()将json转换为javascript对象。使用JSON.toJSON()和JSON.paree()
- 使用安全沙箱执行JavaScript脚本
目前主流的前端框架都针对XSS攻击有相关的防护和最佳实践文档, 例如:Vue.js 3.x, Angular JS。
Java服务端相关
在服务端方面,更多是针对反射型和存储型XSS的防护,主要由以下几个方面的内容。
不可信数据的HTML编码
使用编码库直接将不可信数据(e.g. url参数名/参数值,header,attributes .etc)进行编码检查。可以使用现有的一些第三方编码库Apache Commons Text, Apache Commons Lang3 StringEscapeUtils工具或者OWASP Java 编码器 等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 使用common-lang3 3.7
class XSSRequestWrapper extends HttpServletRequestWrapper {
private Map<String, String[]> sanitizedQueryString;
public XSSRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String parameter = null;
String[] vals = getParameterMap().get(name);
if (vals != null && vals.length > 0) {
parameter = vals[0];
}
return parameter;
}
@Override
public String[] getParameterValues(String name) {
return getParameterMap().get(name);
}
@Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(getParameterMap().keySet());
}
@Override
public Object getAttribute(String name) {
Object value = super.getAttribute(name);
if (value instanceof String) {
return StringEscapeUtils.escapeHtml4((String) value);
}
return value;
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
return StringUtils.isBlank(value) ? value : StringEscapeUtils.escapeHtml4(value);
}
@Override
public Map<String, String[]> getParameterMap() {
if (sanitizedQueryString == null) {
Map<String, String[]> originalQueryString = super.getParameterMap();
Map<String, String[]> res = new HashMap<>();
if (originalQueryString != null) {
for (Map.Entry<String, String[]> entry : originalQueryString.entrySet()) {
String[] values = null;
if (entry.getValue().length == 1) {
values = new String[]{StringEscapeUtils.escapeHtml4(entry.getValue()[0])};
} else {
values = Arrays.stream(entry.getValue()).map(StringEscapeUtils::escapeHtml4).toArray(String[]::new);
}
res.put(StringEscapeUtils.escapeHtml4(entry.getKey()), values);
}
}
sanitizedQueryString = res;
}
return sanitizedQueryString;
}
}
启用内容安全策略(CSP)
Content-Security-Policy是现代浏览器用来增强文档(或网页)安全性的 HTTP 响应标头的名称。Content-Security-Policy 标头允许您限制资源(如 JavaScript、CSS 或浏览器加载的几乎任何内容)的方式。
实现Java Servlet的Filter给每个请求的Header追加CSP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ContentSecurityPolicyFilter implements Filter {
private static final String DEFAULT_CSP = "default-src 'self' data: 'unsafe-inline' 'unsafe-eval';" +
" frame-src 'none'; " +
" object-src 'none'; " +
" report-uri /status;";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (response instanceof HttpServletResponse) {
String csp = ((HttpServletResponse) response).getHeader(HttpHeaderNames.CONTENT_SECURITY_POLICY.toString());
if (StringUtils.isBlank(csp)) {
((HttpServletResponse) response).setHeader(HttpHeaderNames.CONTENT_SECURITY_POLICY.toString(), DEFAULT_CSP);
}
}
chain.doFilter(request, response);
}
}
Google 提供的免费的在线CSP评估工具,针对你的CSP设置进行安全校验,建议和评估。CSP Evaluator
Cookie
反射型XSS一般都是盗取用户的Cookie,并伪造用户登录从而欺骗服务器窃取数据。现代浏览器基本上都支持设置Cookie的Http-Only属性,可以避免客户端访问cookie数据。启用Http-Only和Secure示例:
1
Set-Cookie: Secure; HttpOnly
实现Java Servlet的Filter给响应设置Cookie配置。
1
2
3
4
5
6
7
8
9
10
11
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (response instanceof HttpServletResponse hsr) {
// set-cookie: Secure; HttpOnly
String cookie = hsr.getHeader(HttpHeaderNames.SET_COOKIE.toString());
if (!cookie.contains(DEFAULT_COOKIE)) {
hsr.setHeader(HttpHeaderNames.SET_COOKIE.toString(), cookie + DEFAULT_COOKIE);
}
}
chain.doFilter(request, response);
}
也可以使用web.xml配置
1
2
3
4
5
6
<session-config>
<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
</session-config>
更详细的信息查看MDN HTTP Cookie
不暴露非必要的服务
针对攻击,最好的手段是物理隔绝,不和你玩了,免疫你全部攻击。在设计和架构上对于没有必要开放给用户的页面和端口等,可以使用服务剥离,防火墙等手段将风险隔绝在门外。
引用参考
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 TinyZ Zzh (包含链接: https://tinyzzh.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。 如有任何疑问,请 与我联系 (tinyzzh815@gmail.com) 。
评论