Skip to main content

SnakeYaml 2.0:安全でないデシリアライゼーションの脆弱性を解決する

blog feature supply chain sbom

2023年6月21日

0 分で読めます

昨年12月、CVE-2022-1471についてお知らせしました。この安全でないデシリアライゼーションの問題は、条件がそろうと容易に任意コード実行につながる可能性があります。 

詳しく解説したブログ記事「SnakeYamlの安全でないデシリアライゼーションの脆弱性(CVE-2022-1471)」では、このライブラリの問題と、その悪用方法について説明しました。問題の要点は、SnakeYamlがデフォルトで入力されたyamlを汎用オブジェクト型として解析することでした。そのため、クラスパス上にある別のクラスをデシリアライズできてしまいます。ClassCastExceptionがスローされたとしても、オブジェクトがすでにロードされていれば、被害は発生しています。

影響の大きさは、ライブラリの使い方に大きく左右されます。たとえば、多くの開発者はアプリの設定にyamlを使用するだけです。この脆弱性が悪用されるのは、エンドユーザーなど、信頼できないソースからyamlを受け入れる場合に限られます。それでも、このようなライブラリがどのように使われるかを予測することはできないため、デフォルト設定は安全であるべきです。

Snyk Open SourceでSnakeYamlをアップグレードする

Snyk Open Sourceを使って、古いSnakeYamlライブラリの代替を探してみましょう。Snyk CLIでローカルからsnyk testを実行すると、代替バージョンがあることがわかります。

org.yaml:snakeyaml 1.33における任意コード実行の警告を示すターミナル出力。バージョン2.0で修正済みと記載

Webインターフェースでも、問題を解決するSnykYaml 2.0のバージョンが利用可能であることが示されます。唯一の問題は、Spring Boot 3にはまだ1.xバージョンが含まれていることです。そのため、MavenまたはGradleのマニフェストファイルで、バージョンを手動で置き換える必要があります。

org.yaml:snakeyamlの脆弱性の詳細。任意コード実行、CVE-2022-1471、中程度の深刻度、スコア437を表示。

ライブラリを新しいメジャーバージョンに手動で変更すると、問題が起きる可能性があることに注意してください。変更を安易に行わず、アプリケーションの内部動作に及ぼす影響を理解しておきましょう。 

SnakeYaml 2.0でデシリアライゼーションの脆弱性を緩和する

任意コード実行につながる可能性のあるデフォルトの動作を緩和するため、SnakeYaml 2.0は2023年初頭にリリースされました。このバージョンでは、新しいyaml()で使用されるコンストラクタがSafeConstructorを継承するようになりました。そのため、解析できる型は限定されています。SnakeYamlのSafeConstructorでは、プリミティブ型や文字列、マップなど、標準的なJavaクラスを構築できます。

特定の型は、デフォルトでは解析できなくなりました。そのため、以下のyamlファイルを読み込むと例外が発生します。

!!Gadget ["env"]
Exception in thread "main" Global tag is not allowed: tag:yaml.org,2002:Gadget
 in 'reader', line 1, column 1:
    !!Gadget ["env"]
    ^

これで、デフォルトの実装は脆弱ではなくなりました。ただし、これは互換性を破る変更のため、これまでのコードは動作しなくなる可能性があります。

SnakeYaml 2.xを使う際にYamlの解析ロジックを修正する方法

まず、SnakeYaml 1.xを使わないようにする必要があります。最新のSpring Boot 3.1でさえ、現時点ではSnakeYaml 2.xを同梱していません。そのため、マニフェストファイルで自分でアップグレードする必要があります。Mavenの場合、pomファイルの<dependencyManagement>セクションを利用できます。ほかにも方法はあります。Gradleでは、依存関係の制約を使って推移的依存関係を更新することもできます。

SnakeYaml 2.xでは、以前のバージョンからAPIに互換性のない変更があります。そのため、再び動作させるには、新しい安全なデフォルト設定に合わせてyamlの解析処理を書き換える必要があります。

2つのクラスからなる、ごくシンプルなドメインを考えてみましょう。 

  • Person

  • Comment

Person.java:

public class Person {
    private String name;
    private int age;

    private Comment comment;

    public Person() {
    }

    public Person(String name, int age, Comment class2) {
        this.name = name;
        this.age = age;
        this.comment = class2;
    }
    //getters and setters
}

Comment.java:

public class Comment {

    private String text;
    private String dateTime;

    public Comment() {}

    public Comment(String text) {
        this.text = text;
        this.dateTime = LocalDateTime.now().toString();
    }
    //getters and setters
}

上記のようなエンティティからyamlファイルを作成する場合、SnakeYaml 1.xでは次のようなコードを書いていたことでしょう。

var john = new Person("John", 31, new Comment("This is a comment"));
dumpYaml(john, "file.yaml");

public static void dumpYaml(Person pojo, String filename) throws IOException {
    Yaml yaml = new Yaml();

    try (FileWriter writer = new FileWriter(filename)) {
        yaml.dump(pojo, writer);
    }
}

その結果、次のyamlファイルが生成されます。

!!mypackage.Person
age: 31
comment: {dateTime: '2023-06-15T16:53:23.175989', text: This is a comment}
name: John

SnakeYaml 2.xでは、!!mypackage.Personは受け付けられなくなりました。オブジェクトをyamlファイルに変換する際に、オブジェクトへの参照を取り除けるようになりました。しかし、移行前にエクスポートしたyamlファイルには、まだ問題が残っています。

幸い、この問題も解決できます。特定のオブジェクトに解析する場合は、パーサーが使用するコンストラクタを設定できます。さらに、LoaderOptionsに特定のTagInspectorを追加すると、パッケージタグを許可できます。これにより、オブジェクトに適合するyamlファイルだけを許可し、以前の1.xバージョンで作成されたyamlとの後方互換性を確保できます。

public static Person parseYaml(String filename) throws IOException {
        var loaderoptions = new LoaderOptions();
        TagInspector taginspector = 
                tag -> tag.getClassName().equals(Person.class.getName());
        loaderoptions.setTagInspector(taginspector);

        Yaml yaml = new Yaml(new Constructor(Person.class, loaderoptions));

        try (InputStream in = new FileInputStream(filename)) {
            // Parse the YAML file into a mypackage.MyYamlClass object
            Person obj = yaml.load(in);
            return obj;
        }
    }

さらに、yamlファイルから実際のオブジェクトへの参照、つまりタグを完全に取り除くのが望ましいでしょう。これは以前のSnakeYamlでも可能でした。yamlオブジェクトにrepresenterを追加し、トップレベルオブジェクトのタグをmapに対応付けます。以下は、SnakeYaml 2.xと互換性のある例です。

    public static void dumpYaml(Person pojo, String filename) throws IOException {
        Representer customRepresenter = new Representer(new DumperOptions());
        customRepresenter.addClassTag(Person.class, Tag.MAP);

        Yaml yaml = new Yaml(new Constructor(Person.class, new LoaderOptions()), 
                customRepresenter);

        try (FileWriter writer = new FileWriter(filename)) {
            yaml.dump(pojo, writer);
        }
    }

yamlファイルの先頭に!!mypackage.Person(または同様のタグ)が付かなくなります。すべてのyamlファイルからタグが取り除かれていれば、パーサーからTagInspectorを削除できます。

Snykの最新情報をチェック

オープンソースのセキュリティを確保するには、ライブラリを常に最新バージョンに保つことが重要です。SnakeYaml 1.xを使っていると、外部ソースのyamlファイルを直接または間接的に受け入れた場合に、不要なセキュリティ上の問題につながる可能性があります。

Snyk Open Sourceを使えば、こうした問題を見つけて修正したり、必要に応じて代替バージョンを確認したりできます。次の例では、SnakeYamlをバージョン2.0以上に更新することで、任意コード実行の脆弱性を解消できることを示しています。

org.yaml:snakeyamlのSnyk脆弱性詳細。任意コード実行、CVE-2022-1471、中程度の深刻度、影響を受けるバージョンを表示。

GitリポジトリをSnykに接続すると、重大なセキュリティ問題がない場合でも、依存関係を最新の状態に保つためのプルリクエストを作成できます。問題を未然に防ぐことが最善です。ライブラリを最新の状態に保つことで、脆弱性のリスクを最小限に抑え、セキュリティ問題への対応に伴う余分な作業も減らせます。

依存関係を最新に保つため、バージョン1.7.0から1.8.1へのアップグレードを推奨するSnyk botのプルリクエストコメント。

CTFを始めよう

オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。

続きを読む

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

feature insights context
Blog

予防は、本質的に解決済みの問題なのでしょうか?

エージェントが生成するコードの予防策はアーキテクチャ上解決されていますが、開発を遅らせることなくセキュリティを守る制御を選ぶことが、依然として課題です。

Live Stream

修正エージェントをわかりやすく解説:見つけるより直すことが重要な理由

SnykのRemediation Agentが、セキュリティインテリジェンス、破壊可能性分析、検証を活用して、脆弱性をマージ可能なプルリクエストに変える仕組みをご覧ください。