SST連載・解説記事

  1. HOMEHOME
  2. SSTコラムSSTコラム
  3. SSTtechlog
  4. 08 S2-054, S2-055 および jackson-databindの脆弱性 CVE-2017-7525, CVE-2017-15095 について

(2017年12月6日公開)

SSTtechlog

はじめに

2017年12月1日にStruts2のセキュリティアップデートが公開されました。

攻撃の可能性について先に結論だけ書くと、これらの脆弱性が実際の攻撃として悪用される可能性は低いと考えられます。

以下、本記事でS2-054, S2-055 および S2-055 に影響を与えた jackson-databind の脆弱性について解説します。


情報が公開される前からJackson(Javaで人気のあるJSONライブラリ)の脆弱性が関連している、という話が メーリングリストに流れて おり、筆者は大きな衝撃を受けました。 というのも、その数日前に同僚から「JavaでJSONをパースするのにオススメのライブラリはあるか?」と聞かれ、「JacksonがOSSでも広く使われて実績があるし、ググれば沢山記事やQAが見つかるのでオススメですよ」とドヤ顔で答えたばかりだったのです。

筆者自身が、社内のツール開発などでJacksonを利用しており便利さを感じていました。 しかしながら、その時点で筆者はCVE-2017-7525を把握しておらず、Jacksonが広くOSSで使われて利用者が多いことに安心しきっていたのです。

そこに別の同僚が Adam Caudill 氏のblog を見つけてCVE-2017-7525の存在を教えてくれたのですが、自ら利用しているJacksonをドヤ顔で奨めた数日後にJacksonの脆弱性でStruts2がアップデート公開、しかもJacksonの脆弱性自体は数ヶ月前から対応されていたとなれば、セキュリティ業界に身をおいているエンジニアとして、自らが使っているライブラリの脆弱性情報の収集を怠っていたと指摘されても言い逃れようがありません(実際そうだったとしか言えないのですが)。

そのためここ数日の筆者のメンタルコンディションは最悪の状態で、なんとかするために、とにかくCVE-2017-7525が何なのか、Jacksonは一体どういう状況なのかをまず知ることから始めようと、12/2, 3の土日を費やして本記事の調査・執筆に勤しんだ次第です。

本記事では重要な点をサマリとしてお届けします。

  1. 最初に S2-054, S2-055 について紹介し、
  2. その後 S2-055 に影響を与えた jackson-databind の脆弱性 CVE-2017-7525, CVE-2017-15095 について紹介します。
  3. struts2-rest-showcase を使った検証結果を簡単ですが紹介して、
  4. アプリケーション側の対応について整理します。
  5. 最後に攻撃の難易度について筆者の見解をまとめます。

詳細な調査結果・検証内容についてはGitHubで公開していますので、そちらを参照してください。

S2-054, S2-055 について

S2-054, S2-055 ともに Struts2 REST plugin の脆弱性を修正しています。このpluginはHTTPリクエストのメソッドやURLを、RESTfulなお作法でJavaのクラス・メソッドにマッピングしてくれます。また、リクエスト/レスポンスでのXML/JSON変換にも対応しています。

JSONを処理する際の handler として、JSON-libを使ったhandlerとJackson(jackson-databind)を使ったhandlerの2種類が用意されています。

S2-054 について:

  • JSON-lib ですが2011年以降アップデートされていない古いライブラリとなってしまっており、また DoS の脆弱性もあったそうです。
  • デフォルトはJSON-libを使ったhandlerだったのですが、上記の状況を鑑みて、Jacksonを使ったhandlerをデフォルトとするよう修正しています。
  • JSON-lib のDoSの脆弱性とは具体的に何か、どんなJSONでDoSとなるのかまでは、今回の調査では見つけることができませんでした。

S2-055 について:

  • 使用しているjackson-databindが、後述の CVE-2017-7525, CVE-2017-15095 に対して脆弱な古いバージョンを使っていました。
  • これを、対応済みの最新版を使うように修正しています。

jackson-databind の脆弱性 CVE-2017-7525, CVE-2017-15095 について

jackson-databind はJavaで人気の高いJSONライブラリで、多数のOSSで採用されています。 今回のS2-055で言及されている jackson-databind の脆弱性はCVE-2017-7525, CVE-2017-15095 になりますが、RedHatのサイトに情報があります。

バージョン 2.8.10, 2.9.1 以降で両方の問題に対応済みです。S2-055では現時点の最新版, バージョン 2.9.2を使うよう修正されていました。

この脆弱性は、JSONをJavaオブジェクトに変換する際に、JSON中に「どのJavaクラスにマッピングするのか」分かるよう、Javaのクラス名を埋め込む機能に起因しています。 この機能を悪用して、クラス名とそのフィールドをうまく改ざんすることで、任意のコード実行につなげることが可能です。

詳細は以下のレポートや記事を参照してください。

jackson-databind側の対応としては、CVE-2017-7525の対応としてクラス名についてblack listチェックを追加しています。 コード実行が可能になるようなクラスは限られているため、それらをblack listに登録し、JSON中のクラス名でそれらが来たら弾くようにしています。 その black list に漏れがあったのが CVE-2017-15095 で、2.8.10, 2.9.1 でblack listを改善し、対応完了しました。

なぜJSONにクラス名を埋め込める機能が作られたのか、どのような状況で影響するのか、次の節で簡単に説明します。

ObjectMapper.enableDefaultTyping() と @JsonTypeInfo

まず、以下のようなJavaクラスがあるとします。

class MyData {
    MyConcreteBean myprop;
}

class MyConcreteBean {
    String name;
}

jackson-databindを使ってJSONを上記のクラスに変換するには、以下のようなJSONとJavaコードを書くことになります。

JSON: { "myprop" : { "name" : "hello" } }

Javaコード:

String json = "(上記のJSON)";
ObjectMapper mapper = new ObjectMapper();
MyData r = mapper.readValue(json, MyData.class);

では、MyDataのmypropが以下のようにabstractやinterfaceクラスになったらどうでしょうか?

class MyData {
    MyAbstractBean myprop;
}

abstract class MyAbstractBean { /* ... */ }
class MyConcreteBean1 extends MyAbstractBean { /* ... */ }
class MyConcreteBean2 extends MyAbstractBean { /* ... */ }
class MyConcreteBean3 extends MyAbstractBean { /* ... */ }

JSONで { "myprop" : { "name" : "hello" } } を読んでも、どの具象クラス(concrete class)に戻せばよいのか、JSONからは容易に判別できません。

そこでJacksonでは、以下のようにJSON中にクラス名を埋め込めるようにしました。

{
  "myprop" : [
    "MyConcreteBean1", // <- これを具象クラス名として扱う
    {
      // ここから下を、各フィールドの中身として扱う
      "prop1": ...,
      "prop2": ...,
      ...
    }
  ]
}

この機能はデフォルトはOFFですが、 ObjectMapper.enableDefaultTyping() メソッドを呼ぶとONになります。 また、全体ではなく一部のクラス/フィールドについてだけこの機能をONにしたい、JSON側の書き方を調整したいような場合は @JsonTypeInfo アノテーションを利用します。

詳細は以下のドキュメントをご確認ください。

本来は開発者にとって便利な機能のはずが、クラス名を外部から指定できる機能を悪用することで、Javaのdeserialize処理における任意のコード実行につながる脆弱性となってしまいました。

struts2-rest-showcase を使った検証

REST plugin を使ったサンプルアプリとして、struts2-rest-showcase がStrutsのソースに含まれています。 今回はこれをカスタマイズして、検証に使いました。 カスタマイズしたソースは GitHub に上げていますので、ご興味のある方は見てみてください。

カスタマイズしていて気づいた点としては、REST plugin の Jackson handlerでは ObjectMapper.enableDefaultTyping() を呼んでいません。 そのため @JsonTypeInfo を使ったBeanを追加し、それをJSONからdeserializeすることにしました。

以下が、Struts 2.5.14 でjacksonの脆弱性の影響を受ける Bean クラスです。

public class Zoo {
    public String id;
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY)
    // 任意のクラス名を受け付けるために、Object型にする
    public Object animal;

// ...
}

Struts 2.5.14 の REST plugin を使った状態でアプリを起動し、以下のcurlコマンドで任意のクラス名を指定したJSONを送信します。(JSONの内容については、jackson-databind のテストコードから拝借しています)

curl -v -x localhost:8080 \
  -H "Accept: application/json" \
  "http://localhost:18088/struts2-rest-showcase/zoo" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"id":"3","animal":["com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",{"transletBytecodes":["AAIAZQ=="],"transletName":"a.b","outputProperties":{}}]}'

その結果、以下のスタックトレースを含む例外が発生しました。

Caused by: java.lang.NullPointerException
        at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$1.run(TemplatesImpl.java:401) ~[?:1.8.0_92]

このサンプルコードではNullPointerExceptionがthrowされてしまっていますが、メソッド名的に、いかにも何か副作用を含む処理が発生していることが伺えます。

ここで Struts のバージョンを 2.5.14.1 にアップデートしてアプリをビルド・起動し、同じcurlコマンドを実行します。 すると、以下のエラーメッセージを含む例外が発生しました。(見やすく改行していますが、実際は1行です)

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
Invalid type definition for type 
`com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl`: 
Illegal type (com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl) 
to deserialize: prevented for security reasons

これは jackson-databind 側の脆弱性の対応により、black list チェックでJSON中のクラス名が弾かれたことを意味します。

以上より、Struts 2.5.14 では jackson-databind の影響を受け、 2.5.14.1 でその問題が修正されたことを確認できました。

アプリケーション側での CVE-2017-7525, CVE-2017-15095 対応

基本的にはjackson-databind のバージョンを最新に上げることで対応可能です。

  • Maven/Gradleなど依存関係管理ツールを使っている場合、依存関係が解決された結果、最終的にどのバージョンが使われることになったかを確認してください。
  • Strutsをお使いの方は最新の Struts 2.5.14.1 にアップデートしてください。
  • Struts2以外のフレームワークを使っている場合、jackson-databindの影響でアップデートがかかっていないか確認してみてください。

もしバージョンを上げられない環境であれば、まず以下の3条件全てに該当するか確認してください。全てに該当していると、CVE-2017-7525, CVE-2017-15095 の影響を受けると考えられます。

  1. jackson-databind の 2.8.9 以下, 2.9.0 以下 を使っている。
  2. 信頼されないソース(Web APIなど)から受け取ったJSONをjackson-databindでdeserializeしている。
    • アプリケーションコードで使っていなくても、フレームワーク側で Accept リクエストヘッダーやURLの拡張子に応じて自動処理する機能が有効になっている場合があります。
  3. 以下のいずれかに該当する。
    • ObjectMapper.enableDefaultTyping() を呼んでいる。
    • @JsonTypeInfo アノテーションを使っている。

ObjectMapper.enableDefaultTyping() は実際にはいくつかのバリエーションがあります。また、@JsonTypeInfo アノテーションも設定によってはサブクラスを限定するなどよりセキュアに使えるようです。 そのため JacksonPolymorphicDeserialization を確認し、「プログラマが想定したクラス以外は受け付けない」状態になるよう改修する必要があります。

攻撃の難易度について

  1. JSON中のキー名が、deserialize先のJavaクラスに存在しない場合、Jacksonでは単に無視されます。
  2. そのため、実際に攻撃するとなると、アプリケーション毎のJSONに合わせてカスタマイズする必要が生じます。
  3. よって、複数のアプリケーションで使いまわせるような攻撃コードを作るのは非常に難しいと思われます。
  4. さらに、任意のコード実行につながるクラスと互換性のある型(例えばObject型など)にしないとインスタンス生成が行われず、攻撃も成功しません。
    class MyData {
     // これだと攻撃可能なクラスと互換性が無いのでインスタンス生成が行われない、結果として攻撃できない。
     // MyAbstractBean myprop;
    
     // Object型など、互換性のある型にすることでインスタンス生成 = 攻撃可能となる。
     Object myprop;
    }
    

    一般的な書き方であればまず、わざわざ Object 型にすることは無いと思います。 任意のコード実行が可能になるクラスも、大概はアプリケーションで作り込むBeanクラスとは互換性が無い場合が大半と思われます。

以上より、一般的なWebアプリケーションに対して大規模な攻撃が行われ、実際に脆弱性が悪用される可能性はかなり低いと思われます。


文責 : 坂本 昌彦 (研究開発部所属, 社内で使用するWebアプリケーション診断ツールの開発などに従事)

本記事に関するご意見・お問い合わせは坂本までお願いします。

今までのコラム

Webセキュリティをざっくり理解するための3つのMAP

Page Top