Skip to main content

クランプスが年末にStrutsの脆弱性を届ける

feature snyk honeycomb

2024年1月2日

0 分で読めます

編集者注:2024年1月16日

このブログのコード例を更新し、修正のセキュリティを強化しました。

2023年12月20日、NISTはstruts-coreの新たなパストラバーサル脆弱性を反映するため、CVE情報を更新しました。これはCVE-2023-50164で、SnykのSnyk Vulnerability Databaseにも掲載されています。CVSSの深刻度は9.8(Critical)です。サイバーセキュリティに長く携わっている方なら、パッチが適用されていないStrutsの脆弱性が原因で発生した2017年のEquifaxの情報漏えいを覚えているでしょう。

この記事では、問題の概要と深刻度を解説し、概念実証(PoC)エクスプロイトを紹介するとともに、修正方法を説明します。また、Snykをお使いなら(こちらから無料登録できます)、この脆弱性やほかの脆弱性が見つかった際に通知を受け取り、問題のあるコードの修正方法についてアドバイスを得られます。

まずは修正方法から見ていきましょう。現在使用しているベースバージョンに応じて、Strutsを2.5.33または6.3.0.2以降にアップグレードするだけです。

StrutsのCVE-2023-50164(パストラバーサル脆弱性)はどれほど深刻なのか?

Strutsの脆弱性が任意のコード実行につながった、最も深刻な事例を見てみましょう。2017年に発生したこの脆弱性は、悪名高いEquifaxの情報漏えいにつながりました。このエクスプロイトでは、不正な形式のContent-Typeヘッダーを送信し、そこから発生する例外を悪用しました。リモートからエクスプロイトが可能で、当時はコードによる防御もなかったため、最悪の種類の脆弱性でした。

今回の脆弱性も深刻です。必ずStrutsのバージョンを更新してください。ただし、脆弱なバージョンのStrutsに加えて、安全でないコードも必要となるため、2017年の脆弱性ほど深刻ではありません。

この脆弱性により、ファイルアップロード時にパストラバーサルが可能になります。つまり、相対パスを指定することで、ファイルをアップロードし、指定されたアップロードフォルダの外に「抜け出す」ことができます。相対パスがアプリケーションから配信されるフォルダ内を指している場合、リモートコード実行につながる可能性があります。

StrutsのCVE-2023-50164を悪用するパストラバーサルのPoC

この記事で使用しているコードはすべてGitHubで公開しています。

このプロジェクトは、ビルドにMavenを使用するJavaプロジェクトです。プロジェクトには2つのプロファイルがあります。デフォルトのvulnと、もう一つのno-vulnです。MavenやMavenプロファイルに馴染みがなくても心配はいりません。実行はとても簡単で、デフォルトでは脆弱なバージョンが使われます。必要なのはバージョン17以降のJavaランタイムだけです。

クローンしたプロジェクトのフォルダに移動し、次のコマンドを実行します。

./mvnw clean jetty:run

http://localhost:9999/struts-vuln-pocでサーバーにアクセスできます。ファイルをアップロードするだけのシンプルな画面が表示されます。ただし、ここで時間を費やすわけではありません。このデモが「手品のタネはありません」ということを示すため、http://localhost:9999/struts-vuln-poc/rogue.jspにアクセスしてください。次のような404ページが表示されるはずです。

JSPファイル /rogue.jsp が見つからないことを示すHTTP 404エラーページ(Jetty 9.4.46搭載)

この脆弱性を悪用するため、ターミナルを開いてプロジェクトフォルダに移動し、任意のHTTPクライアントを使う準備をします。ここではcurlを使いますが、ファイルアップロードに対応したHTTPクライアントなら何でも構いません。次のコマンドを実行してください。

curl \
http://localhost:9999/struts-vuln-poc/upload.action \
-F "Upload=@./payload/rogue.jsp" \
-F "uploadFileName=../src/main/webapp/rogue.jsp"

次に、http://localhost:9999/struts-vuln-poc/rogue.jspにアクセスしてください。今度はYa been PWNED!というメッセージが表示されます。

アプリから配信されるフォルダに不正なファイルを置き、脆弱性を悪用できました。ここで、どのようにしてこうなったのかを見ていきましょう。

先ほど、このエクスプロイトが成功するには、脆弱なバージョンのStrutsライブラリと安全でないコードの両方が必要だと説明しました。

まず、安全でないコードを見てみましょう。Uploadアクションには、次の3つのプロパティがあります。

private File upload;
private String uploadFileName;
private String uploadContentType;

Strutsの仕組み上、これらのプロパティにはHTTPクライアント(ブラウザやcurlのようなコマンドラインツール)から送信されたリクエストの値が設定されます。

プロパティが設定された後、executeメソッドがリクエストを処理します。

executeメソッドの中心部分は次のとおりです。

String uploadDirectory = System.getProperty("user.dir") + "/uploads/";
File destFile = new File(uploadDirectory, uploadFileName);
FileUtils.copyFile(upload, destFile);

問題に気づきましたか?自分でバグを見つけようとする代わりに、Snykを使ってこのコードの問題を特定できます。Snykにはコマンドラインインターフェース(CLI)、IDEインテグレーション、Webインターフェースがあるので、普段の作業環境で利用できます。

Snykの静的アプリケーションセキュリティテスト(SAST)機能を使うため、CLIからsnyk code testを実行した結果がこちらです。

Snykの静的解析により、Upload.javaの23行目で重大度の高いパストラバーサル脆弱性が検出されたことを示すターミナル。

この結果から、脆弱性のある具体的な行番号と種類(Path Traversal)がわかります。また、深刻度がHighであることも確認できます。

Snykのソフトウェア構成分析(SCA)機能を使うため、CLIからsnyk testを実行した結果がこちらです。

Snyk testのターミナル出力。Apache Strutsに重大なリモートコード実行の脆弱性があることを示し、6.3.0.1から6.3.0.2へのアップグレードを推奨

この結果から、既知の脆弱性を含む依存関係があることがわかります。具体的には、struts-coreのバージョン6.3.0.1です。また、6.3.0.2に更新することが推奨されています。

どちらの結果も非常に役立ち、すぐに対応できます。さらに、IDEインテグレーションを使うと、より有用で包括的な情報を確認できます。

Java開発に使っているIntelliJ IDEAでプロジェクトを開いています。プロジェクトをSnykでスキャンすると、次の結果が表示されます。

SnykのIntelliJ IDEA用プラグインが、Strutsコンポーネントの脆弱性によるリモートコード実行を検出。

左ペインにはSASTとSCAの結果が表示され、右ペインには具体的な修正方法が表示されます。

さらに便利なのは、コードの問題をクリックすると、同じ問題に対処した他の3つのオープンソースプロジェクトから、コードの修正例が表示されることです。

SnykのIntelliJ IDEA用拡張機能が、Strutsのパストラバーサルの脆弱性を検出。

google/j2objcの例を見てみましょう。下にスクロールすると、この脆弱性に対処するために変更されたコード行がdiff形式で表示されています。

Apache Strutsの重大な脆弱性を検出したSnykのセキュリティスキャンと、正規パスの検証チェックを追加するコード差分

これはコードの修正に大いに役立ちます。特に、あまり詳しくない脆弱性の場合に便利です。

パストラバーサル脆弱性の修正

Snykのスキャンツールで得た情報をもとに、独自コードと依存関係の両方を修正できます。

まずコードを見ていきましょう。curlまたは別のHTTPクライアントで、次のコマンドを実行します。

curl \
http://localhost:9999/struts-vuln-poc/upload-no-vuln.action \
-F "Upload=@./payload/rogue.jsp" \
-F "uploadFileName=../src/main/webapp/rogue.jsp"

出力にAttempted path traversal attackというエラーメッセージが表示されます。Snyk Codeのスキャンで得たヒントを使い、コードを次のように更新しました。

File uploadDirectory = new File(USER_DIR + "/uploads/");
File destFile = new File(uploadDirectory, uploadFileName);

if (
  !destFile.getCanonicalPath().startsWith(uploadDirectory.getCanonicalPath() + File.separator)
  !upload.getCanonicalPath().startsWith(USER_DIR)
) {
  throw new SecurityException("Attempted path traversal attack");
}

FileUtils.copyFile(upload, destFile);

if文は、destFileが定義済みのuploadsディレクトリの正規パスを使用しているか確認します。また、アップロードされたファイルがアプリで定義されたフォルダ内にあり、不正な場所からのものではないことも確認します。ファイルをアップロードすると、Strutsは自動的にクラスパス内の一時フォルダに保存します。これらのチェックに失敗すると、SecurityExceptionがスローされます。

これは、パストラバーサルを防ぐうえで、元のコードから大きく改善されています。

ここで触れておきたい重要な点があります。File.separatorの呼び出しにより、チェック対象のパスの末尾にスラッシュ(/)が付いていることに注目してください。これは非常に重要です。末尾のスラッシュがないと、悪意のあるユーザーが部分パスを使ってアップロードフォルダから抜け出せる可能性があります。getCanonicalPath()は常に末尾のスラッシュを含まない値を返すためです。この点は、Jonathan LeitschuhによるDefConの講演で詳しく説明されています。さらに堅牢な修正方法としてgetCanonicalFile().toPath()を使う方法があり、この記事のリポジトリに含まれています。

ここで最も重要なのは、パストラバーサルの問題に対処するために、Strutsを最新バージョンに更新するのが最も確実で堅牢な方法だということです。相対パスは自動的に除去されるため、コード内の手動チェックに頼る必要がなくなります。

Snykのスキャンで推奨されたほかの変更にも対応すれば、セキュリティ態勢をさらに強化できます。つまり、struts-coreを6.3.0.1から6.3.0.2にアップグレードします。

先ほどクローンしたリポジトリのプロジェクトには、更新版のstruts-coreを使うプロファイルがあります。実行中のアプリを停止し、先ほどアップロードしたsrc/main/webapp/rogue.jspファイルを削除してください。次のコマンドで再起動します。

./mvnw clean jetty:run -P no-vuln

次に、先ほどのcurlコマンドをもう一度実行します。

curl \
http://localhost:9999/struts-vuln-poc/upload.action \
-F "Upload=@./payload/rogue.jsp" \
-F "uploadFileName=../src/main/webapp/rogue.jsp"

今度はFile uploaded successfully to /CVE-2023-50164-POC/uploads/rogue.jspのような成功メッセージが表示されます。

指定した相対パスが無視されたことに注目してください。先ほどのエラーメッセージが表示されなかった理由が気になるかもしれません。更新版のstruts-coreが、入力されたパスを自動的にサニタイズするためです。

Upload.javaクラスの23行目にブレークポイントを設定してcurlリクエストを実行すると、次のようになります。

uploadFileNameに「rogue.jsp」が設定され、アップロードディレクトリと保存先ファイルの変数が表示されたデバッガー画面。

uploadFileNameから、指定した相対パスが取り除かれています。

Snykで脆弱性に先手を打つ

古いバージョンのstruts-coreにおけるパストラバーサル脆弱性のデモがお役に立てば幸いです。この記事で使用しているコードはすべてGitHubで公開しています。

Snykのスキャンツールは、プロジェクトの依存関係の定義と独自コードの両方で問題を検出しました。

Snykを使ってプロジェクトを継続的に監視すれば、生産性をさらに高められます。新たな脆弱性が見つかると自動で通知され、Snykが代わりにプルリクエスト(PR)を自動作成することもできます。あとは内容を確認してマージするだけです。

CVEを一つずつ調べ、コードに問題がないか手作業で確認する代わりに、得意なこと、つまりコードを書くことに集中しましょう!Snykを無料でお試しください:https://app.snyk.io

Apache Strutsへの対応に役立つその他のリソース:

Capture the Flagを始めよう

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