别被“90%”吓到,这真不是危言耸听。根据OWASP Top 10和无数血泪教训,这几个坑太常见了。今天咱们就用代码说话,把它们一个个揪出来。
1. 命令注入:shell=True 是你的“免死金牌”吗?🤔
看到 subprocess.run(cmd, shell=True) 就该打个激灵!用户输入一旦拼接到命令里,服务器就归黑客了。
# 千万别这么干!
user_file = request.GET['file']
subprocess.run(f"cat {user_file}", shell=True) # 输入 "; rm -rf /" 试试?
安全姿势是绕过Shell,直接调用程序。把命令和参数拆成列表,Python会原封不动地传过去。
# 这样才对!
import shlex
user_file = request.GET['file']
# 先做基础校验,比如只允许字母数字下划线
ifnot user_file.replace('_', '').isalnum():
raise ValueError("Invalid filename")
subprocess.run(["cat", user_file])
2. 反序列化炸弹:pickle 不是万能的“存档键” 🧨
很多人图方便,用 pickle 序列化对象存数据库或发网络。但 pickle 能执行任意Python代码!
# 攻击者伪造的数据
malicious_data = b"cos\nsystem\n(S'echo pwned'\ntR."
import pickle
pickle.loads(malicious_data) # Boom! 命令执行了
记住:永远不要反序列化不可信来源的数据。如果只是存数据,用 json、xml 或 marshal(仅限同一Python版本)更安全。
3. XML外部实体(XXE):别让解析器“读心术”变“读文件术” 📖
处理XML时,如果没关掉外部实体加载,黑客就能让你的服务器读任何文件。
# 危险的默认行为!
from xml.etree.ElementTree import fromstring
xml_data = """
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<foo>&xxe;</foo>
"""
root = fromstring(xml_data) # /etc/passwd内容可能就泄露了!
防御很简单:使用 defusedxml 库,它默认禁用了所有危险特性。
# 安全第一!
from defusedxml.ElementTree import fromstring
# 即使收到恶意XML,也会抛出异常
root = fromstring(xml_data)
4. 路径遍历:你以为的“沙盒”,其实是“后门” 🚪
通过 ../ 这种相对路径,攻击者能跳出你设定的目录,访问系统任意文件。
# 经典漏洞
base_dir = "/safe/uploads/"
user_path = request.GET['path'] # 比如 "../../../etc/passwd"
full_path = os.path.join(base_dir, user_path)
with open(full_path, 'r') as f: # Oops!
content = f.read()
正确做法是用 os.path.realpath 解析出真实路径,再检查它是否在允许的根目录内。
# 安全校验
allowed_root = "/safe/uploads/"
user_path = request.GET['path']
full_path = os.path.realpath(os.path.join(allowed_root, user_path))
ifnot full_path.startswith(allowed_root):
raise PermissionError("Path traversal attempt!")
with open(full_path, 'r') as f:
content = f.read()
5. 动态执行:eval 和 exec 是“潘多拉魔盒” 📦
为了灵活性,有些框架会用 eval 执行动态代码。这等于把服务器的控制台直接交给了用户。
# 别手贱!
user_code = input("Enter your code: ")
result = eval(user_code) # 输入 "__import__('os').system('id')" 就完了
替代方案:如果真需要动态逻辑,考虑使用专用的、受限的表达式求值库,或者自己写一个简单的解析器。永远不要信任用户的输入。