ORM mặc định dùng parameterized query — giá trị truyền vào không bao giờ string-format vào SQL, mà gửi riêng qua driver:
# An toàn — Django sinh: WHERE email = %s và pass email làm param
User.objects.filter(email=user_input)Lỗ hổng SQL injection vẫn lọt qua 3 đường:
# 1. .extra() với raw SQL string-format — KHÔNG dùng
User.objects.extra(where=[f"email = '{user_input}'"]) # SQL injection!
# 2. .raw() / cursor.execute string-format
User.objects.raw(f"SELECT * FROM users WHERE email = '{user_input}'") # SQL injection!
# 3. Truyền cột/bảng động qua format (params chỉ bind giá trị, không bind identifier)
MyModel.objects.order_by(user_input) # nguy hiểm nếu user_input không whitelistCách đúng:
# raw an toàn nếu dùng params
User.objects.raw('SELECT * FROM users WHERE email = %s', [user_input])
# Cột động: whitelist trước khi dùng
ALLOWED_SORT = {'created_at', 'title', 'views'}
sort = user_input if user_input in ALLOWED_SORT else 'created_at'
Post.objects.order_by(sort)extra() đã deprecated, tránh hoàn toàn. Func(), Expression, Subquery mới là cách Django chuẩn để build dynamic SQL an toàn.
Quy ước review code nên cấm hẳn pattern f"... {var} ..." xuất hiện trong context SQL.
The ORM uses parameterized queries by default — values are never string-formatted into SQL, they are sent separately through the driver:
# Safe — Django emits: WHERE email = %s and passes email as a param
User.objects.filter(email=user_input)Leaks still happen when:
# 1. .extra() with string-formatted raw SQL — DO NOT use
User.objects.extra(where=[f"email = '{user_input}'"]) # SQL injection!
# 2. .raw() / cursor.execute with string-format
User.objects.raw(f"SELECT * FROM users WHERE email = '{user_input}'") # SQL injection!
# 3. Dynamic column/table names (params bind values, not identifiers)
MyModel.objects.order_by(user_input) # dangerous unless whitelistedDo it properly:
# raw is safe with params
User.objects.raw('SELECT * FROM users WHERE email = %s', [user_input])
# Dynamic column: whitelist before use
ALLOWED_SORT = {'created_at', 'title', 'views'}
sort = user_input if user_input in ALLOWED_SORT else 'created_at'
Post.objects.order_by(sort)Pitfall: extra() is deprecated, avoid it entirely. Func(), Expression, Subquery are the Django-native ways to build safe dynamic SQL.
Code review should ban any f"... {var} ..." pattern inside SQL context.