Pythonのstr.lower()がIDNA 2003のセキュリティ脆弱性だった理由
CPythonのIDNA 2003実装における微妙なバグが、RFC 3454仕様から逸脱し、インタープリタのUnicodeバージョンをケースフォールディングに使用していました。CVE-2026-17084は、Unicode 3.2.0の動作に一致する例外を追加することで修正されました。

Pythonのstr.lower()は頼りになる関数ですが、IDNA 2003コーデック内で使用されると、セキュリティホールを開く可能性があります。問題は、RFC 3491で定義されたIDNA 2003が、Unicode 3.2.0に基づくケースフォールディングを指定するStringPrep(RFC 3454)に依存していることです。しかし、Pythonのstr.lower()は、インタープリタが搭載するUnicodeバージョン(現在は17.0.0)を使用します。この不一致により、コーデックが仕様が意図したものとは異なるドメイン名を生成する可能性があり、混乱やスプーフィングにつながる恐れがあります。
CPythonのstringprepモジュールの脆弱なコードは次のようになっていました:
def map_table_b3(code):
r = b3_exceptions.get(ord(code))
if r is not None: return r
return code.lower()
str.lower()呼び出しは、StringPrepが必要とするUnicode 3.2.0データではなく、インタープリタのUnicodeデータを使用します。Pythonには実際にunicodedata.ucd_3_2_0モジュールがこの目的のために同梱されていますが、コードはケースフォールディングにそれを使用していませんでした。
実際の影響:チェロキー文字の「Ꭰ」(U+13A0)などの特定のUnicode文字は、Unicode 17.0.0では3.2.0とは異なるフォールディングになります。これにより、ASCII互換エンコーディング(ACE)出力が変わります:
# RFC 3454準拠の値
>>> "ᎠᎠ".encode("idna")
'xn--58da'
# Unicode 17.0.0のケースフォールディングを使用した場合の値
>>> "ᎠᎠ".encode("idna")
'xn--kz9aa'
この不一致により、2つの異なるUnicode文字列が同じIDNAラベルにマッピングされるか、同じ文字列がPythonのバージョンに応じて異なるラベルにマッピングされる可能性があります。これは、ドメイン混乱攻撃の典型的なレシピです。
CPython PR #155293でマージされた修正は、str.lower()がUnicode 3.2.0の動作から逸脱するすべてのコードポイントに明示的な例外を追加します。これで、IDNA 2003はインタープリタのUnicodeバージョンに関係なく仕様と一致します。
これはBitshiftによって報告され、Stan Ulbrychが共同開発し、Marc-Andre LemburgとPetr Viktorinがレビューしました。CVE-2026-17084として追跡されています。
ほとんどの開発者にとっての教訓は、str.encode('idna')(IDNA 2003)の代わりにidnaパッケージ(IDNA 2008)を使用することです。ただし、レガシーIDNAをサポートする必要がある場合は、この修正が重要です。
この関数のstr.lower()呼び出しは脆弱性です!
| 側面 | IDNA 2003 | IDNA 2008 |
|---|---|---|
| 仕様 | RFC 3491(StringPrep) | RFC 5890-5893 |
| Unicodeバージョン | 3.2.0 | 最新(例:15.1) |
| Pythonサポート | str.encode('idna') | idnaパッケージ |
| ケースフォールディング | StringPrep B.2/B.3 | UTS #46 |
| セキュリティ状況 | 廃止、CVE-2026-17084あり | 積極的にメンテナンス中 |
ディスカッション
0 件のコメント
最初のコメントを投稿しましょう。