浅谈跨站脚本攻击(XSS)那些事儿

1640 字
8 分钟
浅谈跨站脚本攻击(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预防#

主要是前端的一些防御措施

  1. 避免直接用 .innerHTML, .outerHTML, .write, .innerHTML, 使用 .innerText, .textContentelement.setAttribute()(对部分属性(e.g. width, height, ref .etc)是安全的,对href,onclick等属性也是不安全的)
  2. 避免将不可信数据拼接字符串传入给那些能将字符串当代码执行的API。e.g. onlick, onload,eval(), setTimeout(), setInterval(), href.etc
  3. 避免不可信数据对对象属性的访问, 仅将不可信数据当作右值使用。例如:object[x]
  4. 避免直接使用eval()将json转换为javascript对象。使用JSON.toJSON()和JSON.paree()
  5. 使用安全沙箱执行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.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:

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

JEPS

反射型XSS一般都是盗取用户的Cookie,并伪造用户登录从而欺骗服务器窃取数据。现代浏览器基本上都支持设置Cookie的Http-Only属性,可以避免客户端访问cookie数据。启用Http-Only和Secure示例:

Set-Cookie: Secure; HttpOnly

实现Java Servlet的Filter给响应设置Cookie配置。

@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配置

<session-config>
<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
</session-config>

更详细的信息查看MDN HTTP Cookie

不暴露非必要的服务#

针对攻击,最好的手段是物理隔绝,不和你玩了,免疫你全部攻击。在设计和架构上对于没有必要开放给用户的页面和端口等,可以使用服务剥离,防火墙等手段将风险隔绝在门外。

引用参考#

  1. OWASP XSS
  2. OWASP DOM XSS

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
浅谈跨站脚本攻击(XSS)那些事儿
https://tinyzzh.github.io/posts/2022-10-09-xss_java/
作者
TinyZ Zzh
发布于
2022-10-09
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
TinyZ Zzh
专注于高并发服务器、网络游戏相关(Java、PHP、Unity3D、Unreal Engine等)技术,热爱游戏事业, 正在努力实现自我价值当中。
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
211
分类
38
标签
200
总字数
337,853
运行时长
0
最后活动
0 天前

文章目录