# 漏洞描述
# 影响版本
1 | 2.5.0 <= Struts <= 2.5.32 |
# 修复版本
1 | Apache Struts2 >= 2.5.33 |
# 环境搭建
测试版本
1 | <dependency> |
百度网盘
# 漏洞分析
首先看下官方修复的代码
https://github.com/apache/struts/commit/162e29fee9136f4bfd9b2376da2cbf590f9ea163
这里可以看到有几处都讲参数转换为小写,漏洞应该和这个有关系
我们先上传一个文件
1 | POST /S2_066_war_exploded/upload.action HTTP/1.1 |
打上断点看下流程
在 org.apache.struts2.interceptor.FileUploadInterceptor#intercept 中
这里通过 multiWrapper.getFileNames 方法,对 wrapper 封装的 request 对象以及 inputname 来获取到 filename
通过数组加载进 accept 方法
然后判断不为空开始遍历 accept,将其加载进参数
再来看 strut 是如何处理文件名的
大概理解下代码
1 | protected String getCanonicalName(final String originalFileName) { |
匹配最后一个 / 的位置
在本例中则为 2,也就是 forwardSlash
而…/1.txt 中没有 \\
所以 backwardSlash 为 - 1
所以 if 条件满足
执行 fileName = fileName.substring (forwardSlash + 1);
赋值新的 filename,也就是 / 后面的内容,也就是请求的文件 1.txt
最终返回
并 set 存储到上传对象中
这段代码也就是拦截了路径穿越
以上上传文件的对象最终会保存到 HttpParameter 参数中
所以看下是不是可以变量覆盖
其实刚出的时候看过官方的 commit,看到修改了几个小写
我想到的就是大小写绕过,但是不可能这么简单,就在想是不是什么地方或者是什么加载器也加载了文件和文件内容,导致文件上传。
先来看看上下文对象获取的大概流程
上下文是从 ac.getParameters () 获取的
一直跟进到 ActionContext 下的 get 方法
下面就是找上下文的创建,在 org.apache.struts2.dispatcher.Dispatcher#serviceAction
这里获取上下文是 map 结构存储,key 唯一
那这里就不太可能存在变量覆盖
然后再看会不会是参数绑定
在 com.opensymphony.xwork2.interceptor.ParametersInterceptor#doIntercept
这里参数绑定会经过三个
1 | params.entrySet() |
其中 params 和 parameters 都是通过 HttpParamteters 对象加载上传文件和类型,内容
而 acceptableParameters 是通过 TreeMap 加载的
但是这个加载是有顺序的
通过代码测试
1 | import java.util.TreeMap; |
可以看到会优先大写的
这里直接跟着 Y4 爷的步伐。。。
1 | POST /upload.action HTTP/1.1 |
在 ognl.OgnlRuntime#_getSetMethod 获取 setter ⽅法时调⽤了 ognl.OgnlRuntime#getDeclaredMethods 做处理
这里了遍历方法名,加载到 set 方法,setUpload
得到值为 1,也就是 public 修饰符
后去就是 m 的值赋给新的成员变量,到达 result 返回
最终通过_getSetMethod 返回 method
中间就都是一些类的处理,不是很重要
我们直接看 addIfAccessor 方法
1 | private static void addIfAccessor(List result, Method method, String baseName, boolean findSets) |
大概梳理下逻辑
首先检查方法名是否是以’baseName’结尾
如果是,进一步检查是否以几个前缀开始的
如果是 is 开头长度为 2,不是则为 3
如果和要找的设置器响度相等,就比较 ms 方法减去前缀,其实也就是 baseName 的方法名,如果一致,就添加到 result 中
其中关于 baseName,我们来看下 getDeclaredMethods
其中这部分
1 | String baseName = capitalizeBeanPropertyName(propertyName); |
经过 capitalizeBeanPropertyName 方法处理后得到 baseName
1 | char first = propertyName.charAt(0); |
简单描述下就是,如果传过来的 baseName,首字母是小写的,第二个字母是大写的,直接返回
否则就大写第一个字母返回
又因为我们要触发的是 com.struts2.UploadAction#setUploadFileName
其中 baseName 也就是 UploadFileName
那就只能写成 UploadFileName 或者是 uploadFileName
poc1:
1 | POST /S2_066_war_exploded/upload.action HTTP/1.1 |
poc2:
1 | POST /S2_066_war_exploded/upload.action?uploadFileName=../1234.jsp HTTP/1.1 |
# 漏洞修复
看 diff 不难发现官方将传递的参数改为大小写不敏感,检查当前键是否与 nameLowerCase 相等,忽略大小写,这样就会覆盖我们传递的值