118 lines
2.6 KiB
Python
118 lines
2.6 KiB
Python
"""
|
||
Django 框架安全知识
|
||
"""
|
||
|
||
from ..base import KnowledgeDocument, KnowledgeCategory
|
||
|
||
|
||
DJANGO_SECURITY = KnowledgeDocument(
|
||
id="framework_django",
|
||
title="Django Security",
|
||
category=KnowledgeCategory.FRAMEWORK,
|
||
tags=["django", "python", "web", "orm"],
|
||
content="""
|
||
Django 内置了许多安全保护,但不当使用仍可能引入漏洞。
|
||
|
||
## 内置安全特性
|
||
1. CSRF保护
|
||
2. XSS防护(模板自动转义)
|
||
3. SQL注入防护(ORM)
|
||
4. 点击劫持防护
|
||
5. 安全的密码哈希
|
||
|
||
## 常见漏洞模式
|
||
|
||
### SQL注入
|
||
```python
|
||
# 危险 - raw()和extra()
|
||
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'")
|
||
User.objects.extra(where=[f"name = '{name}'"])
|
||
|
||
# 危险 - RawSQL
|
||
from django.db.models.expressions import RawSQL
|
||
User.objects.annotate(val=RawSQL(f"SELECT {user_input}"))
|
||
|
||
# 安全 - 使用ORM
|
||
User.objects.filter(name=name)
|
||
User.objects.raw("SELECT * FROM users WHERE name = %s", [name])
|
||
```
|
||
|
||
### XSS
|
||
```python
|
||
# 危险 - 禁用自动转义
|
||
{{ user_input|safe }}
|
||
{% autoescape off %}{{ user_input }}{% endautoescape %}
|
||
mark_safe(user_input)
|
||
|
||
# 安全 - 默认转义
|
||
{{ user_input }}
|
||
```
|
||
|
||
### CSRF绕过
|
||
```python
|
||
# 危险 - 禁用CSRF
|
||
@csrf_exempt
|
||
def my_view(request):
|
||
pass
|
||
|
||
# 危险 - 全局禁用
|
||
MIDDLEWARE = [
|
||
# 'django.middleware.csrf.CsrfViewMiddleware', # 被注释
|
||
]
|
||
```
|
||
|
||
### 不安全的反序列化
|
||
```python
|
||
# 危险 - 签名数据可被篡改
|
||
from django.core import signing
|
||
data = signing.loads(user_input) # 如果SECRET_KEY泄露
|
||
|
||
# 危险 - pickle
|
||
import pickle
|
||
data = pickle.loads(request.body)
|
||
```
|
||
|
||
### 敏感信息泄露
|
||
```python
|
||
# 危险 - DEBUG模式在生产环境
|
||
DEBUG = True # settings.py
|
||
|
||
# 危险 - 详细错误信息
|
||
ALLOWED_HOSTS = [] # 空列表在DEBUG=False时会报错
|
||
```
|
||
|
||
### 文件上传
|
||
```python
|
||
# 危险 - 不验证文件类型
|
||
def upload(request):
|
||
file = request.FILES['file']
|
||
with open(f'/uploads/{file.name}', 'wb') as f:
|
||
f.write(file.read())
|
||
|
||
# 安全 - 验证和重命名
|
||
import uuid
|
||
def upload(request):
|
||
file = request.FILES['file']
|
||
ext = os.path.splitext(file.name)[1].lower()
|
||
if ext not in ['.jpg', '.png', '.pdf']:
|
||
raise ValidationError("Invalid file type")
|
||
safe_name = f"{uuid.uuid4()}{ext}"
|
||
# 使用Django的文件存储
|
||
default_storage.save(safe_name, file)
|
||
```
|
||
|
||
## 安全配置检查
|
||
```python
|
||
# settings.py 安全配置
|
||
DEBUG = False
|
||
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
|
||
ALLOWED_HOSTS = ['example.com']
|
||
SECURE_SSL_REDIRECT = True
|
||
SESSION_COOKIE_SECURE = True
|
||
CSRF_COOKIE_SECURE = True
|
||
SECURE_HSTS_SECONDS = 31536000
|
||
X_FRAME_OPTIONS = 'DENY'
|
||
```
|
||
""",
|
||
)
|