这六条是我们扫描器里最容易命中的六条(编号对应判据全文那篇)。它们的共同点是:写法很短、看着人畜无害、出事一步到位。每条给「改前 / 改后」。
1. SEC001 / SEC002:eval() 与 exec()
改前:result = eval(input("算式:"))——输入框里可以写任意表达式,包括读文件、发起网络连接。
改后:需要算数学式就用 ast.literal_eval(只认字面量),需要「根据字符串选函数」就用显式字典映射,把可选项钉死在自己写的表里。
2. SEC006:shell=True
改前:subprocess.run("convert " + filename, shell=True)——文件名里带一个分号,后面就跟了一条你的命令。
改后:参数传列表 subprocess.run(["convert", filename], shell=False),并且先把输入文件名做白名单校验。
3. SEC005:SQL 字符串拼接
改前:cur.execute(f"SELECT * FROM t WHERE id = {uid}")。这一条最阴险的地方是:它看起来只是在拼一个变量,而绝大多数人写的时候都认为「这个值是程序自己生成的,不会有事」。
改后:参数化查询 cur.execute("SELECT * FROM t WHERE id = ?", (uid,))。不要用「先转义再拼」这种做法,那是把安全寄托在自己的细心上。
4. SEC003:pickle.load() 读不可信数据
改前:obj = pickle.load(open(sys.argv[1], "rb"))——pickle 在反序列化时可以调用任意对象的 __reduce__,等于把执行权交给文件作者。
改后:换 JSON 或 MessagePack;确实需要对象序列化的,只加载自己签名的数据,别加载下载来的。
5. SEC004:yaml.load() 未指定 Loader
改前:conf = yaml.load(text)——默认 Loader 可以构造任意 Python 对象,和 pickle 是同类问题。
改后:yaml.safe_load(text)。旧代码里如果确实依赖自定义标签,那要用显式的 Loader 子类,而不是退回默认 Loader。
6. SEC007 / SEC008:硬编码密钥与密码
改前:api_key = "sk-..." 写在源码里,跟着仓库一起进了 git 历史。
改后:从环境变量或密钥管理服务读;已经提交过的那一把必须轮换,删掉代码行不算解决了问题——历史里还在,而且任何拿到仓库的人都能读到。
顺带三条容易漏的
- SEC009:用
assert做校验。生产环境跑python -O时整段 assert 会被优化掉,你的「参数校验」就这么消失了。 - SMELL001:裸
except:。它会把KeyboardInterrupt和真正的 bug 一起吞掉,出问题时你什么日志都看不见。 - SMELL002:
def f(items=[])。默认值在函数定义时创建一次,所有调用共用同一个列表——这种 bug 排查起来很费时间。
想把手上的代码过一遍
两个入口,按预算分:¥9.9 代码体检(单文件 ≤300 行,九条判据 + 人工确认 Top3)和代码审查 + 安全审计(¥299 起)(15 条全跑,报告按严重级分类,每条带文件路径、行号与修复建议)。仓库在公开 GitHub 上也可以直接扫。
报告里每条发现都写成「这里有问题、这样改」,不是「可能有问题」。没命中的判据也会如实写「未命中」,不会为了让报告显得值钱而凑几条。