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="https://www.cnblogs.com/sec0reluo/archive/2022/10/28/false"/>
<!--<constant name="struts.custom.i18n.resources" value="https://www.cnblogs.com/sec0reluo/archive/2022/10/28/global"/>-->
<!-- <constant name="struts.multipart.parser" value="https://www.cnblogs.com/sec0reluo/archive/2022/10/28/jakarta-stream" /> -->
<!--constant name="struts.multipart.maxSize" value="https://www.cnblogs.com/sec0reluo/archive/2022/10/28/1" /-->
<package name="default" extends="struts-default">
<action name="login" >
<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="https://www.cnblogs.com/sec0reluo/archive/2022/10/28/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} ,決議的實際上是標簽中的變數名(根本原因:回圈決議)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/522944.html
標籤:其他
