飞桨(PaddlePaddle)命令注入漏洞 PDSA-2023-020 深度解析:_wget_download任意命令执行(CVE-2023-52311)
【免费下载链接】PaddlePArallel Distributed Deep LEarning: Machine Learning Framework from Industrial Practice (『飞桨』核心框架,深度学习&机器学习高性能单机、分布式训练和跨平台部署)项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle
本文围绕飞桨(PaddlePaddle)安全公告 PDSA-2023-020 展开,剖析paddle.utils.download._wget_download中因直接拼接 URL 调用系统命令导致的命令注入漏洞(CVE-2023-52311),并结合当前仓库源码 python/paddle/utils/download.py 说明其修复方向、防御手法与安全加固实践,帮助读者理解该漏洞成因并掌握排查、复现与防护方法。
漏洞概述
| 项目 | 内容 |
|---|---|
| 安全公告编号 | PDSA-2023-020 |
| CVE 编号 | CVE-2023-52311 |
| 漏洞类型 | 命令注入(Command Injection) |
| 受影响函数 | paddle.utils.download._wget_download |
| 影响版本 | 飞桨 2.6.0 之前版本(< 2.6.0) |
| 修复版本 | 飞桨 2.6.0 及之后版本 |
| 报告来源 | huntr.com |
该公告完整收录于 security/advisory/pdsa-2023-020_cn.md,同时在 security/README_cn.md 的公告清单中以"Command injection in _wget_download,受影响版本 < 2.6.0"列出,是飞桨 2.6.0 之前版本中已披露的多项安全问题之一。
漏洞根因分析
命令注入的本质
命令注入漏洞的根源在于:程序将外部可控输入(此处为 URL 字符串)直接拼接进系统命令中执行,且未对输入做任何过滤或转义。攻击者只要在 URL 中注入 shell 元字符(如;、&、|、$()、反引号`等),就能让拼接后的命令在原有意图之外执行任意命令。
历史实现中的缺陷点
在旧版本中,_wget_download(url, fullname)的实现方式类似:
def _wget_download(url, fullname): # 旧版本实现(存在漏洞) os.system('wget -O "%s" %s' % (fullname, url))攻击者传入的url参数未经任何校验直接拼入os.system命令字符串。官方公告给出的 PoC 如下:
from paddle import utils utils.download._wget_download("aa; touch codexecution", "bb")当执行_wget_download("aa; touch codexecution", "bb")时,实际拼接出的 shell 命令为:
wget -O "bb" aa; touch codexecution由于;是 shell 的命令分隔符,touch codexecution会被当作独立命令执行,攻击者在服务器上成功创建了codexecution文件,即实现了任意命令执行。若攻击者将注入负载替换为curl ... | sh、反弹 shell 等,则可直接接管运行飞桨的目标机器。
攻击面与可利用性
从源码结构看,download.py模块负责下载预训练模型权重、数据集等资源,其主要入口包括:
get_weights_path_from_url(url, md5sum):下载预训练权重,默认存放于~/.cache/paddle/hapi/weights;get_path_from_url(url, root_dir, md5sum, ...):通用下载入口,支持wget与get两种方法。
在漏洞版本中,get_path_from_url的method参数支持'wget',该路径即调用存在缺陷的_wget_download。因此,凡是能向下载 URL 传入外部输入的场景(例如加载来自第三方 URL 的模型权重、数据集下载、定制化模型库)均可能成为攻击入口,攻击者无需认证即可尝试注入。
修复与当前仓库的防御实现
官方在 commitd5550d3f2f5bab48c783b4986ba1cd8e061ce542(PR #59957)中修复了该问题,修复随飞桨 2.6.0 发布。
移除危险的 wget 调用链
在当前仓库中,_download_methods仅保留'get'一种实现,彻底移除了 wget 调用:
_download_methods = {'get': _get_download}对应地,_download通过assert method in _download_methods对方法名做白名单校验,同时限制重试次数(DOWNLOAD_RETRY_LIMIT = 3),杜绝了将 URL 拼入 shell 命令的可能。其源码路径为 python/paddle/utils/download.py。
以 Python 原生 HTTP 客户端替代系统命令
修复后的_get_download使用httpx.stream以流式方式下载文件:
def _get_download(url, fullname): fname = osp.basename(fullname) try: with httpx.stream("GET", url, timeout=None, follow_redirects=True) as req: if req.status_code != 200: raise RuntimeError(f"Downloading from {url} failed with code {req.status_code}!") tmp_fullname = fullname + "_tmp" total_size = req.headers.get('content-length') with open(tmp_fullname, 'wb') as f: if total_size: with tqdm(total=(int(total_size) + 1023) // 1024) as pbar: for chunk in req.iter_bytes(chunk_size=1024): f.write(chunk) pbar.update(1) else: for chunk in req.iter_bytes(chunk_size=1024): if chunk: f.write(chunk) shutil.move(tmp_fullname, fullname) return fullname except Exception as e: logger.info(f"Downloading {fname} from {url} failed with exception {e}") return False关键安全设计如下:
- 不经过 shell:URL 仅作为
httpx请求参数使用,不存在任何系统命令拼接,从根上消除了命令注入面; - 临时文件 + 原子替换:先写入
fullname + "_tmp",下载完成后shutil.move原子移动到目标路径,避免半成品文件被误用; - 状态码校验:非 200 响应直接抛出
RuntimeError; - 下载失败安全返回:任何异常被捕获并记录日志后返回
False,由上层_download决定重试。
下载完整性校验:md5 检查
_download循环中持续执行_md5check(fullname, md5sum),仅当文件存在且 md5 一致时才认为下载成功:
while not (osp.exists(fullname) and _md5check(fullname, md5sum)): if retry_cnt < DOWNLOAD_RETRY_LIMIT: retry_cnt += 1 else: raise RuntimeError(f"Download from {url} failed. Retry limit reached") if not _download_methodsmethod: time.sleep(1) continue_md5check分块(4KB)计算文件哈希并与期望值比对,这不仅能发现下载损坏,也能在一定程度上抵御恶意/被篡改的下载源。
解压环节的路径穿越防护
与命令注入同源的安全加固还体现在解压环节:_safe_extract_tar、_safe_extract_zip与_safe_extract_member拒绝绝对路径与../路径穿越(target_path.startswith(os.path.abspath(target_dir) + os.sep)校验),并对符号链接、硬链接等特殊文件采取跳过或抛错策略,防止解压炸弹与 symlink 攻击。相关实现见 python/paddle/utils/download.py 中_safe_extract_*系列函数。
影响范围与版本修复
- 受影响版本:飞桨 2.6.0 之前所有版本;
- 修复版本:飞桨 2.6.0 及以上(当前仓库
version.txt显示为 3.5.0,已包含修复)。
建议所有飞桨用户立即升级至 2.6.0 或更高版本。升级后可通过检查_download_methods中是否仅存在'get'实现,快速确认已移除_wget_download调用链。
安全自查与防护建议
代码自查清单
针对类似"URL/文件名拼入系统命令"的代码模式,建议按以下清单排查:
- 是否存在
os.system、subprocess.call/run/Popen且参数由外部输入直接拼接; - 是否对 URL 做过
http:///https://协议白名单校验(当前仓库通过 python/paddle/utils/download.py 的is_url做基础检查); - 是否使用参数数组形式传参(
subprocess.run(["wget", "-O", fname, url])),避免 shell 解释; - 是否对下载内容做完整性校验(md5/sha256);
- 解压路径是否防止路径穿越与符号链接攻击。
运行非信任模型的安全准则
飞桨官方在 SECURITY_cn.md 中明确要求:永远在沙箱中加载和运行非信任模型,并了解其可能造成的影响。这是因为paddle.load底层使用 pickle 反序列化,恶意模型可借此执行任意命令。同理,凡涉及外部 URL 资源加载的功能都应视为可攻击面,建议在隔离环境(容器、虚拟机、专用沙箱)中运行,并遵循最小权限原则。
漏洞上报与致谢
飞桨鼓励负责任地披露(Responsible Disclosure)安全问题,安全研究人员可通过 BSRC 平台提交漏洞,提交时建议包含:
- 漏洞细节、复现步骤与 PoC;
- 攻击场景与攻击者能达到的效果;
- 问题是否已公开及公开情况;
- 姓名与从属关系(可选公开)。
飞桨会将修复情况注明在版本发布说明中,并在致谢公告中公布漏洞详情与提报人信息。完整安全模型与上报流程见 SECURITY_cn.md 与 security/README_cn.md。本次漏洞由 huntr.com 提交。
延伸阅读
- PDSA-2023-019:get_online_pass_interval 命令注入:同类"命令注入"问题的另一案例,可对照学习同类漏洞模式;
- PDSA-2023-005:fs.py 命令注入:受影响版本 < 2.5.0 的同类问题;
- 安全公告总表:查看全部已披露公告及受影响版本;
- 安全使用飞桨指南:了解飞桨安全模型、沙箱运行建议与漏洞上报流程。
【免费下载链接】PaddlePArallel Distributed Deep LEarning: Machine Learning Framework from Industrial Practice (『飞桨』核心框架,深度学习&机器学习高性能单机、分布式训练和跨平台部署)项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考