はじめに
こんにちは。セキュリティアセスメント部の山根です。
ごった煮ブログの執筆者はデジタルペンテスト部のメンバーが多いですが、私はセキュリティアセスメント部の所属であり、普段はWebアプリケーションの脆弱性診断をやっています。脆弱性を見つけるのが好きで、プライベートでも脆弱性を探しています。
弊社には「IPA経由で脆弱性を報告すると報奨金がもらえる」という素晴らしい福利厚生が存在するため、脆弱性調査のモチベーションが高まります!
さて、今回2つの脆弱性を発見したのですが、脆弱性が公表されるに至った経緯が興味深いものだったので紹介していきます。
- CVE-2023-40587: Pyramid におけるディレクトリトラバーサルの脆弱性
- CVE-2023-41105: Python における信頼性のない検索パスの脆弱性
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)
大まかな処理の流れは以下のとおりです。
self.norm_docroot
とpath
を結合するnormpath()
によって正規化し、resource_path
に格納するresource_path
がディレクトリであり、かつrequest.path_url
が/
で終わっていれば、resource_path
とself.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自体の脆弱性を発見することができました。
今後もこのような脆弱性調査にまつわる面白い話があれば、ごった煮ブログで発信していきたいと思います。