PyYAML默认加载器存在反序列化风险,yaml.load()可能执行任意代码;必须改用yaml.safe_load()解析配置,它仅支持基础类型,拒绝Python特有标签,是安全标配。

PyYAML默认加载器存在反序列化风险
直接用 yaml.load() 读取不受信的YAML文件可能执行任意代码,比如文件里藏了 !!python/object:os.system 这类标签。这不是理论漏洞,是真实被利用过的安全问题。
根本原因是 yaml.load() 默认使用 FullLoader(或更早版本的 Loader),它允许解析任意Python对象。生产环境必须禁用。
- 永远不要在读取外部配置(如用户上传、CI/CD传入、第三方服务返回)时用
yaml.load(data) - 即使配置来自内部,也应默认按“不可信”处理——权限变更、路径污染、模板注入都可能让YAML内容失控
- Python 5.4+ 的 PyYAML 已将
yaml.load()的默认 loader 改为SafeLoader,但旧版本或显式指定Loader仍会绕过保护
推荐用 yaml.safe_load() 解析纯配置数据
safe_load() 只支持基础 YAML 标签(str、int、list、dict、bool、null),不解析 Python 特有类型,是配置文件的标准解法。
示例:
import yaml
<p>with open("config.yaml", "r", encoding="utf-8") as f:
cfg = yaml.safe_load(f)</p><p>print(cfg["database"]["host"]) # 正常访问字典
</p>
- 如果文件为空或只有注释,
safe_load()返回None,记得判空再取键 - 遇到语法错误(如缩进错、冒号漏写),抛出
yaml.YAMLError,建议包一层 try/catch 做友好提示 - 不支持
!include、!env等自定义标签——这是好事,说明你没引入额外解析逻辑,降低攻击面
需要扩展功能时,必须显式控制标签解析行为
有些项目依赖 !include 合并配置,或用 !float 强制类型。这时不能退回到 load(),而应继承 SafeLoader 并注册白名单标签。
例如只允许 !include(读取同目录下的其他YAML):
import yaml
from pathlib import Path
<p>class ConfigLoader(yaml.SafeLoader):
def <strong>init</strong>(self, stream):
super().<strong>init</strong>(stream)
self._root = Path(stream.name).parent if hasattr(stream, "name") else Path(".")</p><pre class="brush:php;toolbar:false;">def include(self, node):
filename = self.construct_scalar(node)
filepath = self._root / filename
with open(filepath, encoding="utf-8") as f:
return yaml.load(f, ConfigLoader)ConfigLoader.add_constructor("!include", ConfigLoader.include)
使用
with open("main.yaml") as f: cfg = yaml.load(f, ConfigLoader)
- 所有自定义构造器必须从
SafeLoader派生,不能基于FullLoader或UnsafeLoader - 构造器内部避免执行 shell 命令、动态 import、或读取任意路径——
self._root / filename是必要约束 - 若需支持环境变量插值(如
${DB_HOST}),应在safe_load()后用os.environ.get()替换字符串,而非在 YAML 解析层做
Windows路径和编码容易引发 UnicodeDecodeError
YAML 文件若含中文或特殊符号,在 Windows 上用默认 open() 可能报错:「UnicodeDecodeError: 'gbk' codec can't decode byte...」。这不是 PyYAML 的问题,而是文件打开方式不对。
- 始终显式指定
encoding="utf-8",哪怕你确定是英文配置——不同编辑器保存默认编码不一致 - 路径中含反斜杠
\时,YAML 本身不识别转义,建议统一用正斜杠/或原始字符串r"dir\sub",但最好在 YAML 里写成path: "data/config.yaml" - 用
Path(__file__).parent / "config.yaml"构造路径,比拼接字符串更可靠
PyYAML 的安全边界其实很清晰:只要不用 load()、不手动传入危险 loader、不盲目信任输入源,配置解析就不会成为突破口。真正麻烦的是那些“顺手加个 !eval 标签”的临时想法——它们往往出现在上线前最后一刻。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











