加固服务器
包括修改 SSH 端口,关闭/删除不必要的服务等。
理解 Django 的安全特性
包括:
- 跨站脚本保护 XSS
- 跨站请求伪造保护 CSRF
- SQL 注入保护
- 劫持保护
- 支持 TLS/HTTPS/HSTS,包括安全 cookie
- 安全的密码存储,默认使用 PBKDF2 算法和 SHA256 哈希算法
- 自动 HTML 转义
- 一个能对抗 XML Bomb 攻击的 expat 解析器
- 加固了的 JSON、YAML 和 XML 序列化/反序列化工具
更多知识,见官方文档相应页面
在生产环境下关闭 DEBUG 模式
关闭后,同时也要设置 ALLOWED_HOSTS,否则抛出的 *SuspiciousOperation** 错误而导致的 500 页会很难调试。
妥善保管 SECRET_KEY 等信息
全部采用 HTTPS
包括图片等静态文件也使用 HTTPS,不然出现的 “insecure resources” 警告信息会使用户离开我们的网站。
添加 django.middleware.security.SecurityMiddleware 的方式:
- 添加 django.middleware.security.SecurityMiddleware 到 settings.MIDDLEWARE_CLASSES
- 设置 settings.SECURE_SSL = True
需要注意的是, django.middleware.security.SecurityMiddleware 不会对 JS、CSS 和图片进行 HTTPS 重定向,需要在 Web 服务器软件上配置。
关于 SSL 证书,应该从一个可信源购买,不要使用自签名的证书。比较方便快捷的应该是通过 namecheap.com 购买 Comodo Positive SSL 和 RapidSSL,两者都是 $10 左右一年,一般在 10 来分钟之内就能完成。
使用安全 cookie
应该告诉浏览器不要在非 HTTPS 下传输 cookies,设置如下:
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
使用 HTTP 严格传输安全协议(HSTS)
HSTS 可以在 Web 服务器级上设置,也可以在 Django 中设置(通过 settings.SECURE_HSTS_SECONDS)。
值得注意的是,HSTS是一个单向头,一旦设置成了 N 秒,无法通知浏览器进行重置。因此不要将 HSTS 的 max-age 值设置成超出你的可维护范围。
Wikipedia 上有关于 HSTS 配置的示例代码段,可以拿来使用。
当开启 HSTS 后,你的网页会包含一个 HTTP 头指示那些支持 HSTS 的浏览器只能通过安全连接进行访问:
- 浏览器会将 HTTP 连接重定向到 HTTPS
- 如果无法进行安全连接(比如证书是自签名的或已过期),会抛出错误消息并禁止继续访问
一个 HSTS 应答头可能如下:
-Strict-Transport-Security: max-age=31536000; includeSubDomains
HSTS 配置的一些建议:
- 尽可能使用 HSTS 的 includeSubDomains,它能防止通过非安全子域名向你的域名写 cookie 等的相关攻击
- 初次部署时将 max-age 值设小一些,如 3600 (1 小时)。因为一旦设置后就无法重置。
- 一旦确信网站已经没有问题了,再将 max-age 值设置大一点,如 31536000 (12 个月) 或 63072000 (24 个人)。
要注意的是,一旦 HSTS 使用了 includeSubDomains,所有的子域名都要使用 HTTPS,而且无法修改回使用 HTTP。
HTTPS 配置工具
Mozilla 提供了一个 SSL 配置生产工具。虽然不是很完美,但是如安全专家所说:“一般来说,HTTPS 总比 HTTP 好。”
设置好后,可能使用 Qualys SSL Labs 的测试工具 进行测试,看看我们配置的有多好,最好拿到 A+ 分 :)
使用 ALLOWED_HOSTS 验证
生产环境中一定要设置 ALLOWED_HOSTS,从而避免抛出 SuspiciousOperation 异常。
修改数据的表单一定要开启 CSRF 保护
避免跨站脚本攻击 XSS
优先使用 Django 模板系统,而不是 make_safe
即使是很少的 HTML 字符串,也尽量通过模板系统处理。
禁止用户修改 HTML 标签上的属性
返回给 JavaScript 使用的数据先进行 JSON 编码
防御对 Python 代码注入攻击
Python 内置可以执行代码的函数
小心使用 eval(), exec() 和 execfile()。如果你在项目中允许将任意字符串或文件传入到这些函数,就会有安全漏洞。更多 Eval Really Is Dangerous by Ned Batchelder。
Python 标准库中可以执行代码的模块
“Never unpickle data received from an untrusted or unauthenticated source.”
不可以使用 pickle 模块对用户发送来的数据进行反序列化。关于 pickle 的更多安全资料:
- https://lincolnloop.com/blog/playing-pickle-security/
- https://blog.nelhage.com/2011/03/exploiting-pickle/
能执行代码的第三方库
当使用 PyYAML 时,只使用 yaml.safe_load()。
避免使用基于 cookie 的会话
这种会话有几下问题:
- 用户有可能会看到会话的内容
- 如果攻击者获知了项目的 SECRET_KEY,并且你的会话数据是基于 JSON 的,就可能被破解来伪造登录
- 如果攻击者获知了项目的 SECRET_KEY,并且你的会话数据是基于 pickle 的,破解后不仅能伪造登录,而且可以上传任意可执行代码
- 这种会话不能确保使其失效。攻击者可以一直使用旧会话数据。
所有进入 Django 表单的数据都要验证
禁止支付相关表单项上的自动填充功能
包括信息卡、CVV、PIN 等。因为用户可能会在公共电脑上输入。
实现代码如下:
from django import forms
class SpecialForm(forms.Form):
my_secret = forms.CharField(
widget=forms.TextInput(attrs={'autocomplete': 'off'}))
对于会在公共场合要填入的,考虑将输入项修改成 PasswordInput:
from django import forms
class SecretInPublicForm(forms.Form):
my_secret = forms.CharField(widget=forms.PasswordInput())
小心处理用户上传的文件
要完全安全地处理只能通过一个完全独立的域名进行处理。或者将上传的文件保存到 CDN 中。
服务器头要设置 Content-Disposition: attachment 来避免浏览器会自动解析显示这些内容。
当无法用 CDN 时
确保上传的文件保存到一个无法被执行的目录中。并且要将上传文件的后缀限制到一份白名单中。
Django 与用户上传的文件
Django 有两个项能允许用户上传文件: FileField 和 ImageField。
如果只接受特定格式的文件类型,尽可能确保上传的就是这些类型:
- 使用 python-magic 库检查上传的文件头
- 使用专门针对该类型文件的 Python 库进行验证。例如 Django 的 ImageField 源码中就是用 PIL 库来验证的
- 使用 defusedxml 而不是 Python 的内置 XML 库或 lxml
自定义的验证器这里不管用,因为它们是在项内容通过 to_python() 方法转化成 Python 对象后再进行验证的。
不要使用 ModelForms.Meta.exclude
使用 ModelForms 时,应该使用 Meta.fields。
不要使用 ModelForms.Meta.fields = “all”
小心 SQL 注入攻击
确保在原始 SQL 中进行正确转义处理:
- ORM 的 .raw()
- ORM 的 .extra()
- 直接访问数据库的指针
不要保存信用卡数据
推荐使用第三方服务如 Stripe、Braintree、Adyen、PayPal 等,然后将它们整合到项目中。
如果要评估一个开源的电子商务解决方式,先了解下它是否将支付相关信息存放在了数据库中,如果是,那就换用其它的吧。
加固 Django Admin
修改默认的 Admin URL
默认的是 yoursite.com/admin/,将它修改成一个又长又难猜的。
使用 django-admin-honeypot
通过一个虚假的 admin/ 登录页将对企图登录的用户信息进行记录。
只允许通过 HTTPS 访问 Admin
限制访问的 IP
限制 IP 可以在 Web 服务器级设置,如 Django admin logins on Nginx。也可以通过在 django 的 middleware 中实现。
小心使用 allow_tags
通过 allow_tags 和 django.utils.html.format_html,HTML 标签就可以在 admin 中显示了。
推荐的原则是:只允许将 allow_tags 使用在系统生成的数据上,如主键、日期、计算结果等。而字符串和用户输入的数据上绝不能用。
Admin Docs 与 Admin 采取一样的安全措施
因为 Admin Docs 中可以看到项目的体系结构,故要加以保护。
监测你的网站
定期检查网站的访问和错误日志。安装监测工具进行定义检查。
保持依赖包更新
推荐使用 requires.io,它能自动将 requirements 文件与 PyPI 上的版本进行核对。
防止点击劫持
见: https://docs.djangoproject.com/en/1.8/ref/clickjacking/
使用 defusedxml 来避免 XML Bomb 攻击
尝试使用双因子认证
通常是密码 + 手机短信。它要求用户有手机与手机网络。但是 基于时间的一次性密码算法 TOTP 则没有这个限制,该算法在 Google 等公司的双因子认证中广泛使用。
相关资料:
使用 SecurityMiddleware
Django 1.8 内置的 django.middleware.security.SecurityMiddleware 已经实现了 django-secure 包的大部分功能。
强制使用强密码
一个强密码包含不同大小写的字符、数字、标点符号等。
强密码的相关工具:
对网站进行安全检查
这种安全检查不是安全审计,可以通过 ponycheckup.com 进行。
在网站上建立一个漏洞提交页面
类似的可参考页面: GitHub’s “Responsible Disclosure of Security Vulnerabilities”
停止使用 django.utils.html.remove_tag
该函数可以会在 Django 2.0 中移除。可以用 bleach 来代替使用。
制定处置预案
类似的预案:
- 关闭网站或设置只读模式
- 开启一个静态 HTML 页
- 备份全部数据
- 向 [email protected] 发邮件
- 再开始查问题
关闭网站或设置为只读模式
在 Heroku 上:
$ heroku maintenance:on
Enabling maintenance mode for myapp... done
对于用自动化工具自己部署的网站,也应该创建类似的功能。
相关资料:
- Nginx 创建 HTTP 503 维护页
- django-db-tools 可以用来将项目的数据库设置为只读模式
开启一个静态 HTML 页
之前就应该创建一个维护静态页。
备份全部数据
备份代码和数据,内鬼的危害更大。
向 [email protected] 发邮件
重要的原因:
- 描述问题能使你集中注意力。
- Django 安全团队可能会给你提供建议。
- 可能是 Django 的问题。
使用 UUID 对主键进行混淆
Django 1.8 中有一个很有用的 models.UUIDField。示例:
import uuid as uuid_lib
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class IceCreamPayment(models.Model):
uuid = models.UUIDField(
db_index=True,
default=uuid_lib.uuid4,
editable=False)
def __str__(self):
return str(self.pk)
使用方法:
>>> from payments import IceCreamPayment
>>> payment = IceCreamPayment()
>>> IceCreamPayment.objects.get(id=payment.id)
<IceCreamPayment: 1>
>>> payment.uuid
UUID('0b0fb68e-5b06-44af-845a-01b6df5e0967')
>>> IceCreamPayment.objects.get(uuid=payment.uuid)
<IceCreamPayment: 1>
安全相关资料
- The Tangled Web: A Guide to Securing Modern Web Applications
- The Web Application Hacker’s Handbook
- google browsersec wiki
- mozilla Secure coding guidelines