最近在复盘confluence CVE-2022-26134 这个漏洞,发现根本原因还是Struts2 架构的问题,这块的知识点挺多,不是一篇文章能解释清楚的,因此准备搞个系列,理理Struts2 架构相关的漏洞及原理。
- Ognl
- velocity
- Struts2
- Confluence
Ox01 基础知识
参考【1】中R17a 已经有整理了相关的OGNL相关基础知识,这里不做深入介绍,简单罗列下知识点。
- expression 表达式、相关语法
- context 上下文
- root 对象
0x02 原理
2.1 生成AST树
以以下poc为例
1 | Ognl.getValue("@java.lang.Runtime@getRuntime().exec('open -a Calculator')", context, context.getRoot()); |
Ognl的处理逻辑大致如下
1 | OgnlParser parser = new OgnlParser(new StringReader(expression)); |
R17a师傅的图画的很好,生成的AST树如下:
2.2 执行AST
SimpleNode是AST节点的实现,Ognl.getValue最终会调用SimpleNode.getValueBody/getValue 接口,递归实现整个AST树的执行。
AST数基本结构有很多,在ognl.Ognl路径下,其中几个本实例涉及到的
- ASTChain
- ASTConst
- ASTMethod
- ASTStaticMethod
比较重要的是SimpleNode.getValueBody 接口的实现。
详细的代码跟踪流程R17a师傅文章里也有,不细讲,大致如下。
ASTChain.getValueBody 会正序遍历子节点的getValue 方法,前一个结果会作为后一个的参数
result = this.children[i].getValue(context, result);
第一个节点为ASTStaticMethod,其getValueBody 通过OgnlRuntime.callStaticMethod 生成Runtime实例,作为result
Object var10 = OgnlRuntime.callStaticMethod(context, this.className, this.methodName, args);
第二个节点ASTMethod,其getValueBody 首先会根据它的子节点组建args,然后调用OgnlRuntime.callMethod,调用Runtime.exec(args)
1
2
3
4
5
6for(int icount = args.length; i < icount; ++i) {
args[i] = this.children[i].getValue(context, root);
}
// 其中source参数为上一步的result
// this.methodName为exec
Object result = OgnlRuntime.callMethod(context, source, this.methodName, (String)null, args);
这个case的详细调用栈如下
1 | invokeMethod:500, OgnlRuntime (ognl) |
0x03 Unicode编码
Confluence CVE-2021-26084 的POC中有用到Unicode编码,一些师傅的文章里也有提到过这个特性,例如参考【2】Lotso 师傅的文章。但是没看到关于这块的代码跟踪,这里丰富下。
1 |
|
支持Unicode 的逻辑在ognl.JavaCharStream 中,大致逻辑如下
1 | // 从char buffer中读一个char,目的为了兼容unicode |
0x04 总结与思考
本文简要跟踪了Ognl表达式的原理,主要涉及AST语法树的生成与执行,基本都是老知识,内容量不多。唯一的新增点是unicode的编码里面还有可以玩的一些东西。
思考:
- struts 后续框架引入了SecurityMemberAccess,这个有一些绕过姿势,但是基本都是反射控制开关,可以从Ognl这边出发寻找突破吗?