积跬步至千里

前后端不分离"老"项目,XSS 漏洞处理实践

前言

本月迭代需求没有几个,领导给我派了一个漏洞修复的活。这个项目是一个前后端不分离的ToB老项目,前端使用Velocity 模板+JQuery+miniui,后端是用的 Spring。嗯,前后端不分离,技术老旧,模块众多且耦合,基础设施不完善,让我从一开始校招(2022)刚进来接触到这个项目时候,就感到“畏惧、难受”。虽然后端 Spring 我还了解一些,但是一个十几年的老项目,光 Java 代码就 130 多万行,代码中还充斥着各种二开的代码,再加上业务的复杂,自己本身的知识薄弱,所以一直就是抗拒的!

抗拒能咋办,毕竟工作时常已经快两年了,还能推了不成。那就检验一下自己分析、解决问题的能力进步了没有。

XSS 漏洞

解决问题时,要先了解问题本身(名词含义),产生机制(产生背景),从而在去找一些前人的一些实践经验(文章),对比汲取,再着手去解决问题。

XSS 简介

XSS全称:跨站脚本(Cross Site Scripting) ,为了不和层叠样式表(Cascading Style Sheets)的缩写CSS混合,所以改名为XSS;攻击者会向web页面(input表单、URL、留言版等位置)插入恶意JavaScript代码,导致管理员/用户访问时触发,从而达到攻击者的目的。

XSS 分类

反射型(非持久型)

反射型XSS,又称非持久型XSS,攻击相对于受害者而言是一次性的,具体表现在受害者点击了含有的恶意JavaScript脚本的url,恶意代码并没有保存在目标网站,而Web应用程序只是不加处理的把该恶意脚本“反射”回受害者的浏览器而使受害者的浏览器执行相应的脚本。

举个例子:比如我在一个站点,恶意的传递特殊Url路径参数,如:Src一张图片,站点服务器接收到请求,并没有对路径传参做XSS 过滤,从而接收请求并将请求返回,这张照片就渲染到当前站点的页面内了,恶意的脚本被执行了!

你也可能会想,服务器端又没存储这段恶意脚本,只会影响我自己啊,不会影响其他人啊,反射型 XSS 的危害在哪呢?其实不然,如果有人诱导你去点击一个链接,然后这个网站刚好你是登录过的,你的Cookie等凭证敏感信息都在当前的Domain 下,这个链接刚好是带有恶意脚本的,那么你的Cookie等凭证敏感信息很可能被泄露,从而攻击者盗用你的身份,做一些恶意操作

存储型(持久型)

存储型XSS是指应用程序通过Web请求获取不可信赖的数据,在未检验数据是否存在XSS代码的情况下,便将其存入数据库。当下一次从数据库中获取该数据时程序也未对其进行过滤,页面再次执行XSS代码持续攻击用户。存储型XSS漏洞大多出现在留言板、评论区,用户提交了包含XSS代码的留言到数据库,当目标用户查询留言时,那些留言的内容会从服务器解析之后加载出来。

DOM型(非持久型)

DOM,全称Document Object Model,是一个平台和语言都中立的接口,可以使程序和脚本能够动态访问和更新文档的内容、结构以及样式 ,DOM-XSS简单理解就是不与后台服务器产生数据交互,是一种通过DOM操作前端代码输出的时候产生的问题。

XSS 漏洞危害

  • 反射型 XSS:

危害:
用户凭据泄露:攻击者可以窃取受害者的 Cookie、会话标识符等敏感信息。
执行恶意操作:恶意脚本可以执行用户未授权的操作。
钓鱼攻击:诱导用户输入敏感信息。
扩散攻击面:通过传播恶意链接影响更多用户。

  • 存储型 XSS:

危害:
持久性攻击:恶意脚本存储在服务器上,任何访问受影响页面的用户都会执行这些脚本。
广泛影响:攻击可影响所有访问受感染页面的用户,导致大规模信息泄露、账户劫持等。
恶意传播:恶意脚本可能进一步传播攻击,如在论坛、留言板等公共平台上。

  • DOM 型 XSS:

危害:
前端攻击:脚本直接在用户浏览器的 DOM 环境中执行,绕过了传统的服务器端防御措施。
持续性:攻击持久存在于受影响的前端代码中,通常难以通过服务器端过滤或清理。
执行恶意操作:攻击者可以操纵页面内容、修改页面结构,甚至窃取敏感信息。

XSS 漏洞处理实践

本次扫描出来的 XSS 漏洞一共有2个,反射型一个,存储型一个,下面给出实例

存储型

这是一个存储型的 XSS,业务场景是在上传附件时,恶意构造了文件名,从而触发恶意脚本的执行。
image

这里,公司安全部门给出对于 XSS 漏洞的解决方法是对 html进行实体化编码。那么什么是 html实体化编码呢,能解决什么问题呢?带着疑问我进行了搜索。实体化编码可以简单的理解为对一些特殊字符的转义。比如说,用户上传附件的名字是 <script>alert('XSS');</script>如果后端没有对附件名字做实体化编码或者说转义的话,那么当附件成功上传保存到数据库,然后渲染到前端html页面的时候,浏览器会将附件的名字解释为正常的 js 脚本并执行,那么此时就发生了存储型 XSS

印象中项目的代码有一个 XssFilter,所以我就点开了这个过滤器类

@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse))
			throw new ServletException("此过滤器只保护HTTP资源");
		HttpServletRequest req = (HttpServletRequest) request;
		
		if(StringUtil.ignoreSuffix(req.getRequestURI())){ //在可以忽略的文件后缀中,此类资源文件放行
			chain.doFilter(request, response);
			return;
		}
		if (exclude(req)) { //指定路径通过
			chain.doFilter(request, response);
			return;
		}

		XssHttpRequestWrapper xssRequest = new XssHttpRequestWrapper(req,excludeMap);  
		req = this.wrapRequest(xssRequest);

		//过滤客户端提交表单中特殊字符
		Map<String,String> rules = null;
		if(req.getMethod().equalsIgnoreCase("post")){
			rules = addCustomParams(req, postMap);
		}else{
			rules = addCustomParams(req, getMap);
		}

		this.filterParams(req, rules);

		chain.doFilter(req, response);
	}

眨眼一看上去,这不是有做 XSS 过滤的吗,为啥没有转义呢?仔细的检查了一下 XssHttpRequestWrapper,这个请求包装类中并没有对 POST Multi form-data请求类型做处理,而项目中附件上传请求正式此种请求类型。那好,我就加上针对此种请求的过滤,本地调试一下,我又发现居然在 HttpServletRequest 中拿不到请求参数。

针对于 POST Multi form-data 请求,没法直接在HttpServletRequest 使用 getParameterMap() 等获取请求参数,而是要使用 MultipartResolver将请求进行处理才能拿到请求参数。
image

image

使用MultipartResolver 处理 HttpServletRequest 如下所示
image

此时就可以针对请求参数做 XSS过滤了。但是最终还是没有在 Filter 中去做过滤,我去问了一下我们组的技术大佬军哥,他建议我还是不要动这个过滤器了,因为整个项目(二三十个模块微服务)全部依赖这个Filter,既然只有附件上传时的名称有 XSS注入风险,那就只针对附件上传方法中的附件名称做下实体化编码即可。还是大佬考虑的全面!这要是改出毛病,整个系统都”爆炸“!

反射型 XSS

公司安全部门提的反射型 XSS,其实就是在url路径上构造恶意脚本字符,然后服务端没有进行参数过滤,在请求成功返回后,在浏览器端执行恶意代码。
image

在上面的XssFilter 中对 /tab 路径进行了放行,可能是历史原因,tab 链接页面路径没有强制做XSS防御,而是做成了管控点,还是问了军哥,军哥说你把这个管控点去掉吧。好好好!这种活我最爱了!

参考

posted @ 2024-08-15 19:26  大阿张  阅读(10)  评论(0编辑  收藏  举报