浅谈跨站脚本攻击(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检测
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 编码器 等。
// 使用common-lang3 3.7class 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:
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示例:
Set-Cookie: Secure; HttpOnly实现Java Servlet的Filter给响应设置Cookie配置。
@Overridepublic 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配置
<session-config> <cookie-config> <http-only>true</http-only> <secure>true</secure> </cookie-config></session-config>更详细的信息查看MDN HTTP Cookie
不暴露非必要的服务
针对攻击,最好的手段是物理隔绝,不和你玩了,免疫你全部攻击。在设计和架构上对于没有必要开放给用户的页面和端口等,可以使用服务剥离,防火墙等手段将风险隔绝在门外。
引用参考
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!