写点什么

OGNL 表达式注入分析 _

作者:EquatorCoco
  • 2024-03-29
    福建
  • 本文字数:3225 字

    阅读完需:约 11 分钟

OGNL 基础

依赖

<dependency>	<groupId>ognl</groupId>	<artifactId>ognl</artifactId>	<version>3.1.19</version></dependency>
复制代码


OGNL 三要素


  • Expression 表达式

  • root 根对象、即操作对象

  • context 上下文,用于保存对象运行的属性及值,有点类似运行环境的意思,保存了环境变量

看个例子

package org.example;
public class Tester { public User user;
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }}
复制代码


package org.example;
public class User { private String name; private int age; public String getName(){ return name; } public int getAge(){ return age; } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } public User(String name, int age){ this.name = name; this.age = age; }}
复制代码


package org.example;
import ognl.Ognl;import ognl.OgnlContext;import ognl.OgnlException;
public class Main { public static void main(String[] args) throws OgnlException { Tester tester = new Tester(); User user = new User("F12", 20); tester.setUser(user); // 创建context, 设置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); }}
// 输出F12
复制代码

运行以上代码就是获取org.example.Tester.user.name的值,上述我们是创建了一个 tester,并且让他的 user 属性为一个 User 对象,且 tester 设置为 root,表达式为user.name也就是获取 root 即 tester 的 user 属性的 name 属性。


OGNL 语法


  • .操作符

一个例子,(#a=new java.lang.String("calc")).(@java.lang.Runtime@getRuntime().exec(#a)),也可以这样(#a=new java.lang.String("calc")),(@java.lang.Runtime@getRuntime().exec(#a)),中间的点换成逗号。可以发现它执行的方式有点类似递归,他把.前面的表达式当做结果给后面的表达式执行了这里需要注意一下#前我们用括号包裹起来了,这是为了符合语法,假如去掉那一层包裹会报错



  • #操作符

用于调用非 root 对象

package org.example;
import ognl.Ognl;import ognl.OgnlContext;import ognl.OgnlException;
public class Main { public static void main(String[] args) throws OgnlException { Tester tester = new Tester(); User user = new User("F12", 20); tester.setUser(user); // 创建context, 设置root OgnlContext context = new OgnlContext();// context.setRoot(tester); context.put("user", user); // 设置表达式 String expression = "#user.name"; // 解析表达式 Object ognl = Ognl.parseExpression(expression); // 调用获取值 Object value = Ognl.getValue(ognl, context, context.getRoot()); System.out.println(value); }}// 输出F12
复制代码


用于创建 Map#{"name": "f12", "level": "noob"}用于定义变量如一开始的例子#a=new java.lang.String("calc"),定义了一个字符串常量

  • @操作符

用于调用静态属性、静态方法、静态变量,如上述的@java.lang.Runtime@getRuntime().exec


OGNL 版本限制

在 OGNL>=3.1.25 版本中设置了黑名单

public static Object invokeMethod(Object target, Method method, Object[] argsArray)    throws InvocationTargetException, IllegalAccessException{
if (_useStricterInvocation) { final Class methodDeclaringClass = method.getDeclaringClass(); // Note: synchronized(method) call below will already NPE, so no null check. 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("........"); }
复制代码


投影与选择


OGNL 支持类似数据库当中的选择与投影功能。

  • 投影:选出集合当中的相同属性组合成一个新的集合。语法为 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));// [org.example.User@6433a2, org.example.User@5910e440]System.out.println(Ognl.getValue("#list.{^ #this.age > 22}", context, list));// [org.example.User@6433a2]System.out.println(Ognl.getValue("#list.{$ #this.age > 22}", context, list));// [org.example.User@5910e440]
复制代码


OGNL Expression 解析流程


getValue处打个断点,跟进,注意这个 node 的类型ASTchain在 OGNL 表达式中,解析和执行就是通过ASTXXXX这些方法去解析执行的,一共有ASTChainASTConstASTCtorASTInstanceofASTListASTMethodASTStaticFieldASTStaticMethod.....等多种方法,其中最根本的就是 chain



进入 chain 的 getValue 方法



进入evaluateGetValueBody方法,这里判断 context 是不是 const,这里并不是



往下走进入 getValueBody,获取子节点,并进入子节点的 getValue 方法,然后就这样一直循环



最后进入OgnlRuntime.callMethod



一直往下走,这里 invoke,弹出计算器



文章转载自:F12~

原文链接:https://www.cnblogs.com/F12-blog/p/18102189

体验地址:http://www.jnpfsoft.com/?from=001

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
OGNL表达式注入分析 __Java_EquatorCoco_InfoQ写作社区