参考:
https://attackerkb.com/topics/Q5f0ItSzw5/cve-2023-22515/rapid7-analysis
https://mp.weixin.qq.com/s/rIfYrO1i4LPpgCGyxSLHUg
https://blog.s1r1us.ninja/research/brokenconflu
漏洞复现和分析
复现起来非常简单,在首页加上参数
1 | /login.action?bootstrapStatusProvider.applicationConfig.setupComplete=false |
就能访问 /setup
添加管理员了
1 | /setup/setupadministrator-start.action |
原理也很简单,因为在默认的 interceptor
中包含了一个:
1 | com.atlassian.xwork.interceptors.SafeParametersInterceptor |
它继承了父类并且调用了 super.doIntercept(invocation)
:
1 | com.opensymphony.xwork2.interceptor.ParametersInterceptor |
这里做的东西大概就是获取参数然后进入到 setParameters
,这部分和 S2-003
那个洞一样的入口,都是这个 ParametersInterceptor
:
https://zhzhdoai.github.io/2020/12/24/Struts2%E6%BC%8F%E6%B4%9E%E7%AC%94%E8%AE%B0%E4%B9%8BS2-003/
7.x 的分析
那么为什么 7.x
的版本没有呢,因为关键类 SafeParametersInterceptor
并没有继承 ParametersInterceptor
,然后整体也没有调用到这个 ParametersInterceptor
。
虽然 SafeParametersInterceptor
这个类也是有一些 ognl 的操作的,但是他有一个函数:
进入函数,测试调用一下会发现 a.b
也会返回 false
:
稍微看一下:
能看到这里最基本的就是不能有 .
,有的话就需要进入 isSafeComplexParameterName
再额外一些判断,这里就不细跟了。
8.5.2 的修复
在 8.5.2
中,依然会继承 ParametersInterceptor
,但是它会过经过一些判断
首先就是:
1 | com.opensymphony.xwork2.interceptor.ParametersInterceptor#setParameters |
这里判断是否是可接受的参数:
1 | com.opensymphony.xwork2.interceptor.ParametersInterceptor#isAcceptableParameter |
然后跳过一些判断,直接讲两个比较关键的判断,首先是判断是否是 acceptable
的,就是要满足这个正则:
1 | com.atlassian.xwork.interceptors.SafeParametersInterceptor#isAcceptableParameter |
这里的:
1 | acceptedPatterns = \w+((\.\w+)|(\[\d+])|(\['\w+']))* |
然后就是这里:
1 | com.atlassian.xwork.interceptors.SafeParametersInterceptor#isSafeComplexParameter |
首先是最上面的 com.atlassian.xwork.interceptors.SafeParametersInterceptor#isComplexParameter
,判断如果包含 .
或者满足这个正则 ".*\\['\\w+']"
就代表复杂。
复杂的话就会获取 Descriptors
然后又要判断方法是否有 ParameterSafe
这个注解(基本是不用看了感觉~
总结一下,首先是必须要满足上面的 acceptedPatterns
,然后最好不要满足 isComplexParameter
。
这里举个例子,比如:
1 | a[0] = 1 |
这里 0
修复对比
说了这么多,好像还没说新旧版本具体的区别,其实就是在新版的:
1 | com.atlassian.xwork.interceptors.SafeParametersInterceptor |
多了个函数 isAcceptableParameter
:
这个函数做了一些额外的判断,在旧版本是没有的。
虽然默认来说
1 | com.opensymphony.xwork2.interceptor.ParametersInterceptor |
这个类有个默认的 isAcceptableParameter
函数,但是并没有起到非常具体的过滤作用,新版本的判断了要 set 的方法是否存在 ParameterSafe
的注解。
一些扩展思考
其实类似的判断在 7.x
的版本就有了:
1 | com.atlassian.xwork.interceptors.SafeParametersInterceptor#isSafeParameterName(java.lang.String, com.opensymphony.xwork.Action, boolean) |
判断了是否包含 .
啊,是否符合 a['b']
这样的格式啊。
然后 isSafeComplexParameterName
下面就是判断了 write
的方法是否是 ParameterSafe
这个注解。
在最上面的第三个参考链接中提到了一个绕过方式,就是:
1 | ?a['a'][0]=abc |
这样的方法,为什么可以绕过这个函数呢:
第一个比较严苛的条件:
首先是需要符合这个正则:
1 | \w+((\.\w+)|(\[\d+\])|(\['[\w.]*'\]))* |
a['b'][数字]
这样的格式是可以过的
然后到 else
:
不能符合这个正则:
1 | .*\['[a-zA-Z0-9_]+'\] |
然后如果能找到这样的方式利用的话,7.X
也就可以利用了。