Struts2 漏洞 S2-001

Struts2 系列(希望能是个系列)

Struts2 介绍

Struts2 使用 Webwork2 为代码基础,以 xwork 为底层实现的核心,ognl 作为浏览器与 Java 对象数据流转沟通的语言。

Struts2 中请求从输入到输出的过程:

struts2.xml 的核心作用:

20170411114947511

标签的解析过程:

image-20210907102807409

image-20210907103151359

解析标签是从 doStartTag 开始的,TextFieldTag 从父类 AbstractTag 的父类 继承了该方法

image-20210907103617207

image-20210907103749935

image-20210907103901285

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

image-20210907110756007

getBean 会调用到 UIBean 的构造函数,获取标签属性。this.populateParams 设置一些标签的属性值。最后经过判断返回不同的值。

doStartTag 结束后会进入 doEndTag 函数,跟进 end 函数

image-20210907144532081

end 首先调用了 evaluateParams 函数,此函数解析每个标签的属性

image-20210907144655826

并且在满足一系列条件时,会组成表达式,并调用 findValue。可以看到此时的标签 name 属性是 username,所以其实 username 也可以触发漏洞

image-20210907145128880

这里还是以输入 password 为 %{1+1} 为例,运行到上图位置,这里会先将 expr 赋值为 %{1+1},然后判断 altSyntax

image-20210907151821003

可以看到,此处的 this.stack 是一个 OgnlValueStack,包含 root 和 context ,root 是一个 ArrayList 实现的栈,context 是一个 Map

image-20210907152028872

isUseAltSyntax 会 return

1
2
3
4
altSyntax
|| context.containsKey("useAltSyntax")
&& context.get("useAltSyntax") != null
&& "true".equals(context.get("useAltSyntax").toString())

结果为 true

altSyntax 功能是 Struts 2 框架用于处理标签内容的一种新语法( 不同于普通的 HTML ) , 该功能主要作用在于支持对标签中的 OGNL 表达式进行解析并执行 . 该功能在struts2核心配置文件struts.properties中默认开启 .

于是此处 expr 变为 %{password}。随后调用 this.findValue(expr, valueClazz)

image-20210907153344257

if 条件满足,会进入 TextParseUtil.translateVariables('%', expr, this.stack)

image-20210907154213223

第一轮循环,首先解析表达式,找到匹配的大括号,确定%{...}中间内容的索引(start 和 end),最终取到 var,本例中为 password

image-20210907155658433

此处的 o 来自于Object o = stack.findValue(var, asType);,获取的是 password 真正的值,即 %{1+1}。findValue 执行了 OGNL 表达式。

findValue 是一个从表达式生成语法树,然后执行的过程

image-20210907173122025

282 行,OgnlUtil.getValue,进入 compile,把表达式编译成语法树?

image-20210907175958163

最后进入 Ognl.getValue,执行表达式

image-20210907173722289

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
2
3
4
if (loopCount > maxLoopCount) {
// translateVariables prevent infinite loop / expression recursive evaluation
break;
}

参考

从零带你看 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 漏洞详细分析 - 老路的博客 (dean2021.github.io)

XWork框架的元素详解 - 老路的博客 (dean2021.github.io)

作者

lll

发布于

2021-08-30

更新于

2022-09-19

许可协议