开发安全指北:ORM 防住了 SQL 注入,那“文件名注入”呢?
在现代 Web 开发中,ORM(对象关系映射)框架的普及极大地降低了 SQL 注入的风险。很多开发者(包括以前的我)因此产生了一种错觉:“只要我用了 SQLAlchemy 或 Django ORM,我的后端就是安全的。”
然而,现实往往会给你一记响亮的耳光。最近我在做安全排查时被告知存在“文件注入”和“文件名注入”风险。这让我意识到,Web 安全远不止 SQL 注入那么简单。
今天我们就来聊聊,除了 SQL 注入,开发过程中还有哪些容易被忽视的“注入”陷阱,以及我们该如何防御。
一、 文件名注入与路径遍历 (Path Traversal)
这是很多初级甚至中级开发者最容易踩的坑。
1. 场景还原
假设你有一个上传头像的功能,后端代码可能是这样写的:
1 | import os |
看起来很正常,对吧?
2. 攻击方式
如果攻击者构造了一个恶意的请求,把文件名改成了这样:../../../../etc/passwd(Linux)或者 ../../windows/system32/cmd.exe(Windows)。
当 os.path.join 拼接路径时,它会解析 ..,导致文件最终被保存到了 /var/www/uploads/../../../../etc/passwd,也就是 /etc/passwd。
后果:
- 覆盖系统关键文件:攻击者可以覆盖你的配置文件、系统脚本,甚至 SSH authorized_keys。
- 任意文件读取:如果是下载功能,攻击者可以通过
../../读取服务器上的任意文件(源代码、数据库配置等)。
3. 防御方案
核心原则:永远不要信任用户的输入,包括文件名。
在 Python 中,werkzeug 库提供了一个非常好的工具函数 secure_filename。
1 | from werkzeug.utils import secure_filename |
如果不使用库,你也必须手动剥离路径分隔符,并限制文件名的字符集(例如只允许字母、数字、下划线)。
二、 命令注入 (Command Injection)
当你的应用需要调用系统命令时,如果参数处理不当,就会引发命令注入。
1. 场景还原
假设你需要做一个简单的网络诊断工具,允许用户输入 IP 进行 Ping 测试。
1 | import os |
2. 攻击方式
用户输入正常的 IP 127.0.0.1 没问题。
但如果用户输入:127.0.0.1; rm -rf / (Linux) 或 127.0.0.1 & del C:\* (Windows)。
最终执行的命令变成了:ping -c 1 127.0.0.1; rm -rf /。
系统会先执行 ping,紧接着执行删除命令。
3. 防御方案
方法一:使用 subprocess 模块并避免 shell=True
这是最推荐的做法。将命令和参数作为一个列表传递,Python 会负责处理参数转义,系统会将参数视为字符串而非可执行命令。
1 | import subprocess |
方法二:使用专用库代替系统命令
能用 Python 库解决的,绝不调用系统命令。
- 想 Ping?用
pythonping库。 - 想处理视频?用
ffmpeg-python绑定库,而不是os.system('ffmpeg ...')。
三、 Python 特有的坑:Pickle 反序列化漏洞
这就不是“注入”了,但属于极其严重且容易被忽视的安全问题。
1. 场景还原
为了方便,很多开发者会用 pickle 模块把 Python 对象序列化后存入 Redis 或文件中,读取时再反序列化。
1 | import pickle |
2. 攻击方式
pickle 是一个允许执行任意代码的协议。攻击者可以构造一段恶意的字节流,当你的代码执行 pickle.loads() 时,这段字节流里的恶意代码(比如反弹 Shell)就会被立刻执行。
3. 防御方案
核心原则:绝对不要对不可信的数据源使用 pickle.load() 或 pickle.loads()。
- 使用 JSON:如果只是存储数据(字典、列表、字符串、数字),永远优先使用
json。它不仅跨语言通用,而且安全。 - 签名验证:如果你必须用
pickle(比如在内部可信节点间传输),必须对数据进行加密签名(HMAC),确保数据在传输过程中没有被篡改。
四、 总结
安全开发不仅仅是“防住 SQL 注入”那么简单。作为一个后端开发,我们需要建立起一种“零信任”的思维模式:
- 输入即恶意:前端传来的文件名、IP 地址、ID、JSON 数据,统统当作潜在的攻击载体。
- 最小权限原则:运行 Web 服务的用户不应该有 root 权限,不应该能访问项目目录以外的文件。
- 使用成熟的轮子:不要自己写正则去过滤路径,用
secure_filename;不要自己拼 SQL,用 ORM;不要自己拼 Shell 命令,用subprocess。
希望这篇总结能帮你避开开发路上的那些安全暗礁。




