Python製Webフレームワークの脆弱性を報告したはずが、Pythonの脆弱性を見つけていた話 - ラック・セキュリティごった煮ブログ

ラック・セキュリティごった煮ブログ

セキュリティエンジニアがエンジニアの方に向けて、 セキュリティやIT技術に関する情報を発信していくアカウントです。

【お知らせ】2021年5月10日~リニューアルオープン!今後はこちらで新しい記事を公開します。

株式会社ラックのセキュリティエンジニアが、 エンジニアの方向けにセキュリティやIT技術に関する情報を発信するブログです。(編集:株式会社ラック・デジタルペンテスト部)
当ウェブサイトをご利用の際には、こちらの「サイトのご利用条件」をご確認ください。

デジタルペンテスト部提供サービス:ペネトレーションテスト

Python製Webフレームワークの脆弱性を報告したはずが、Pythonの脆弱性を見つけていた話

はじめに

こんにちは。セキュリティアセスメント部の山根です。

ごった煮ブログの執筆者はデジタルペンテスト部のメンバーが多いですが、私はセキュリティアセスメント部の所属であり、普段はWebアプリケーションの脆弱性診断をやっています。脆弱性を見つけるのが好きで、プライベートでも脆弱性を探しています。

弊社には「IPA経由で脆弱性を報告すると報奨金がもらえる」という素晴らしい福利厚生が存在するため、脆弱性調査のモチベーションが高まります!

さて、今回2つの脆弱性を発見したのですが、脆弱性が公表されるに至った経緯が興味深いものだったので紹介していきます。

CVE-2023-40587の発見

調査対象

最近はWebフレームワークの脆弱性を探すのに凝っており、Python製のWebフレームワークであるPyramid (2023/11/1時点でスター数3.9k) の脆弱性を探していました。

Pyramidは "start small, finish big, stay finished" という理念のもとで作られているフレームワークであり、必要最低限の機能で始められ、後から拡張することができます。

普段の脆弱性診断はブラックボックス脆弱性を探すのですが、OSS脆弱性を探す際はまずソースコードに目を通すことにしています。ソースコードを読むことで、脆弱性の手がかりを得ることができるからです。

静的ファイル配信処理の深掘り

しばらくコードを眺めていると、静的ファイル配信処理の一部が目に留まりました。

resource_path = normcase(normpath(join(self.norm_docroot, path)))
if isdir(resource_path):
    if not request.path_url.endswith('/'):
        raise self.add_slash_redirect(request)
    resource_path = join(resource_path, self.index)

大まかな処理の流れは以下のとおりです。

  1. self.norm_docrootpathを結合する
  2. normpath()によって正規化し、resource_pathに格納する
  3. resource_pathディレクトリであり、かつrequest.path_url/で終わっていれば、resource_pathself.indexを結合し、そのパスを参照する

self.norm_docrootには予め設定した値が入り、self.index"index.html"で固定のため、攻撃者がコントロールできるのはpathおよびrequest.path_urlのみです。

normpath()の不思議な挙動

上記のコードは一見安全なように思えますが、どうにか悪用できないかとファジングなどを試したところ、normpath()\0 (ヌル文字) を含むパスを渡した場合、\0以降が削除されることがわかりました。

>>> from os.path import normpath
>>> normpath('/hoge/\0/fuga')
'/hoge/'

normpath()ってこんな挙動だったっけ?」と思いつつも、これを利用することでディレクトリトラバーサルが可能でした。あくまで公開ディレクトリの1階層上にあるindex.htmlが閲覧できるというものであり、悪用可能性は低いですが、脆弱性としてIPAへ報告することにしました。

CVE-2023-40587の公表

その後しばらくして、報告した脆弱性CVE-2023-40587が公表されました。内容を一部引用して紹介します。


影響を受けるシステム

以下のバージョンの Pyramid で作成したアプリケーションを Python 3.11.0 から 3.11.4 を使う環境にデプロイしている場合、本脆弱性の影響を受けます。

Pyramid バージョン 2.0.0 および 2.0.1

詳細情報

Pylons Project が提供する Pyramid は、Python 用の Web フレームワークです。Pyramid には、ディレクトリトラバーサル (CWE-22) の脆弱性が存在します。

想定される影響

細工されたリクエストによって、静的ファイル配信ディレクトリの一階層上のディレクトリにある index.html にアクセスされる可能性があります。


さらに詳細を知りたい方へ

GitHubに掲載されているアドバイザリがあるので、興味のある方は見てみてください。

アドバイザリではPoCに関する情報が記載されていないため、具体的なPoCは伏せておきます。(ここまで読んだみなさんにとっては自明かもしれませんが...)

CVE-2023-41105の公表

ここまでの流れはいつも通りだったのですが、公表された脆弱性の内容を見て驚きました。なんと、参考情報としてPythonの脆弱性情報が記載されているのです!

An issue was discovered in Python 3.11 through 3.11.4. If a path containing '\0' bytes is passed to os.path.normpath(), the path will be truncated unexpectedly at the first '\0' byte. There are plausible cases in which an application would have rejected a filename for security reasons in Python 3.10.x or earlier, but that filename is no longer rejected in Python 3.11.x.

Python 3.11.4までのバージョンにおいて、os.path.normpath()\0を含むパスを渡すと、\0以降が削除されるという脆弱性が発見されたとのことです。不思議な挙動だなあとは思っていたのですが、まさかCVEが採番されることになるとは思いませんでした。

3.11.5 のRelease Noteには記載がなかったのですが、Python Security Announceメーリングリストのアーカイブにて、私の名前が脆弱性の発見者としてクレジットされていることを確認できました。こういうところに名前が載るのは嬉しいですね!

Finder: Masashi Yamane of LAC Co., Ltd

Python脆弱性といえば、先日見つかったurllib.parse()が空白文字から始まるURLを適切にパースできない問題CVE-2023-24329がブロックリストの回避に悪用されるため話題になっていました。一方で、本脆弱性は「アローリストの回避に悪用可能である」と上記アーカイブで言及されています。

If allowlisting is applied before a call to os.path.normpath() is used later in the program, the allowlisting can be circumvented if the path containing null bytes is constructed to pass the allowlist but then change to the targeted resource after truncation.

今までにもいくつか脆弱性を見つけてきましたが、言語の脆弱性を見つけたのは初めてであり、今までにない達成感を得ることができました。

おわりに

今回、Python製のWebフレームワークにおける脆弱性を調査している過程で、意外にもPython自体の脆弱性を発見することができました。

今後もこのような脆弱性調査にまつわる面白い話があれば、ごった煮ブログで発信していきたいと思います。