プログラミング
Pythonにおけるstr.lower()がセキュリティ脆弱性となる場合
When str.lower() is a security vulnerability in Python – Seth Larson (sethmlarson.dev)
要約
Pythonの文字列メソッド`str.lower()`は、IDNA(Internationalizing Domain Names in Applications)の処理において、Unicodeのバージョン間の差異によりセキュリティ脆弱性を引き起こす可能性がある。IDNA 2003仕様ではUnicode 3.2.0のケースフォールディング規則が定義されているが、Pythonの`str.lower()`は実行環境のUnicodeバージョンに依存するため、この仕様との不一致が生じうる。この問題は、ドメイン名の正規化処理で予期せぬ挙動を引き起こし、CVE-2026-17084として報告された。
全文翻訳
ブログ : について : RSS : ブログロール
Pythonにおけるstr.lower()がセキュリティ脆弱性となる場合
セス・ラーソン @ 2026-08-18
一部のインターネット標準はASCII文字のみをサポートしていますが、世界はラテンアルファベット以上のものを使用しています。したがって、ドメイン名で使用するためのUnicodeからASCIIへのマッピングが必要です。NamePrepは、RFC 3491でStringPrepのプロファイルとして定義されており、Internationalizing Domain Names in Applications(IDNA)、別名「IDNA 2003」の重要なコンポーネントです。StringPrepアルゴリズムはRFC 3454で定義されています。
IDNA 2003は、RFC 5890、5891、5892、および5893で定義されているIDNA 2008によって廃止されました。Pythonはidnaコーデック(str.encode('idna'))を通じてIDNA 2003をサポートしており、IDNA 2008はPythonパッケージインデックスのidnaパッケージによってサポートされています。
PythonのStringPrepの実装は、標準ライブラリのstringprepモジュールに実装されています。一般的に、idnaパッケージ(IDNA 2008)を使用すべきであり、.encode("idna")(IDNA 2003)を使用すべきではありませんが、古い動作が必要な場合もあります。
StringPrepは、セクション3.2で「ケースフォールディング」ステップ(ケースフォールディングは、おおよそ「コードポイントの小文字化/大文字化の方法」)を定義しており、マッピングテーブルB.2およびB.3を通じてすべての文字をマッピングすることにより、文字列の大文字/小文字を区別しない比較を可能にします。B.2は実質的にstr.lower()であり、Unicodeルールに従ってすべての文字を小文字化し、B.3には例外が含まれています。
これを実装したPythonコード(B.3テーブルが正しくキャプチャされていると仮定)は以下のコードです。
def map_table_b3(code):
r = b3_exceptions.get(ord(code))
if r is not None:
return r
return code.lower()
そして、それは問題ないように見えるかもしれません…そしてタイトルはおそらくすでにそれを物語っています。この関数内のstr.lower()の呼び出しは脆弱性です!
なぜなら、strは、特定のPythonインタープリタが出荷されているUnicodeデータを使用するからです。PythonインタープリタがどのUnicodeバージョンを使用しているかは、unicodedata.unidata_versionにアクセスすることで確認できます。
>>> import unicodedata
>>> unicodedata.unidata_version
'17.0.0'
Pythonのすべてのバージョンで利用可能なUnicode 3.2.0データ(unicodedata.ucd_3_2_0)のデータベースもあります。これは、StringPrepおよびIDNAアルゴリズム専用です。
$ grep -I "ucd_3_2_0" -R Lib/
Lib/stringprep.py:from unicodedata import ucd_3_2_0 as unicodedata
Lib/encodings/idna.py:from unicodedata import ucd_3_2_0 as unicodedata
これは重要です! StringPrepは、一貫して動作するためにこの特定のUnicodeバージョンに依存しています。RFC 3454のB.2およびB.3テーブルは、基本的にUnicode 3.2.0のケースフォールディングルールをテーブルにエンコードしたものです。したがって、新しいUnicodeケースフォールディングルールではなく、Unicode 3.2.0のケースフォールディングルールを使用する必要があります。
これが、str.lower()を呼び出すことが、実装と仕様の違いを表し、したがって脆弱性となる理由です。
# RFC 3454準拠の値 ('Ꭰ' は U+13A0)
>>> "ᎠᎠ".encode("idna")
'xn--58da'
# Unicode 17.0.0 ケースフォールディングを使用した場合の値
>>> "ᎠᎠ".encode("idna")
'xn--kz9aa'
修正は、str.lower()が特定の関数に対してのみUnicode 3.2.0を使用しているかのように動作する新しい例外を作成することでした。
したがって、各Unicodeコードポイントを調べ、Pythonが出荷するUnicodeバージョンとUnicode 3.2.0を比較したときのstr.lower()の動作の違いを記録します。そして、それがすべてです。これでIDNA 2003は仕様と一貫性があります。
脆弱性を報告してくれたBitshift、修正を共同開発してくれたStan Ulbrych、そして修正をレビューしてくれたMarc-Andre LemburgとPetr Viktorinに感謝します。詳細についてはCVE-2026-17084を参照してください。
Python Software FoundationでのSecurity Developer-in-Residenceとしての私の仕事は、Alpha-Omegaによってスポンサーされています。PythonエコシステムにおけるセキュリティをサポートしてくれたAlpha-Omegaに感謝します。
さて、最後までたどり着きましたね! Mastodon、メール、またはBlueskyであなたの考えを共有してください。このブログの193件の記事のアーカイブを閲覧してください。インターネットで見つけたクールなもののリストをチェックしてください。RSSまたはメールニュースレターでこのブログをフォローしてください。外に出ましょう(最良の選択肢)。