Skip to content

ptrace Exit-Race#21472

Open
bhaskarbhar wants to merge 4 commits into
rapid7:masterfrom
bhaskarbhar:cve_2026_46333
Open

ptrace Exit-Race#21472
bhaskarbhar wants to merge 4 commits into
rapid7:masterfrom
bhaskarbhar:cve_2026_46333

Conversation

@bhaskarbhar
Copy link
Copy Markdown
Contributor

ptrace_exit_race.mp4

Description

This PR adds a new local Linux information disclosure module for CVE-2026-46333.

Fixes #21471

CVE-2026-46333 is a race condition vulnerability in the Linux kernel's __ptrace_may_access() logic during process teardown. During do_exit(), privileged file descriptors may remain accessible temporarily after task->mm becomes NULL, allowing unprivileged local users to duplicate sensitive file descriptors using pidfd_getfd(2).

The module targets /usr/bin/chage and attempts to disclose /etc/shadow by racing the process teardown window repeatedly.

The module:

  • performs kernel version validation,
  • checks for gcc,
  • validates the presence of /usr/bin/chage,
  • compiles a temporary local exploit binary,
  • attempts repeated race-condition exploitation,
  • stores disclosed /etc/shadow contents as loot,
  • and cleans up generated artifacts after execution.

This module performs information disclosure only and does not create a new session.

Documentation has also been added under:

documentation/modules/exploit/linux/local/cve_2026_46333_chage.md

Verification

  • Start msfconsole
  • Obtain a Linux shell session on a vulnerable host
  • use exploit/linux/local/cve_2026_46333_chage
  • set SESSION <session_id>
  • Optionally set RACE_ROUNDS
  • Run check
  • Verify the target appears vulnerable
  • Run run
  • Verify /etc/shadow contents are disclosed
  • Verify loot is stored successfully
  • Verify temporary exploit artifacts are cleaned up
  • Verify no new session is created
  • Verify documentation was added

Testing

Tested successfully on:

  • Debian 13 x64
  • Linux kernel 6.12.85+deb13-amd64

The module successfully disclosed /etc/shadow and stored the contents as Metasploit loot.

I have attached a POC video.

@h00die
Copy link
Copy Markdown
Contributor

h00die commented May 17, 2026

Exploit modules execute a payload. Since this is a file content disclosure it should be rewritten as a post module

@bcoles
Copy link
Copy Markdown
Contributor

bcoles commented May 17, 2026

Modules in modules/exploits/linux are expected to execute a payload (usually creating a session). As this module discloses file contents it should be placed in modules/post/linux/.

@bhaskarbhar
Copy link
Copy Markdown
Contributor Author

bhaskarbhar commented May 17, 2026

@bcoles @h00die Sure. I will move the module and its documentation to their respective /post/linux/gather directory. And make a new Video POC.

@bhaskarbhar
Copy link
Copy Markdown
Contributor Author

bhaskarbhar commented May 17, 2026

ptrace_exit_race1.mp4

@h00die @bcoles

Updated Description

CVE-2026-46333 is a race condition vulnerability in the Linux kernel's
__ptrace_may_access() logic during process teardown. During do_exit(),
privileged file descriptors may remain accessible temporarily after
task->mm becomes NULL, allowing unprivileged local users to duplicate
sensitive file descriptors using pidfd_getfd(2).

The module targets /usr/bin/chage and attempts to disclose
/etc/shadow by repeatedly racing the process teardown window.

The module:

  • performs kernel version validation,
  • checks for gcc,
  • validates the presence of /usr/bin/chage,
  • compiles a temporary local helper binary,
  • attempts repeated race-condition exploitation,
  • stores disclosed /etc/shadow contents as loot,
  • and cleans up generated artifacts after execution.

This module performs information disclosure only and does not create a
new session.

Documentation has also been added under:

documentation/modules/post/linux/gather/cve_2026_46333_chage.md

Verification

  • Start msfconsole
  • Obtain a Linux shell session on a vulnerable host
  • use post/linux/gather/cve_2026_46333_chage
  • set SESSION <session_id>
  • Optionally set RACE_ROUNDS
  • Run run
  • Verify /etc/shadow contents are disclosed
  • Verify loot is stored successfully
  • Verify temporary exploit artifacts are cleaned up
  • Verify no new session is created
  • Verify documentation was added

Testing

Tested successfully on:

  • Debian 13 x64
  • Linux kernel 6.12.85+deb13-amd64

The module successfully disclosed /etc/shadow and stored the contents
as Metasploit loot.

Note

In the video POC you can see that the module does not work everytime, i.e. you have to configure the RACE_ROUNDS according to your need. The file disclosure can be after 500 times(default), 1000 times or more as per the option set.

@gardnerapp
Copy link
Copy Markdown
Contributor

I just added a pull request to your repo. The changes add entropy to the target binary via a random int variable. This thwarts hashed based detection of the exploit. Additionally, I added a TARGET_FILE option to support reading files other than /etc/shadow.

@bhaskarbhar
Copy link
Copy Markdown
Contributor Author

bhaskarbhar commented May 18, 2026

I just added a pull request to your repo. The changes add entropy to the target binary via a random int variable. This thwarts hashed based detection of the exploit. Additionally, I added a TARGET_FILE option to support reading files other than /etc/shadow.

@gardnerapp Thanks for the review. I’m not currently seeing the PR though. Could you send the PR link?

@smcintyre-r7 smcintyre-r7 self-assigned this May 18, 2026
@smcintyre-r7 smcintyre-r7 moved this from Todo to In Progress in Metasploit Kanban May 18, 2026
Comment thread modules/post/linux/gather/cve_2026_46333_chage.rb Outdated
Comment thread modules/post/linux/gather/cve_2026_46333_chage.rb Outdated
Comment thread modules/post/linux/gather/cve_2026_46333_chage.rb Outdated
Comment thread modules/post/linux/gather/cve_2026_46333_chage.rb Outdated

if output.include?('$')

print_good('Successfully disclosed /etc/shadow')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The post/linux/gather/hashdump has an #unshadow method that it uses to log the the data to the database. It would be really beneficial to follow the precedence set by that module for how /etc/shadow should be handled. Feel free to copy paste that method into this module (include a note of where you took it from) since the other instances of #unshadow in the BSD and Solaris modules are slightly different.

Use that #unshadow method to log the creds to the database, and then use the same #store_loot arguments as applicable. (shadow.tx and Linux Password Shadow File, etc.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment wasn't addressed. The stored file in loot is still shadow.txt not shadow.tx as well as the stored creds aren't consistent when they should be since the result of a successful module run is leaking the shadow file.

Your module outputs:

msf post(linux/gather/cve_2026_46333_chage) > run
[*] Detected kernel version: 6.8.0-111-generic
[!] ptrace_scope=1 may reduce exploit reliability
[*] Writing exploit source to /tmp/.qfMLXA.c
[*] Compiling exploit payload
[*] Launching race with 500 attempts
[+] Successfully disclosed /etc/shadow
[+] Logged shadow credential for systemd-network
[+] Logged shadow credential for systemd-timesync
[+] Logged shadow credential for systemd-resolve
[+] Logged shadow credential for polkitd
[+] Logged shadow credential for fwupd-refresh
[+] Logged shadow credential for smcintyre
[+] Loot stored at: /home/smcintyre/.msf4/loot/20260528135345_prtesting_192.168.159.132_linux.shadow_891758.txt

[+] Stole fd 11 -> /etc/shadow
root:*:19836:0:99999:7:::
daemon:*:19836:0:99999:7:::
bin:*:19836:0:99999:7:::
sys:*:19836:0:99999:7:::
sync:*:19836:0:99999:7:::
games:*:19836:0:99999:7:::
man:*:19836:0:99999:7:::
lp:*:19836:0:99999:7:::
mail:*:19836:0:99999:7:::
news:*:19836:0:99999:7:::
uucp:*:19836:0:99999:7:::
proxy:*:19836:0:99999:7:::
www-data:*:19836:0:99999:7:::
backup:*:19836:0:99999:7:::
list:*:19836:0:99999:7:::
irc:*:19836:0:99999:7:::
_apt:*:19836:0:99999:7:::
nobody:*:19836:0:99999:7:::
systemd-network:!*:19836::::::
systemd-timesync:!*:19836::::::
dhcpcd:!:19836::::::
messagebus:!:19836::::::
systemd-resolve:!*:19836::::::
pollinate:!:19836::::::
polkitd:!*:19836::::::
syslog:!:19836::::::
uuidd:!:19836::::::
tcpdump:!:19836::::::
tss:!:19836::::::
landscape:!:19836::::::
fwupd-refresh:!*:19836::::::
usbmux:!:20343::::::
sshd:!:20343::::::
smcintyre:$6$[REDACTED]:20343:0:99999:7:::
[*] Post module execution completed
msf post(linux/gather/cve_2026_46333_chage) > loot

Loot
====

host             service  type          name        content     info                        path
----             -------  ----          ----        -------     ----                        ----
192.168.159.132           linux.shadow  shadow.txt  text/plain  Linux Password Shadow File  /home/smcintyre/.msf4/loot/20260528135345_prtesting_192.168.159.132_linux.shadow_891758.txt

msf post(linux/gather/cve_2026_46333_chage) > creds
Credentials
===========

id   host  origin           service  public            private                                                                                   realm  private_type        JtR Format      cracked_password
--   ----  ------           -------  ------            -------                                                                                   -----  ------------        ----------      ----------------
368        192.168.159.132           systemd-network   !*                                                                                               Nonreplayable hash  des,bsdi,crypt
369        192.168.159.132           systemd-timesync  !*                                                                                               Nonreplayable hash  des,bsdi,crypt
370        192.168.159.132           systemd-resolve   !*                                                                                               Nonreplayable hash  des,bsdi,crypt
371        192.168.159.132           polkitd           !*                                                                                               Nonreplayable hash  des,bsdi,crypt
372        192.168.159.132           fwupd-refresh     !*                                                                                               Nonreplayable hash  des,bsdi,crypt
373        192.168.159.132           smcintyre         $6$  (TRUNCATED)         Nonreplayable hash  sha512,crypt

msf post(linux/gather/cve_2026_46333_chage) > 

The existing hashdump module outputs:

msf post(linux/gather/hashdump) > run
[+] polkitd:!*:991:991:User for polkitd:/:/usr/sbin/nologin
[+] smcintyre:$6$[REDACTED]:1000:1000:Spencer McIntyre:/home/smcintyre:/bin/bash
[+] Unshadowed Password File: /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.hashes_427649.txt
[*] Post module execution completed
msf post(linux/gather/hashdump) > loot

Loot
====

host             service  type                  name                   content     info                            path
----             -------  ----                  ----                   -------     ----                            ----
192.168.159.132           linux.passwd          passwd.tx              text/plain  Linux Passwd File               /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.passwd_006521.txt
192.168.159.132           linux.shadow          shadow.tx              text/plain  Linux Password Shadow File      /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.shadow_949521.txt
192.168.159.132           linux.passwd.history  opasswd.tx             text/plain  Linux Passwd History File       /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.passwd.his_816246.txt
192.168.159.132           linux.hashes          unshadowed_passwd.pwd  text/plain  Linux Unshadowed Password File  /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.hashes_427649.txt

msf post(linux/gather/hashdump) > creds
Credentials
===========

id   host  origin           service  public     private                                                                                   realm  private_type        JtR Format      cracked_password
--   ----  ------           -------  ------     -------                                                                                   -----  ------------        ----------      ----------------
376        192.168.159.132           polkitd    !*                                                                                               Nonreplayable hash  des,bsdi,crypt
377        192.168.159.132           smcintyre  $6$ (TRUNCATED)         Nonreplayable hash  sha512,crypt

msf post(linux/gather/hashdump) > 

I'm not terribly worried about what your module prints, that's up to you but what is reported and stored in the database should be the exact same given that these two modules are processing the exact same file. Without normalization on these fields in the database, there's not much point in storing the data because we can't act on it.

What I'd recommend to ensure this consistency is:

  • Extract the #unshadow method from the hashdump module
  • Extract the reporting into a method, something like report_linux_hashdump(etc_passwd, etc_shadow) and include this without this which seems unrelated
  • Keep that opassword reporting in the original hashdump module
  • Update both your module and the new hashdump module to use your new report_linux_hashdump method
  • Update your module to read /etc/passwd to pass to report_linux_hashdump, it's world readable anyways so you shouldn't need to re-run the exploit
  • If you're feeding this into AI, have it write specs for the new mixin too

@github-project-automation github-project-automation Bot moved this from In Progress to Waiting on Contributor in Metasploit Kanban May 18, 2026
@bhaskarbhar
Copy link
Copy Markdown
Contributor Author

@smcintyre-r7 Please review the changes

@gardnerapp
Copy link
Copy Markdown
Contributor

gardnerapp commented May 21, 2026

Here it is sorry for the inconvenience, I am new to reviews. Let me know of any issues thank you.

@bhaskarbhar
Copy link
Copy Markdown
Contributor Author

bhaskarbhar commented May 21, 2026

Here it is sorry for the inconvenience, I am new to reviews. Let me know of any issues thank you.

Hi @gardnerapp I reviewed your PR. The code that you were working on is older. You can view the new changes in the recent commit of the branch.
My suggestion, if this PR gets merged, create another issue labeling suggestion-feature and do the changes because I cant directly merge it also the code that you worked on is older.

Comment on lines +77 to +93
unless command_exists?('gcc')
return Exploit::CheckCode::Safe(
'gcc is missing; exploit cannot compile'
)
end

unless file?('/usr/bin/chage')
return Exploit::CheckCode::Safe(
'chage target binary not present'
)
end

unless setuid?('/usr/bin/chage') || stat('/usr/bin/chage').setgid?
return Exploit::CheckCode::Safe(
'chage does not appear to have SGID/SUID permissions'
)
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These each need to be changed from "Safe" to "Unknown". We need to differentiate between "we can't exploit this" and "the target isn't vulnerable". All three of these cases are checking for our ability to exploit this target. The vulnerability in how the kernel handles ptrace is independent of these conditions.


if output.include?('$')

print_good('Successfully disclosed /etc/shadow')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment wasn't addressed. The stored file in loot is still shadow.txt not shadow.tx as well as the stored creds aren't consistent when they should be since the result of a successful module run is leaking the shadow file.

Your module outputs:

msf post(linux/gather/cve_2026_46333_chage) > run
[*] Detected kernel version: 6.8.0-111-generic
[!] ptrace_scope=1 may reduce exploit reliability
[*] Writing exploit source to /tmp/.qfMLXA.c
[*] Compiling exploit payload
[*] Launching race with 500 attempts
[+] Successfully disclosed /etc/shadow
[+] Logged shadow credential for systemd-network
[+] Logged shadow credential for systemd-timesync
[+] Logged shadow credential for systemd-resolve
[+] Logged shadow credential for polkitd
[+] Logged shadow credential for fwupd-refresh
[+] Logged shadow credential for smcintyre
[+] Loot stored at: /home/smcintyre/.msf4/loot/20260528135345_prtesting_192.168.159.132_linux.shadow_891758.txt

[+] Stole fd 11 -> /etc/shadow
root:*:19836:0:99999:7:::
daemon:*:19836:0:99999:7:::
bin:*:19836:0:99999:7:::
sys:*:19836:0:99999:7:::
sync:*:19836:0:99999:7:::
games:*:19836:0:99999:7:::
man:*:19836:0:99999:7:::
lp:*:19836:0:99999:7:::
mail:*:19836:0:99999:7:::
news:*:19836:0:99999:7:::
uucp:*:19836:0:99999:7:::
proxy:*:19836:0:99999:7:::
www-data:*:19836:0:99999:7:::
backup:*:19836:0:99999:7:::
list:*:19836:0:99999:7:::
irc:*:19836:0:99999:7:::
_apt:*:19836:0:99999:7:::
nobody:*:19836:0:99999:7:::
systemd-network:!*:19836::::::
systemd-timesync:!*:19836::::::
dhcpcd:!:19836::::::
messagebus:!:19836::::::
systemd-resolve:!*:19836::::::
pollinate:!:19836::::::
polkitd:!*:19836::::::
syslog:!:19836::::::
uuidd:!:19836::::::
tcpdump:!:19836::::::
tss:!:19836::::::
landscape:!:19836::::::
fwupd-refresh:!*:19836::::::
usbmux:!:20343::::::
sshd:!:20343::::::
smcintyre:$6$[REDACTED]:20343:0:99999:7:::
[*] Post module execution completed
msf post(linux/gather/cve_2026_46333_chage) > loot

Loot
====

host             service  type          name        content     info                        path
----             -------  ----          ----        -------     ----                        ----
192.168.159.132           linux.shadow  shadow.txt  text/plain  Linux Password Shadow File  /home/smcintyre/.msf4/loot/20260528135345_prtesting_192.168.159.132_linux.shadow_891758.txt

msf post(linux/gather/cve_2026_46333_chage) > creds
Credentials
===========

id   host  origin           service  public            private                                                                                   realm  private_type        JtR Format      cracked_password
--   ----  ------           -------  ------            -------                                                                                   -----  ------------        ----------      ----------------
368        192.168.159.132           systemd-network   !*                                                                                               Nonreplayable hash  des,bsdi,crypt
369        192.168.159.132           systemd-timesync  !*                                                                                               Nonreplayable hash  des,bsdi,crypt
370        192.168.159.132           systemd-resolve   !*                                                                                               Nonreplayable hash  des,bsdi,crypt
371        192.168.159.132           polkitd           !*                                                                                               Nonreplayable hash  des,bsdi,crypt
372        192.168.159.132           fwupd-refresh     !*                                                                                               Nonreplayable hash  des,bsdi,crypt
373        192.168.159.132           smcintyre         $6$  (TRUNCATED)         Nonreplayable hash  sha512,crypt

msf post(linux/gather/cve_2026_46333_chage) > 

The existing hashdump module outputs:

msf post(linux/gather/hashdump) > run
[+] polkitd:!*:991:991:User for polkitd:/:/usr/sbin/nologin
[+] smcintyre:$6$[REDACTED]:1000:1000:Spencer McIntyre:/home/smcintyre:/bin/bash
[+] Unshadowed Password File: /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.hashes_427649.txt
[*] Post module execution completed
msf post(linux/gather/hashdump) > loot

Loot
====

host             service  type                  name                   content     info                            path
----             -------  ----                  ----                   -------     ----                            ----
192.168.159.132           linux.passwd          passwd.tx              text/plain  Linux Passwd File               /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.passwd_006521.txt
192.168.159.132           linux.shadow          shadow.tx              text/plain  Linux Password Shadow File      /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.shadow_949521.txt
192.168.159.132           linux.passwd.history  opasswd.tx             text/plain  Linux Passwd History File       /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.passwd.his_816246.txt
192.168.159.132           linux.hashes          unshadowed_passwd.pwd  text/plain  Linux Unshadowed Password File  /home/smcintyre/.msf4/loot/20260528135517_mastertesting_192.168.159.132_linux.hashes_427649.txt

msf post(linux/gather/hashdump) > creds
Credentials
===========

id   host  origin           service  public     private                                                                                   realm  private_type        JtR Format      cracked_password
--   ----  ------           -------  ------     -------                                                                                   -----  ------------        ----------      ----------------
376        192.168.159.132           polkitd    !*                                                                                               Nonreplayable hash  des,bsdi,crypt
377        192.168.159.132           smcintyre  $6$ (TRUNCATED)         Nonreplayable hash  sha512,crypt

msf post(linux/gather/hashdump) > 

I'm not terribly worried about what your module prints, that's up to you but what is reported and stored in the database should be the exact same given that these two modules are processing the exact same file. Without normalization on these fields in the database, there's not much point in storing the data because we can't act on it.

What I'd recommend to ensure this consistency is:

  • Extract the #unshadow method from the hashdump module
  • Extract the reporting into a method, something like report_linux_hashdump(etc_passwd, etc_shadow) and include this without this which seems unrelated
  • Keep that opassword reporting in the original hashdump module
  • Update both your module and the new hashdump module to use your new report_linux_hashdump method
  • Update your module to read /etc/passwd to pass to report_linux_hashdump, it's world readable anyways so you shouldn't need to re-run the exploit
  • If you're feeding this into AI, have it write specs for the new mixin too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Waiting on Contributor

Development

Successfully merging this pull request may close these issues.

CVE-2026-46333(ssh-keysign-pwn / ptrace Exit-Race)

5 participants