草庐IT

Struts2-001浅析

admin_luo 2023-03-28 原文

Struts2是一个基于MVC设计模式设计模式的Web应用框架应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。

Struts2处理请求流程如下:

S2-001

Struts2 对 OGNL 表达式的解析使用了开源组件 opensymphony.xwork 2.0.3,所以实际上这是一个 xwork 组件的漏洞,影响了 Struts2。

参考链接:https://cwiki.apache.org/confluence/display/WW/S2-001

该漏洞是因为Struts2的标签处理功能:altSyntax , 在该功能开启时 , 支持对标签中的Ognl表达式进行解析并执行。

而altSyntax在解析的时候 ,是依赖于开源组件xwork 。

漏洞gadget:

com.apache.struts2.views.jsp.ComponentTagSupport.doEndTag()
	org.apache.struts2.components.UIBean.end()
		org.apache.struts2.components.UIBean.evaluateParams()
			org.apache.struts2.components.Component.findValue()
				com.opensymphony.xwork2.util.TextParseUtil.translateVariables()
					com.opensymphony.xwork2.util.OgnlValueStack.findValue()
						com.opensymphony.xwork2.util.OgnlUtil.findValue()
							com.opensymphony.xwork2.util.Ognl.getValue()

而在doEndTag之前 ,是doStartTag(),用于获取一些组件信息和属性赋值,总之是些初始化的工作。

搭建环境

编辑有漏洞的页面:

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

<html>
<head>
  <title>S2-001 demo</title>

</head>
<body>

<s:form action="login">
  <s:textfield name="username" label="username" />
  <s:textfield name="password" label="password" />
  <s:submit></s:submit>
</s:form>

</body>
</html>

web.xml配置如下:

<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>

test.OGNLTest.demo01Action

package test.OGNLTest;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class demo01Action extends ActionSupport {

    private String username = null;
    private String password = null;

    public demo01Action() {
    }

    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 this.username.equalsIgnoreCase("admin") && this.password.equals("admin") ? "success" : "error";
        } else {
            return "error";
        }
    }
}

/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>
    <constant name="struts.devMode" value="false"/>
    <!--<constant name="struts.custom.i18n.resources" value="global"/>-->
    <!-- <constant name="struts.multipart.parser" value="jakarta-stream" /> -->
    <!--constant name="struts.multipart.maxSize" value="1" /-->

    <package name="default" extends="struts-default">
        <action name="login" class="test.OGNLTest.demo01Action">
            <result name="success">/welcome.jsp</result>
            <result name="error">/index.jsp</result>
        </action>
    </package>

</struts>

welcome.jsp:

<%@ taglib prefix="s" uri="/struts-tags" %>
<%--
  Created by IntelliJ IDEA.
  User: Sec0re_luo
  Date: 2022/10/27
  Time: 9:37
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>TEST</title>
</head>
<body>
hello , <s:property value="username"></s:property>
</body>
</html>

因为在web.xml中配置了struts的filter , 在处理请求时 ,会进入org.apache.struts2.dispatcher.FilterDispatcher

OGNL的使用此处不再赘述。

漏洞利用POC:

%{@java.lang.System @getProperty("user.dir")}

漏洞触发关键点:

FilterDispatcher.doFilter()会调用Dispatcher.serviceAction() ,该方法是核心方法:

首先会调用createContextMap 来获取HttpServletRequest 和 HttpServletResponse 和 ServletContext , 并将其放入extraContext中。

Map<String, Object> extraContext = this.createContextMap(request, response, mapping, context);
        try {
            UtilTimerStack.push(timerKey);
            String namespace = mapping.getNamespace();
            String name = mapping.getName();
            String method = mapping.getMethod();
            Configuration config = this.configurationManager.getConfiguration();
            ActionProxy proxy = ((ActionProxyFactory)config.getContainer().getInstance(ActionProxyFactory.class)).createActionProxy(namespace, name, extraContext, true, false);  //此处创建Action代理类
            proxy.setMethod(method);
            request.setAttribute("struts.valueStack", proxy.getInvocation().getStack()); //此处创建了DefaultActionInvocation 的实例
            if (mapping.getResult() != null) {
                Result result = mapping.getResult();
                result.execute(proxy.getInvocation());
            } else {
                proxy.execute();
            }

            if (stack != null) {
                request.setAttribute("struts.valueStack", stack);
            }
        }

之后通过createActionProxy 来创建Action代理类 ,在这过程中也会创建 DefaultActionInvocation 的实例,并通过其 createContextMap() 方法创建一个 OgnlValueStack 实例,并将 extraContext 全部放入 OgnlValueStack 的 context 中。

之后 , 调用了proxy.execute() 来将DefalutActionInvocation.InvocationContext放入了ActionContext中 :

而在上述DefaultActionInvocation 初始化的时候 , 会调用DefaultActionInvocation .createAction(contextMap)

createAction又会调用

在ObjectFactory.buildAction中 , 调用了

实例化了当前访问的类:demo01Action,并将其放入 OgnlValueStack 的 root 中。

this.dispatcher.serviceAction() 方法的最后,执行创建的 ActionProxy 实例的 execute() 方法,调用创建的 DefaultActionInvocation 的 invoke() 方法,调用程序配置的各个 interceptors 的 doIntercept() 方法执行相关逻辑,其中的一个拦截器是 ParametersInterceptor,这个拦截器会在本次请求的上下文中取出访问参数,将参数键值对通过 OgnlValueStack 的 setValue 通过调用 OgnlUtil.setValue() 方法,最终调用 OgnlRuntime.setMethodValue 方法将参数通过 set 方法写入到 action 中,并存入 context 中。

此时 OgnlValueStack 实例中 root 中的 Action 对象的参数值已经被写入了。

在循环执行 interceptors 结束后,DefaultActionInvocation 的 invoke() 方法执行了 invokeActionOnly() 方法,这个方法通过反射调用执行了 action 实现类里的 execute 方法,开始处理用户的逻辑信息。

用户逻辑走完后,会调用 DefaultActionInvocation 的 executeResult() 方法,调用 Result 实现类里的 execute() 方法开始处理这次请求的结果。

如果返回结果是一个 jsp 文件,则会调用 JspServlet 来处理请求,然后交由 Struts 来处理解析相关的标签。

在进行标签解析的时候 ,有两个方法:ComponentTagSupport#doStartTag 和 ComponentTagSupport#doEndTag

doStartTag是一些初始化的方法

而doEndTag , 是标签解析结束后要做的事情

    public int doEndTag() throws JspException {
        this.component.end(this.pageContext.getOut(), this.getBody());
        this.component = null;
        return 6;
    }

会调用 this.component.end()方法 , 最终触发点在TextParseUtil#translateVariables()方法 :

   public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, TextParseUtil.ParsedValueEvaluator evaluator) {
        Object result = expression;

        while(true) {
            int start = expression.indexOf(open + "{");
            int length = expression.length();
            int x = start + 2;
            int count = 1;

            while(start != -1 && x < length && count != 0) {
                char c = expression.charAt(x++);
                if (c == '{') {
                    ++count;
                } else if (c == '}') {
                    --count;
                }
            }

            int end = x - 1;
            if (start == -1 || end == -1 || count != 0) {
                return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
            }

            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 {
                result = left + right;
                expression = left + right;
            }
        }
    }

此处 , 使用了while(true) 来进行循环调用 ,先获取了标签的值 ,如username , 之后便使用stack.findValue(var, asType); 来查找username的值 (即为前端输入过来的值) , 之后便得到payload:%{@java.lang.System @getProperty("user.dir")} , 之后循环解析了标签中的变量名

之后即进入了

findValue中 ,最终会调用到OGNL.getValue() 来进行解析 , 从而触发漏洞

循环解析的过程 :

username --> %{username} --> %{@java.lang.System @getProperty("user.dir")} --> D:\Environment\apache-tomcat-9.0.52\bin

在第一次OGNL解析时 , 解析的是%{var} ,解析的实际上是标签中的变量名(根本原因:循环解析)

有关Struts2-001浅析的更多相关文章

  1. javascript - 在 javascript 中使用 jquery 加载 Struts 2 Action - 2

    我正在尝试通过使用jquery来定位div和加载内容的struts操作,从javascript重新加载目标div。有人知道怎么做吗?问题是我如何使用(javascript)jquery来执行此操作。BR,托拜厄斯 最佳答案 最简单的做法是使用jQuery.load()功能。$('#targetDivId').load('${your.struts.url}',function(){//stufftodowhenthedivhasbeenreloaded});现在明白你应该确保你的操作的响应是一个不是真正完整的HTML页面的页面,因为

  2. java - Struts2:如何在 ActionSupport 中获取 ServletRequest 实例 - 2

    如何在我的操作中获取ServletRequest实例?我实现了ServletRequestAware但我无法在操作中获取请求对象。struts.xmlapplication/json我正在使用Ajax/JavaScript进行调用:req.onreadystatechange=onReadyState;req.open(POST,Cart.action,false);req.setRequestHeader("Content-Type","application/json;charset=utf-8");req.send(JSONstr);JSON对象:vardata={cartIte

  3. javascript - 001 形式的数字 - 2

    001格式的数字有没有专门的名字。例如,数字20将是​​020,1将是001。当你不知道某个东西的名字时,很难谷歌搜索!因为我已经在浪费你们的时间了,有人知道一个函数吗用于将数字更改为这种格式。 最佳答案 我认为这通常被称为“填充”数字。 关于javascript-001形式的数字,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/6823592/

  4. javascript - 如何将对象从前端传递到 Struts 2 - 2

    我试图通过JavaScript将一个字段的值发送到Struts2后端,但它返回NullpointerException。....提交表单后,请求将发送到以下JavaScript方法以发送到后端。functionpayslipPayment(){varformValues=$('#myform').serialize();....xmlhttp.open("get","../payslip/pay?"+formValues,false);xmlhttp.send();}请求将按如下方式创建和发送http://localhost/payslip/pay/employee.payslip.i

  5. Alibaba Nacos JWT令牌使用默认密钥浅析 - 2

    简介Nacos/nɑ:kəʊs/是DynamicNamingandConfigurationService的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos提供简单的鉴权实现,为防止业务错用的弱鉴权体系,不是防止恶意攻击的强鉴权体系。一、漏洞原理Nacos服务管理平台因默认密钥导致的认证绕过漏洞。在默认配置为未修改的情况下,攻击者可以构造用户token进入后台,导致系统被攻击与控制。许多Nacos用户只开启了鉴权,但没有修改默认密钥,导致Nacos系统仍存在被入侵的风险。V1.4.2V2.2.0大致讲一下相关的内容:1、Nacos鉴权原理Nacos支持基于

  6. javascript - struts2:使用 javascript 和 jquery 根据第一个选择值更新第二个选择 - 2

    我正在开发一个struts2项目,其中有3个html选择控件,每个控件都依赖于之前的选择。假设第一个选择是国家,第二个是州,第三个是城市。州选择中的选项列表将被过滤以仅显示该国家/地区的州等。由于其他一些限制,我使用基本的html选择控件而不是struts2。这是我当前如何填充选择的示例:">我认为我需要做的是onchange事件执行ajax调用以根据所选“国家/地区”检索“州”列表。问题是:1.如何使用jquery执行此ajax调用?2.我需要传递什么作为ajax调用的url?只是Action名称?3.如何解析返回结果?我可以从Java代码返回具有“代码”和“标签”以及其他属性的“状

  7. javascript:缩短大数,强制保留小数位,并选择将 1000 表示为 .001 百万 - 2

    我正在努力完成三件事-我想缩短大数字并添加K/M/B后缀我希望能够强制小数位数我希望能够强制将数千表示为百万的小数只需缩短,四舍五入到小数点后两位1200000---->>>120万1248000---->>>125万248000---->>>248K缩短,强制保留2位小数1200000---->>>120万1248000---->>>125万248000---->>>248.00K缩短,强制小数点后3位,强制几千到几百万1200000---->>>1.200M1248000---->>>1.248M248000---->>>0.248M我有一个javascript函数,我发现它可以做

  8. RK3399驱动开发 | 06 - GT911触摸屏驱动调试及驱动浅析(Linux 5.4内核) - 2

    更新内容更新时间完成初稿2022-09-21文章目录一、GT9111.触摸芯片2.原理图二、驱动调试1.测试gt911是否正常通信2.添加驱动3.添加设备树描述4.测试三、驱动源码浅析1.i2cplatform总线设备挂载2.probe挂载流程3.触摸中断处理机制一、GT9111.触摸芯片GT911是汇顶科技(GOODiX)的一款转为7“~8”设计的5点电容触摸方案,拥有26个驱动通道和14个感应通道,可以满足更高的touch精度要求。

  9. pointers - Golang 编辑从 main() 到函数的 struts 数组 - 2

    希望您能提供帮助,下面是我的代码的简明版本。基本上我将一个结构数组传递给floatInSlice(),其中一个新结构被添加到该数组或一个现有结构AudienceCategory.Sum得到++除b.Sum=b.Sum+1外,其他一切正常现在我知道,如果我想将一个对象作为指针而不是值传递,我需要使用*/&但我似乎无法让它工作,任何帮助都非常有用!typeAudienceCategorystruct{CatintSumint}varcounter=[]AudienceCategory{}funcmain(){fori:=1;i编辑*******终于搞定了,谢谢大家的帮助funcfloatI

  10. 漏洞复现 - - -Struts2(s2-045)远程命令执行漏洞 - 2

    一,Struts2是什么Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts2是Struts的下一代产品,是在struts1和WebWork的技术基础上进行了合并的全新的Struts2框架。其全新的Struts2的体系结构与Struts1的体系结构差别巨大。Struts2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts2可以理解为WebWork的更新产品。虽

随机推荐