应使用 ast.literal_eval 替代 eval 解析用户字符串,因其仅支持基础字面量;路径需用 pathlib.path.resolve() 归一化并校验根目录;subprocess 禁用 shell=true;json 解析须用 object_hook 限制嵌套深度。

用 ast.literal_eval 替代 eval 解析字符串数据
直接调 eval 执行用户给的字符串,等于把 shell 交出去——哪怕只传了个 "[1, 2, 3]",攻击者也能塞进 __import__('os').system('rm -rf /')。而 ast.literal_eval 只认基础字面量:数字、字符串、列表、字典、元组、布尔和 None,遇到函数调用或属性访问直接抛 ValueError。
- 适用场景:解析用户提交的 JSON-like 字符串(如配置项、过滤条件),但又不想引入完整 JSON 解析器
- 注意它不支持单引号字符串以外的语法变体,比如
"{'a': 1}"合法,"{a: 1}"(没引号的 key)会报错 - 性能上比
json.loads略慢,但胜在能处理单引号和末尾逗号等 Python 原生语法 - 示例:
import ast user_input = "{'name': 'Alice', 'scores': [95, 87]}" data = ast.literal_eval(user_input) # ✅ 安全 # user_input = "__import__('sys').exit()" # ❌ literal_eval 直接 ValueError
接收文件路径时必须用 pathlib.Path.resolve() 校验
用户传个 "../../etc/passwd" 就读到系统文件?问题出在没做路径归一化和根目录约束。光靠字符串替换 ".." 或正则过滤远远不够,符号链接、大小写混用、Unicode 归一化都会绕过。
- 先用
Path(user_path).resolve()得到绝对真实路径,再检查是否落在允许目录下 - 务必捕获
FileNotFoundError和PermissionError,因为resolve()在路径不存在或不可读时会抛异常 - 别依赖
os.path.abspath(),它不处理符号链接,且不校验路径是否存在 - 示例:
from pathlib import Path allowed_root = Path("/var/www/uploads") user_path = Path("../.ssh/id_rsa") try: real_path = user_path.resolve() if not str(real_path).startswith(str(allowed_root)): raise PermissionError("Path outside allowed directory") except (FileNotFoundError, PermissionError): raise ValueError("Invalid or inaccessible path")
用 subprocess.run 传参时禁用 shell=True
用户输入拼进命令字符串再 shell=True 执行,是远程代码执行(RCE)的黄金入口。哪怕加了 shlex.quote,不同 shell 的行为差异、环境变量注入、空格与 Unicode 边界问题仍可能翻车。
- 永远把命令拆成列表:
["ls", "-l", user_supplied_filename],让subprocess自己做参数传递 - 如果非得用 shell 功能(比如管道、通配符),必须先用
shlex.split()解析原始字符串,再逐项shlex.quote(),但强烈不建议 -
cwd、env、timeout这些参数要显式设好,避免继承危险环境 - 错误现象:用户输
"; rm -rf /"导致整个磁盘被清空
JSON 解析必须指定 object_hook 防止递归爆炸
恶意构造的超深嵌套 JSON(比如 10 万层字典套字典)会让 json.loads 占满栈内存甚至触发 C 层递归限制,导致服务崩溃。Python 默认不限制嵌套深度,这是个隐形炸弹。
立即学习“Python免费学习笔记(深入)”;
- 用
object_hook回调函数,在每次构建 dict 时检查当前层级,超限就抛异常 - 别信
sys.setrecursionlimit(),它治标不治本,还可能影响其他逻辑 - 第三方库如
orjson默认更严格,但原生json必须手动加固 - 示例:
import json def guard_depth(obj): if isinstance(obj, dict): # 实际中可从闭包或上下文获取当前深度 if len(obj) > 1000: # 防止超大对象 raise ValueError("Too many keys in object") return obj <p>json.loads(user_json, object_hook=guard_depth)
事情说清了就结束。最常被忽略的是路径校验后没检查符号链接跳转、JSON 深度限制写死在回调里却忘了传入实际层级计数、还有人以为 ast.literal_eval 能安全处理带注释的 Python 字面量——它不能。










