Struts2 漏洞 S2-001
Struts2 系列(希望能是个系列)
Struts2 介绍
Struts2 使用 Webwork2 为代码基础,以 xwork 为底层实现的核心,ognl 作为浏览器与 Java 对象数据流转沟通的语言。
Struts2 中请求从输入到输出的过程:
struts2.xml 的核心作用:
标签的解析过程:
解析标签是从 doStartTag 开始的,TextFieldTag 从父类 AbstractTag 的父类 继承了该方法
OGNL
Struts2 漏洞多数是 OGNL 表达式解析的漏洞。
Object-Grafph Navigation Language,一种表达式语言。OGNL 有三大要素:
表达式 (Expression)
表达式是 OGNL 的核心,所有的 OGNL 操作都是针对表达式的解析后进行的。表达式会规定此次 OGNL 操作到底要干什么。
根对象(Root Object)
跟对象是 OGNL 的操作对象,表达式规定干什么,跟对象指定对谁干
上下文环境(Context)
规定 OGNL 的操作环境,其中包含了根对象。跟对象作为一个特殊变量进行处理,对于根对象的存取操作的表达式不需要加
#
进行区分。实现,root 对象和 上下文环境都是 OgnlValueStack 的属性值。
root 对象是一个栈,每一次请求会将请求的 action 压栈,所以在 URL 中可以输入 action 中的属性进行赋值,在参数拦截器中会从 root 栈的栈顶到栈底一次寻找同名的属性进行赋值。
Context 的对象是一个 Map,其中 key 是对象的引用,value 是对象具体的存储信息。其中存储了 OgnlValueStack的引用。
表达式操作,实际就是对 OgnlValueStack 中对象的操作:
对 root 对象的访问
name
,获取 root 对象中 name 属性的值department.name
,获取 root 对象中 department 属性的 name 属性的值department['name']
,department["name"]
,获取 root 对象中 department Map key 为 name 的值
对 context 上下文环境的访问,
#
#introduction
,获取上下文环境中名为 introduction 对象的值#parameters.user
#parameters['user']
,#parameters["user"]
对静态变量、静态方法的访问,
@class@filed/method
@com.example.core.Resource@ENABLE
,访问该类的 ENABLE 属性@com.example.core.Resource@get()
,调用该类的 get 方法
方法调用,与 Java 方法调用相同
group.containsUser(#requestUser)
,调用 root 对象中 group 中的 containsUser 方法,参数为 context 中名为 requestUser 的对象。
S2-001
远程调试 Vulhub 中的 S2-001,找到标签解析的位置 doStartTag
getBean 会调用到 UIBean 的构造函数,获取标签属性。this.populateParams 设置一些标签的属性值。最后经过判断返回不同的值。
doStartTag 结束后会进入 doEndTag 函数,跟进 end 函数
end 首先调用了 evaluateParams 函数,此函数解析每个标签的属性
并且在满足一系列条件时,会组成表达式,并调用 findValue。可以看到此时的标签 name 属性是 username,所以其实 username 也可以触发漏洞
这里还是以输入 password 为 %{1+1}
为例,运行到上图位置,这里会先将 expr 赋值为 %{1+1}
,然后判断 altSyntax
可以看到,此处的 this.stack 是一个 OgnlValueStack,包含 root 和 context ,root 是一个 ArrayList 实现的栈,context 是一个 Map
isUseAltSyntax 会 return
1 | altSyntax |
结果为 true
altSyntax 功能是 Struts 2 框架用于处理标签内容的一种新语法( 不同于普通的 HTML ) , 该功能主要作用在于支持对标签中的 OGNL 表达式进行解析并执行 . 该功能在struts2核心配置文件struts.properties中默认开启 .
于是此处 expr 变为 %{password}
。随后调用 this.findValue(expr, valueClazz)
if 条件满足,会进入 TextParseUtil.translateVariables('%', expr, this.stack)
第一轮循环,首先解析表达式,找到匹配的大括号,确定%{...}
中间内容的索引(start 和 end),最终取到 var,本例中为 password
此处的 o 来自于Object o = stack.findValue(var, asType);
,获取的是 password
真正的值,即 %{1+1}
。findValue 执行了 OGNL 表达式。
findValue 是一个从表达式生成语法树,然后执行的过程
282 行,OgnlUtil.getValue,进入 compile,把表达式编译成语法树?
最后进入 Ognl.getValue,执行表达式
TextUtils.stringset
判断字符串是否为 null 或者空字符串,两者都不是则返回 True。显然 left 为%{
,right 为 }
,stringset 返回 true。最后expression 被赋值为%{1+1}
,进行第二轮循环。
第二轮循环中,expression 为 %{1+1}
,其他无区别,在 stack.findValue 时返回结果为 2,expression 为 2,进入第三轮循环。
第三轮循环中,expression 为 2,不存在%{
,start 为 -1,return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
,返回了 2,随后输出到用户界面。
漏洞的关键在于循环会多次进行,直至 expression 不再是 OGNL 表达式。修复时,增加了 loopCount 和 maxLoopCount
1 | if (loopCount > maxLoopCount) { |
参考
从零带你看 struts2 中 ognl 命令执行漏洞 (seebug.org)
Struts2学习(1):Struts2框架结构详解_欢迎来到Jimmy的博客-CSDN博客_struts框架详解
CVE-2007-4556 Struts2_001 Remote Code Execution 远程代码执行漏洞分析 - H0t-A1r-B4llo0n (guildhab.top)
Struts2 漏洞 S2-001