开发规范
腾讯 Python 安全指南
1. 代码实现
1. 避免使用不安全的对称加密算法(避免 DES/3DES,建议 AES)
2. 确保重要行为都记录在日志上,且可靠保存 6 个月以上
3. 禁止将未经验证的用户输入直接记录日志(防止注入漏洞:恶意用户插入伪造的日志数据,从而让系统管理员以为是系统行为)
4. 避免在日志中保存敏感信息(明文密码、密文密码 等等)
2. 系统口令
1. 禁止使用 空口令、弱口令、已泄漏口令
2. 口令强度需满足
+ 密码长度大于 14 位
+ 包含:大小写英文字母、数字、特殊字符
+ 不得使用默认的初始密码
+ 不能与最近 6 次使用过的密码重复
+ 不得与其它外部系统使用相同的密码
3. 口令存储安全
+ 禁止明文存储口令
+ 禁止使用弱密码学算法加密存储口令
+ 使用 不可逆算法 和 随机 salt 对口令进行加密存储
+ 禁止传递明文口令
+ 禁止在不安全的信道中传输口令
3. 配置 / 环境
1. 建议使用 Python 3.6+ 版本,因为 Python3 在 2020 年就停止维护了,相关漏洞不能得到及时修复和维护
2. 禁止使用不安全的第三方包组件
3. 密钥存储安全(使用对称加密算法时,需要保护好加密密钥)
4. 禁止硬编码敏感配置(源码中硬编码 AK/SK、IP、DB 账密 等配置信息)
5. 应使用配置系统或 KMS 密钥管理系统
4. 接口开发类
4.1 输入验证
1. 必须按照类型进行数据校验,校验内容包括但不限于:数据长度、数据范围、数据类型、数据格式
2. 推荐使用组件 Cerberus、jsonschema、Django-Validators
4.2 SQL 操作
1. 使用参数化查询,强制区分数据和命令,避免产生 SQL 注入漏洞
`cur.execute("SELECT id, password FROM auth_user WHERE id=%s", (userid, ))`
2. 推荐使用 ORM 框架来操作数据库,如使用 SQLAlchemy
3. 如果使用动态拼接 SQL 方式,必须对参数进行安全过滤,过滤不安全字符
4.3 执行命令
1. 避免直接调用函数执行系统命令,如 `os.system()`、`os.popen()`、`subprocess.call()` 等
2. 如评估无法避免,执行命令时应避免拼接外部命令,且进行执行命令的白名单限制
3. 必须过滤传入命令执行函数的字符
import os
import sys
import shlexdomain = sys.argv[1]
# 替换可以用来注入命令的字符为空
badchars = "\n&;|'\"$()`-"
for char in badchars: domain = domain.replace(char, " ")result = os.system("nslookup " + shlex.quote(domain))
4.4 XML 的读写
1. 必须禁用外部实体的方法,防止 XXE 攻击
`from lxml import etree`
`xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))`
4.5 文件操作
1. 必须对文件类型限制,比如通过白名单对上传、下载的文件类型、大小 进行严格校验。仅允许业务所需的文件类型上传。避免上传木马、WebShell 等文件
2. 必须禁止外部文件存储于可执行目录,建议使用 `tempfile` 库来处理临时文件和临时目录
3. 必须避免路径穿越,保存在本地文件系统时,必须对路径进行合法校验,避免目录穿越漏洞
4. 文件目录避免外部参数拼接。保存文件目录建议后台写死并对文件名进行校验
5. 文件名 hash 化处理(建议文件保存时,将文件名替换为随机字符串)
# 文件类型校验
import os ALLOWED_EXTENSIONS = ['txt','jpg','png']
def allowed_file(filename):
if ('.' in filename and '..' not in filename and os.path.splitext(filename)[1].lower() in ALLOWED_EXTENSIONS): return filename return None
# 文件存储路径校验
import osupload_dir = '/tmp/upload/' # 预期的上传目录
file_name = '../../etc/hosts' # 用户传入的文件名
absolute_path = os.path.join(upload_dir, file_name) # /tmp/upload/../../etc/hosts
normalized_path = os.path.normpath(absolute_path) # /etc/hosts
if not normalized_path.startswith(upload_dir): # 检查最终路径是否在预期的上传目录中
raise IOError()
# 替换文件名
import uuid
def random_filename(filename):
ext = os.path.splitext(filename)[1] new_filename = uuid.uuid4().hex + ext return new_filename
4.6 网络请求
1. 必须限定访问网络资源地址范围,当程序需要从用户指定的 URL 地址获取网页文本内容、加载指定地址的图片、下载 等操作时,需要对 URL 进行安全校验。
+ 只允许 HTTP / HTTPS
+ 解析目标 URL,获取 host,解析 host 获取 IP,将 IP 转成 long 型
+ 检查 IP 是否为内网 IP
+ 10.0.0.0/8
+ 172.16.0.0/12
+ 192.168.0.0/16
+ 127.0.0.0/8
+ 请求 URL,检查是否有跳转,有就重新执行一遍校验步骤
4.7 响应输出
1. 必须设置正确的 HTTP 响应包类型(Content-Type)
+ 禁止非 HTML 类型的响应包设置为 “text/html”
2. 必须设置安全的 HTTP 响应头
+ X-Content-Type-Options: nosniff
+ HttpOnly 控制用户登鉴权的 Cookie 字段 应当设置 HttpOnly 属性以防止被 XSS 漏洞 / JavaScript 操纵泄漏
+ X-Frame-Options
该头用于指示浏览器禁止当前页面在frame、 iframe、embed等标签中展现。从而避免点击劫持问题。它有三个可选的值: DENY: 浏览器会拒绝当前页面加 载任何frame页面; SAMEORIGIN:则frame页面的地址只能为同源域名下的页面 ALLOW-FROM origin:可以定 义允许frame加载的页面地址
3. 必须对外输出页面包含第三方数据时进行编码处理
# 推荐使用 mozilla 维护的 bleach 库来进行过滤
import bleachbleach.clean('an <script>evil()</script> example')
# u'an <script>evil()</script> example'
4.8 数据输出
1. 敏感数据加密存储
+ 敏感数据应使用SHA2、RSA等算法进行加密存储
+ 敏感数据应使用独立的存储层,并在访问层开启访问控制
+ 包含敏感信息的临时文件或缓存一旦不再需要应立刻删除
2. 敏感信息须再后台进行脱敏后返回,禁止接口返回敏感信息交由前端/客户端进行脱敏处理
3. 高敏感信息禁止存储、展示
+ 口令、密保答案、生理标识等鉴权信息禁止展示
+ 非金融类业务,信用卡cvv码及日志禁止存储
4. 个人敏感信息脱敏展示
+ 身份证只显示第一位和最后一位字符,如3****************1
+ 移动电话号码隐藏中间6位字符,如134******48
+ 工作地址/家庭地址最多显示到 “区” 一级
+ 银行卡号仅显示最后4位字符,如************8639
5. 隐藏后台地址
+ 若程序对外提供了登录后台地址,应使用随机字符串隐藏地址("xxxx/login" -> "xxxx/ranD0Str")
4.9 权限管理
1. 默认鉴权
+ 除非资源完全可对外开放,否则系统默认进行身份认证(使用白名单的方式放开不需要认证的接口或页面)
2. 授权遵循最小权限原则
+ 程序默认用户应不具备任何操作权限
3. 避免越权访问
+ 对于非公共操作,应当校验当前访问账号进行 操作权限(常见于CMS)和 数据权限校验
+ 验证当前用户的登录态
+ 从可信结构中获取经过校验的当前请求账号的身份信息(如:session),禁止从用户请求参数或 Cookie 中获取外部传入不可信用户身份直接进行查询
+ 校验当前用户是否具备该操作权限
+ 校验当前用户是否具备所操作数据的权限
+ 校验当前操作是否账户是否预期账户
4. 及时清理不需要的权限
+ 程序应定期清理非必需用户的权限
4.10 异常处理
1. 应合理使用 try/except/finally 处理系统异常,避免出错信息输出到前端
2. 对外环境禁止开启 debug 模式,或将程序运行日志输出到前端
4.11 Flask 安全
1. XSS 跨站脚本攻击略
2. CSRF 跨站请求伪造对于修改服务器上的内容的每个请求,必须使用一次性令牌并将其存储在 cookie 中,并将其与表单一起传输。再次收到服务器上的数据后,比较两个令牌,并确保它们相等。Flask 并没有提供 CSRF 解决方案,因为使用 Flask-WTF 可以解决。
3. JSON 安全性使用 `return jsonify({'code': 200, 'data': None, 'msg': ''})`
在 Flask 0.10- 中,jsonify 没有将顶级数组序列化为 json。现在已支持序列化数组。
4. 安全 HTTP/HTTPS Headers
+ `response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'`
告诉浏览器将所有的 HTTP 请求转换为 HTTPS,防止中间人攻击
+ `response.headers['Content-Security-Policy'] = "default-src 'self'"`
告诉浏览器它可以从哪里加载各种类型的资源。尽可能使用此标头
+ `response.headers['X-Content-Type-Options'] = 'nosniff'`
强制浏览器接受响应内容类型,而不是尝试检测它,这可能会被滥用导致 XSS 攻击
+ `response.headers['X-Frame-Options'] = 'SAMEORIGIN'`
防止外部网站将您的网站嵌入到iframe. 这可以防止一类攻击,其中外框的点击可以无形地转换为对页面元素的点击。这也称为“点击劫持”。
+ `response.headers['X-XSS-Protection'] = '1; mode=block'`
如果请求包含类似 JavaScript 的内容并且响应包含相同的数据,浏览器将尝试通过不加载页面来防止反射型 XSS 攻击。
5. 设置 Cookie 选项
# 全局设置 cookie config
app.config.update(
# Secure 将 cookie 限制为仅 HTTPS 流量
SESSION_COOKIE_SECURE=True,
# HttpOnly 保护 cookie 的内容不被 JavaScript 读取
SESSION_COOKIE_HTTPONLY=True,
# SameSite限制 cookie 与来自外部站点的请求一起发送的方式
# Lax防止从外部站点发送带有 CSRF 倾向请求的 cookie,例如提交表单
# Strict阻止发送带有所有外部请求的 cookie,包括遵循常规链接
SESSION_COOKIE_SAMESITE='Lax',
)
# 局部设置 cookie config
response.set_cookie('username', 'flask', secure=True, httponly=True, samesite='Lax')
# 局部设置 cookie 过期时间
response.set_cookie('snakes', '3', max_age=600)
# 全局设置 cookie 过期时间
app.config.update(
PERMANENT_SESSION_LIFETIME=600
)@app.route('/login', methods=['POST'])def login():
...
session.clear()
session['user_id'] = user.id
session.permanent = True
...
# 使用itsdangerous.TimedSerializer签名和验证等cookie值(或需要安全签名的任何值)!!!!!
6. 复制/粘贴 到终端某些隐藏字符如 "\b"、"^H" 会导致文本在 HTML 中的呈现方式与 粘贴到终端 时的解释方式不同。如果希望保持一致,考虑应用额外的过滤,替换掉所有的隐藏特殊字符。