参考:
远程调试:
Struts2-001 远程代码执行漏洞浅析-安全客 - 安全资讯平台
不错的bypass:
https://github.blog/2023-01-27-bypassing-ognl-sandboxes-for-fun-and-charities/
OGNL基础
学好OGNL注入的第一步,先学OGNL基础,其实这一部分拖欠了挺久的,一直给其他事情冲散了,今天就给他补上,换了新头像:可爱的奈月(OneRoom)
先导入一下依赖:
1 | <dependency> |
(1)OGNL三要素
- Expression表达式
- root根对象、即操作对象
- context上下文,用于保存对象运行的属性及值,有点类似运行环境的意思,保存了环境变量
下面是一个比较简单的Demo:
1 | package org.example; |
1 | package org.example; |
1 | package org.example; |
运行上述代码即可获取org.example.Tester.user.name
的值,注意是.user.name
的值,上述我们是创建了一个tester,并且让他的user属性为一个User对象,表达式为user.name
也就是获取tester的user属性的name属性。
这一点和网上一些demo有些许不同,在这里给他纠正了
(2)OGNL语法
.
操作符,就和上面第一个demo的作用一样,用于调用对象的属性和方法user.name
;且上一个节点的结果作为下一个节点的上下文,如(#a=new java.lang.String("calc")).(@java.lang.Runtime@getRuntime().exec(#a))
;也可以换成逗号(#a=new java.lang.String("calc")),(@java.lang.Runtime@getRuntime().exec(#a))
如下:
可以发现它执行的方式有点类似递归,他把.
前面的表达式当做结果给后面的表达式执行了
这里需要注意一下#
前我们用括号包裹起来了,这是为了符合语法,假如去掉那一层包裹会报错:
@
操作符:用于调用静态属性、静态方法、静态变量,如上述的@java.lang.Runtime@getRuntime().exec
#
操作符:
用于调用非root对象:
1 | package org.example; |
倘若我们不加那个#,那么就会报错
因为它并不是根对象
用于创建Map:#{"name": "chenlvtang", "level": "noob"}
用于定义变量:
如一开始的例子#a=new java.lang.String("calc")
,定义了一个字符串常量
- $操作符:一般用于配置文件,${name}
- %操作符:计算其中的OGNL表达式,%{hacker.name}
- List:直接使用{“green”, “red”, “blue”}创建
- 对象创建:new java.lang.String[]{“foobar”}
(3)OGNL版本限制
在OGNL>=3.1.25 3.1.12版本设置了黑名单:
1 | public static Object invokeMethod(Object target, Method method, Object[] argsArray) |
(4)投影与选择
OGNL 支持类似数据库当中的选择与投影功能。
- 投影:选出集合当中的相同属性组合成一个新的集合。语法为 collection.{XXX},XXX 就是集合中每个元素的公共属性。
- 选择:选择就是选择出集合当中符合条件的元素组合成新的集合。语法为 collection.{Y XXX},其中 Y 是一个选择操作符,XXX 是选择用的逻辑表达式。选择操作符有 3 种:
- ? :选择满足条件的所有元素
- ^:选择满足条件的第一个元素
- $:选择满足条件的最后一个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21User p1 = new User("name1", 11);
User p2 = new User("name2", 22);
User p3 = new User("name3", 33);
User p4 = new User("name4", 44);
Map<String, Object> context = new HashMap<String, Object>();
ArrayList<User> list = new ArrayList<User>();
list.add(p1);
list.add(p2);
list.add(p3);
list.add(p4);
context.put("list", list);
System.out.println(Ognl.getValue("#list.{age}", context, list));
// [11, 22, 33, 44]
System.out.println(Ognl.getValue("#list.{age + '-' + name}", context, list));
// [11-name1, 22-name2, 33-name3, 44-name4]
System.out.println(Ognl.getValue("#list.{? #this.age > 22}", context, list));
// [User(name=name3, age=33, address=null), User(name=name4, age=44, address=null)]
System.out.println(Ognl.getValue("#list.{^ #this.age > 22}", context, list));
// [User(name=name3, age=33, address=null)]
System.out.println(Ognl.getValue("#list.{$ #this.age > 22}", context, list));
// [User(name=name4, age=44, address=null)]
OGNL Expression解析流程
这一点还是比较重要的,因为过后一些Bypass可能是和这个有关的
断点直接这样给。
进入第一个getValue方法,在这里调用了另一个getValue,并且注意这个ASTchain,在OGNL表达式中,解析和执行就是通过ASTXXXX
这些方法去解析执行的,一共有ASTChain、ASTConst、ASTCtor、ASTInstanceof、ASTList、ASTMethod、ASTStaticField、ASTStaticMethod…..等多种方法,其中最根本的就是Chain
调用Chain的getValue,接下里做一个链式调用
进入evaluateGetValueBody方法里面去。
判断是不是const,这里并不是
获取子节点,并且调用子节点的getValue,随后就这样不断的重复流程
最后进入OgnlRuntime.callMethod
最终执行命令。
S2-001分析
环境搭建
摆了()不太想复现,就是一个OGNL注入罢了
环境搭建采用vulhub进行搭建,vulhub
About this Post
This post is written by Boogipop, licensed under CC BY-NC 4.0.