クランプスが年末にStrutsの脆弱性を届ける
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ランタイムだけです。
クローンしたプロジェクトのフォルダに移動し、次のコマンドを実行します。
http://localhost:9999/struts-vuln-pocでサーバーにアクセスできます。ファイルをアップロードするだけのシンプルな画面が表示されます。ただし、ここで時間を費やすわけではありません。このデモが「手品のタネはありません」ということを示すため、http://localhost:9999/struts-vuln-poc/rogue.jspにアクセスしてください。次のような404ページが表示されるはずです。

この脆弱性を悪用するため、ターミナルを開いてプロジェクトフォルダに移動し、任意のHTTPクライアントを使う準備をします。ここではcurlを使いますが、ファイルアップロードに対応したHTTPクライアントなら何でも構いません。次のコマンドを実行してください。
次に、http://localhost:9999/struts-vuln-poc/rogue.jspにアクセスしてください。今度はYa been PWNED!というメッセージが表示されます。
アプリから配信されるフォルダに不正なファイルを置き、脆弱性を悪用できました。ここで、どのようにしてこうなったのかを見ていきましょう。
先ほど、このエクスプロイトが成功するには、脆弱なバージョンのStrutsライブラリと安全でないコードの両方が必要だと説明しました。
まず、安全でないコードを見てみましょう。Uploadアクションには、次の3つのプロパティがあります。
Strutsの仕組み上、これらのプロパティにはHTTPクライアント(ブラウザやcurlのようなコマンドラインツール)から送信されたリクエストの値が設定されます。
プロパティが設定された後、executeメソッドがリクエストを処理します。
executeメソッドの中心部分は次のとおりです。
問題に気づきましたか?自分でバグを見つけようとする代わりに、Snykを使ってこのコードの問題を特定できます。Snykにはコマンドラインインターフェース(CLI)、IDEインテグレーション、Webインターフェースがあるので、普段の作業環境で利用できます。
Snykの静的アプリケーションセキュリティテスト(SAST)機能を使うため、CLIからsnyk code testを実行した結果がこちらです。

この結果から、脆弱性のある具体的な行番号と種類(Path Traversal)がわかります。また、深刻度がHighであることも確認できます。
Snykのソフトウェア構成分析(SCA)機能を使うため、CLIからsnyk testを実行した結果がこちらです。

この結果から、既知の脆弱性を含む依存関係があることがわかります。具体的には、struts-coreのバージョン6.3.0.1です。また、6.3.0.2に更新することが推奨されています。
どちらの結果も非常に役立ち、すぐに対応できます。さらに、IDEインテグレーションを使うと、より有用で包括的な情報を確認できます。
Java開発に使っているIntelliJ IDEAでプロジェクトを開いています。プロジェクトをSnykでスキャンすると、次の結果が表示されます。

左ペインにはSASTとSCAの結果が表示され、右ペインには具体的な修正方法が表示されます。
さらに便利なのは、コードの問題をクリックすると、同じ問題に対処した他の3つのオープンソースプロジェクトから、コードの修正例が表示されることです。

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

これはコードの修正に大いに役立ちます。特に、あまり詳しくない脆弱性の場合に便利です。
パストラバーサル脆弱性の修正
Snykのスキャンツールで得た情報をもとに、独自コードと依存関係の両方を修正できます。
まずコードを見ていきましょう。curlまたは別のHTTPクライアントで、次のコマンドを実行します。
出力にAttempted path traversal attackというエラーメッセージが表示されます。Snyk Codeのスキャンで得たヒントを使い、コードを次のように更新しました。
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ファイルを削除してください。次のコマンドで再起動します。
次に、先ほどのcurlコマンドをもう一度実行します。
今度はFile uploaded successfully to /CVE-2023-50164-POC/uploads/rogue.jspのような成功メッセージが表示されます。
指定した相対パスが無視されたことに注目してください。先ほどのエラーメッセージが表示されなかった理由が気になるかもしれません。更新版のstruts-coreが、入力されたパスを自動的にサニタイズするためです。
Upload.javaクラスの23行目にブレークポイントを設定してcurlリクエストを実行すると、次のようになります。

uploadFileNameから、指定した相対パスが取り除かれています。
Snykで脆弱性に先手を打つ
古いバージョンのstruts-coreにおけるパストラバーサル脆弱性のデモがお役に立てば幸いです。この記事で使用しているコードはすべてGitHubで公開しています。
Snykのスキャンツールは、プロジェクトの依存関係の定義と独自コードの両方で問題を検出しました。
Snykを使ってプロジェクトを継続的に監視すれば、生産性をさらに高められます。新たな脆弱性が見つかると自動で通知され、Snykが代わりにプルリクエスト(PR)を自動作成することもできます。あとは内容を確認してマージするだけです。
CVEを一つずつ調べ、コードに問題がないか手作業で確認する代わりに、得意なこと、つまりコードを書くことに集中しましょう!Snykを無料でお試しください:https://app.snyk.io
Apache Strutsへの対応に役立つその他のリソース:
CVE-2023-50164の深刻度は?
Capture the Flagを始めよう
オンデマンドのバーチャル入門ワークショップで、Capture the Flagの課題の解き方を学びましょう。
