安全开发指南(持续更新)

开发规范

October 9, 2021
2021 技术

开发规范

腾讯 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 &lt;script&gt;evil()&lt;/script&gt; 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 中的呈现方式与 粘贴到终端 时的解释方式不同。如果希望保持一致,考虑应用额外的过滤,替换掉所有的隐藏特殊字符。