0%

Apache Struts2 S2-067 任意文件上传漏洞(CVE-2024-53677)

漏洞描述

Apache Struts是美国阿帕奇(Apache)基金会的一个开源项目,是一套用于创建企业级Java Web应用的开源MVC框架。
Apache Struts在特定条件下,存在文件上传漏洞(网宿评分:高危、CVSS 3.0 评分:8.1):
攻击者可以操纵文件上传参数来实现路径遍历,在某些情况下,这可能导致恶意文件上传。

影响版本

Struts 2.0.0 - 2.3.37(EOL)
Struts 2.5.0 - 2.5.33(EOL)
Struts 6.0.0 - 6.3.0.2

环境搭建

S2-066

漏洞成因

S2-067S2-066有90%的相似,S2-067S2-066补丁的绕过。
通过前一篇文章的分析我们直到S2-066的修复是在向ActionContextparameters属性添加一个参数,先检查parameters属性是否为空,如果为空,则直接添加参数,
如果parameters不为空,则遍历文件上传的参数ContentTypeUploadFileName等,将其先同意转换为小写,然后在与parameters中的元素比较,
当存在相同的元素的时候先删除parameters中的参数再将新的参数添加到parameters中,这样就避免了用户上传表单中非文件参数对文件上传参数的影响覆盖。
通过对S2-066的学习我们还可以直到,form表单参数的name属性的值会被当作OGNL解析,用来向ValueStack上的root映射中的元素设置属性值。
既如此,name属性的值就可以是任何有效的OGNL表达式,从而实现任意属性的设置。
当我们使用这样的表达是的时候top.uploadFileName会发生什么。
让我们定位到com.opensymphony.xwork2.interceptor.ParametersInterceptor.setParameters方法
这里面调用了ValueStacksetParameter方法向ValueStackroot映射中设置属性值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected void setParameters(final Object action, ValueStack stack, HttpParameters parameters) {

for (Map.Entry<String, Parameter> entry : acceptableParameters.entrySet()) {
String name = entry.getKey();
Parameter value = entry.getValue();
try {
newStack.setParameter(name, value.getObject()); // name == top.uploadFileName
} catch (RuntimeException e) {
if (devMode) {
notifyDeveloperParameterException(action, name, e.getMessage());
}
}
}
...
}

通过setParaameters的方法签名也可以看出来其第一个参数是一个OGNL表达式。

1
2
3
public void setParameter(String expr, Object value) {
setValue(expr, value, devMode);
}

程序继续向下执行最终会来到 com.opensymphony.xwork2.ognl.OgnlUtil.compileAndExecute方法。
这个方法会根据ognl表达式来获取ognl表达式对应的ognl树,然后解析ognl树。
ognl表达式为uploadFileName的时候,得到的treeASTProperty类型,S2-066就是执行ASTPropertysetValueBody方法设置ValueStackroot映射中的元素。
ognl表达式为top.uploadFileName的时候,得到的treeASTChain类型,S2-067就是执行ASTChainsetValueBody方法设置ValueStackroot映射中的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private <T> Object compileAndExecute(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException {
Object tree;
if (enableExpressionCache) {
tree = expressions.get(expression);
if (tree == null) {
tree = Ognl.parseExpression(expression);
checkEnableEvalExpression(tree, context);
}
} else {
tree = Ognl.parseExpression(expression);
checkEnableEvalExpression(tree, context);
}

final T exec = task.execute(tree);
// if cache is enabled and it's a valid expression, puts it in
if (enableExpressionCache) {
expressions.putIfAbsent(expression, tree);
}
return exec;
}

ASTChainsetValueBody方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void setValueBody(OgnlContext context, Object target, Object value) throws OgnlException {
boolean handled = false;
int i = 0;

for(int ilast = this._children.length - 2; i <= ilast; ++i) {
...

if (!handled) {
target = this._children[i].getValue(context, target);
}
}

if (!handled) {
this._children[this._children.length - 1].setValue(context, target, value);
}

}

ASTChainChildren包含两个元素均为ASTProperty类型,就是通过.top.uploadFileName进行了分割成两部分。
首先通过top节点去取target的值,注意这里target之前是有值的就是ValueStackroot映射部分,即一个CompoundRoot对象,包含两个元素
UploadAction以及DefaultTextProvider对象。这里也就是通过top节点取取一个值来覆盖原来的target
img.png
代码继续向下执行会来到com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor.getProperty方法。
这里判断name是否为top,如果为top则返回root的第一个元素,否则会遍历root中的元素,如果元素中有name属性则返回该元素。
root的第一个元素即为UploadAction,即上一步求的targetUploadAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public Object getProperty(Map context, Object target, Object name) throws OgnlException {
CompoundRoot root = (CompoundRoot) target;
OgnlContext ognlContext = (OgnlContext) context;

if (name instanceof Integer) {
Integer index = (Integer) name;
return root.cutStack(index);
} else if (name instanceof String) {
if ("top".equals(name)) {
if (root.size() > 0) {
return root.get(0);
} else {
return null;
}
}

for (Object o : root) {
if (o == null) {
continue;
}

try {
if ((OgnlRuntime.hasGetProperty(ognlContext, o, name)) || ((o instanceof Map) && ((Map) o).containsKey(name))) {
return OgnlRuntime.getProperty(ognlContext, o, name);
}
} catch (OgnlException e) {
if (e.getReason() != null) {
final String msg = "Caught an Ognl exception while getting property " + name;
throw new XWorkException(msg, e);
}
} catch (IntrospectionException e) {
// this is OK if this happens, we'll just keep trying the next
}
}

//property was not found
if (context.containsKey(OgnlValueStack.THROW_EXCEPTION_ON_FAILURE))
throw new NoSuchPropertyException(target, name);
else
return null;
} else {
return null;
}
}

重新求得target后则会调用uploadFileName的节点得setValue方法为target设置值。
uploadFileName节点是ASTProperty类型,这就是典型得OGNL表达式的语法了,后面的内容就与S2-066的逻辑一致了。
因为在S2-067中我们使用的payloadtop.uploadFileNameUploadFileName在统一大小写后也并不相等,所以就能完美的绕过S2-066添加的remove方法的补丁了。

1
this._children[this._children.length - 1].setValue(context, target, value);

另外,ValueStackroot映射中包含两个元素,一个是UploadAction,一个是DefaultTextProvider,既然UploadAction的属性可以被设置,那么DefaultTextProvider的属性也是可以被设置的。
这样我们就可以控制DefaultTextProvider对象的属性值,是不是可以做些什么呢?

漏洞修复

新增了一个类并被推荐使用。。。。FileUploadAction并没有被修改,应该是这样的。赶时间

参考链接

Buy me a coffee.

欢迎关注我的其它发布渠道