Django-生产环境中的安全
生产环境的安全设计
生产环境安全要考虑的因素
- 防火墙:把攻击挡在外面,建立安全区
- 应用安全:密码攻击 & 访问限流 – 防恶意攻击
- 架构安全:部署架构的安全性,应用架构安全设计
- 数据安全:SSL,敏感数据加密与日志脱敏
- 密码安全与业务安全:权限控制 & 密码安全策略
防火墙
-
防火墙的作用
- 建立安全区,把攻击挡在外面
-
防火墙的类别
- 硬件防火墙
- WAF防火墙
- 操作系统防火墙
-
WAF 防火墙
- WAF:Web application firewall,基于预先定义的规则, 如预先定义的正则表 达式的黑名单,不安全URL 请求等
- 防止 SQL 注入,XSS, SSRF等web攻击
- 防止CC攻击屏蔽常见的扫描黑客工具,扫描器
- 屏蔽异常的网络请求屏蔽图片附件类目录php执行权限
- 防止 web shell 上传
-
系统防火墙:常用的 Linux 系统防火墙
-
iptables:Linux原始自带的防火墙工具iptables
-
ufw:Ubuntu的防火墙工具ufw即:uncomplicated firewall 的简称,简单防火墙。
-
简介
- Linux原始的防火墙工具 iptables 过于繁琐
- Ubuntu 提供了基于iptables之上的防火墙ufw
- ufw 支持图形界面操作
-
规则
- 开启 ufw 后,默认是允许所有连接通讯
- 且配置的策略也有先后顺序,每一条策略都有序号
- 服务器上配置,建议先deny from any(阻止所有连接),再放开需要开放的访问
-
图示
-
-
firewalld:CentOS的防火墙工具firewalld
-
应用安全
-
防恶意密码攻击
-
防恶意密码攻击策略
- 在用户连续登陆 n 次失败后, 要求输入验证码登陆
-
可选方案:使用 simple captcha 插件
-
安装 & 配置
(1)安装
1
pip install django-simple-captcha
(2)在您的settings.py中添加captcha到INSTALLED_APPS
1
2
3
4INSTALLED_APPS = [
# ...
'captcha',
](3)运行:
python manage.py migrate
(4)urls.py中添加路径映射1
2
3urlpatterns += [
path('captcha/', include('captcha.urls')),
](5)注意:需要提前安装Pillow依赖包
-
添加登陆验证 Form 和 Views 视图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49from django import forms
from captcha.fields import CaptchaField
from django.contrib.auth.forms import AuthenticationForm
class CaptchaTestForm(forms.Form):
captcha = CaptchaField(label='验证码')
max_failed_login_count = 3
def login_with_captcha(request):
if request.POST:
failed_login_count = request.session.get('failed_login_count', 0)
# 没有连续的登陆失败, 使用默认的登陆页; 连续 n 次登陆失败, 要求输入验证码
if failed_login_count >= max_failed_login_count:
form = CaptchaLoginForm(data=request.POST)
else:
form = AuthenticationForm(data=request.POST)
# Validate the form: the captcha field will automatically
# check the input
if form.is_valid():
request.session['failed_login_count'] = 0
# authenticate user with credentials
user = authenticate(username=form.cleaned_data["username"], password=form.cleaned_data["password"])
if user is not None:
# attach the authenticated user to the current session
login(request, user)
return HttpResponseRedirect(reverse_lazy('admin:index'))
else:
failed_login_count += 1
request.session['failed_login_count'] = failed_login_count
logger.warning(
" ----- failed login for user: %s, failed times:%s" % (form.data["username"], failed_login_count))
if failed_login_count >= max_failed_login_count:
form = CaptchaLoginForm(request.POST)
messages.add_message(request, messages.INFO, 'Not a valid request')
else:
## 没有连续的登陆失败, 使用默认的登陆页; 连续 n 次登陆失败, 要求输入验证码
failed_login_count = request.session.get('failed_login_count', 0)
if failed_login_count >= max_failed_login_count:
form = CaptchaLoginForm(request.POST)
else:
form = AuthenticationForm()
return render(request, 'templates/login.html', {'form': form}) -
添加登陆模板页
1
2
3
4
5
6
7
8
9
10
11
12{% extends 'base.html' %}
{% block content %}
<form method="post" style="margin-left: 10px;">
{% csrf_token %}
{{ form.as_p }}
<div class="form-actions">
<input type="submit" class="btn btn-primary" style="width:120px;" value="登录"/>
</div>
</form>
{% endblock %} -
添加登陆失败的频次控制
1
2
3
4if failed_login_count >= max_failed_login_count:
form = CaptchaLoginForm(data=request.POST)
else:
form = AuthenticationForm(data=request.POST) -
设置管理员的登陆页, 默认使用带连续失败需要验证码的页面
-
-
-
应用访问限流 – 防恶意攻击
-
Rest Framework API 限流
- 可以对匿名用户,具名用户进行限流
- 可以设置峰值流量(如每分钟60次请求)
- 也可以设置连续一段时间的流量限制(比如每天3000次)
1
2
3
4
5
6
7
8
9
10REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSEs': [
'example.throttles.BurstRateThrottle',
'example.throttles.sustainedRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'burst': '60/min',
'sustained': '3000/day'
}
}
-
应用限流:对页面的访问频次进行限流
- 可以选方案: 使用 django-ratelimit 插件
- 安装
1
pip install django-ratelimit
- 用装饰器修饰:
views.py
1
2
3
4
5
6
7
8
9from ratelimit.decorators import ratelimit
def myview(request):
# ...
def secondview(request):
# ...
- 安装
- 示例策略:一分钟最多请求5次登陆页,防止暴力攻击登陆页
1
2
3
4
5from ratelimit.decorators import ratelimit
- 可以选方案: 使用 django-ratelimit 插件
-
架构安全
-
防火墙
-
XSS
-
CSRF
-
SQL
-
应用的部署架构
- 典型中小型互联网应用部署架构
- 服务器内部组成私有网络
- 图示
-
密钥存储原则
数据安全
-
SSL 证书的使用
Let’s Encrypt
SSL证书的使用Let's Encrypt
是一家非盈利机构, 免费提供 SSL 证书。Let's Encrypt
的目标是为了构建一个安全的互联网。Let's Encrypt
的证书被各大主流的浏览器和网络服务商支持。- 提供的证书90天过期,需要自动重新申请。 有相应的工具可以使用。
Let’s Encrypt
:Certbot 的两种使用方式-
Webroot 方式
certbot 会利用既有的 web server,在其 web root 目录下创建隐藏文件,
Let's Encrypt
服务端会通过域名来访问这些隐藏文件,以确认你的确拥有对应域名的控制权。-
编辑 nginx.conf 配置文件, 确保可以访问 /.well-known/ 路径及里边存放的验证文件
1
2
3
4location /.well-known/acme-challenge/ {
default_type "text/plain";
root /data/www/example;
} -
重新加载 nginx
1
nginx -s reload
-
调用命令生成证书
1
2/usr/bin/certbot certonly --email admin@example.com --webroot -w
/data/www/example -d example.com -d www.example.com--email
为申请者邮箱--webroot
为 webroot 方式,-w 为站点目录,-d 为要加 https 证书的域名- 命令会在 web root 目录中创建 /.well-known 文件夹,其中包含了域名所有权的验证文件
- Certbot 会访问域名下面 /.well-known/acme-challenge/ 来验证域名是否绑定,并生成证书
-
在 nginx/tengine 中开启 https
-
证书生成完成后可以到 /etc/letsencrypt/live/ 目录下查看对应域名的证书文件。 编辑 nginx 配置文件监听 443 端口,启用 SSL,并配置 SSL 的公钥、私钥证书路径。
文件 描述 cert.pem 服务器证书 chain.pem 包含Web浏览器为验证服务器而需要的证书或附加中间证书 fullchain.pem cert.pem + chain.pem privkey.pem 证书的私钥
-
-
在 Nginx 使用证书
-
Nginx 配置文件中配置 SSL 证书, 以及监听端口, 重新加载 nginx
1
2
3
4
5
6
7
8
9
10
11server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
index index.html index.htm index.php;
root /home/wwwroot/example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}1
2# 重新加载nginx/tengine
nginx -s reload
-
-
-
Standalone 方式
Certbot 会自己运行一个 web server 来进行验证。如果我们自己的服务器上已经有 web server 正在运行 (比如 Nginx 或 Apache ), 用 standalone 方式的话需要先关掉它,以免冲突。
-
示例:Debian 上面安装 certbot 工具
1
2
3
4
5
6
7# 以debian为例子,先安装snapd应用包管理工具(或者直接使用apt-get安装certobt)
sudo apt update
sudo apt-get install snapd
sudo snap install core; sudo snap refresh core
# 然后使用snap安装certbot
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
-
-
Let’s Encrypt
SSL 证书续期- 证书 3个月过期一次
- 使用 snapd 安装的 certbot,会启动一个 cron job 或者 systemd 的定时任务,在证书过期前自动续期
- 运行这个命令测试自动续期
1
2
3
4
5## 测试自动续期命令
certbot renew --dry-run
## ―检查定时任务
systemctl list-timers
-
敏感数据加密
- 对敏感数据,比如用户提交的内容,财务报告,第三方合同等数据进行加密
- 使用 Python 的 cryptography 库
1
pip install cryptography
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# Fernet module is imported from the
#cryptography package
from cryptography.fernet import Fernet
# key is generated
key = Fernet.generate_key()
# value of key is assigned to a variable
f = Fernet(key)
# the plaintext is converted to ciphertext
token = f.encrypt(b"welcome to django")
# display the ciphertext
print(token)
# decrypting the ciphertext
d = f.decrypt(token)
# display the plaintext
print(d)
-
日志脱敏
在日志记录中,过滤掉敏感信息存储,避免敏感信息泄漏。
可以用 sensitive_variables 装饰器阻止错误日志内容包含这些变量的值
具体参考 sensitive_post_parameters, sensitive_post_parameters1
2
3
4
5
6
7
8from django.views.decorators.debug import sensitive_variables
asensitive_variables('user', 'pw', 'cc')
def process_info(user):
pw = user.pass_word
cc = user.credit_card_number
name = user.name
...- 用户名
- 密码
- 手机号
- 银行卡号
- 地址
密码安全与业务安全
-
权限控制
- 遵循最小原则, 长时间没用自动回收。
- 思路: 定时任务检查所有用户,找到长时间没有登陆的用户,回收相应的权限, 或删除账号。
-
密码策略
-
密码复杂度策略 AUTH_PASSWORD_VALIDATORS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityvalidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 9,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordvalidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
] -
密码过期策略
- 启用中间件
1
2
3
4
5MIDDLEWARE__CLASSES = [
...
"account.middleware.ExpiredPasswordMiddleware",
...
] - 配置过期策略
1
2
3ACCOUNT_PASSWORD_USE_HISTORY = True
# number of secs,this is 5 days
ACCOUNT_PASSWORD_EXPIRY = 60*60*24*5
- 启用中间件
-