diff --git a/lib/msf/core/post/linux/kernel.rb b/lib/msf/core/post/linux/kernel.rb index 19b2dd8b4ff2..6f0bfb4ce1b4 100644 --- a/lib/msf/core/post/linux/kernel.rb +++ b/lib/msf/core/post/linux/kernel.rb @@ -1,331 +1,338 @@ # -*- coding: binary -*- module Msf -class Post -module Linux -module Kernel - include ::Msf::Post::Common - include Msf::Post::File - # - # Returns uname output - # - # @return [String] - # - def uname(opts='-a') - cmd_exec("uname #{opts}").to_s.strip - rescue - raise "Failed to run uname #{opts}" - end - - # - # Returns the kernel release - # - # @return [String] - # - def kernel_release - uname('-r') - end - - # - # Returns the kernel version - # - # @return [String] - # - def kernel_version - uname('-v') - end - - # - # Returns the kernel name - # - # @return [String] - # - def kernel_name - uname('-s') - end - - # - # Returns the kernel hardware - # - # @return [String] - # - def kernel_hardware - uname('-m') - end - - # - # Returns the kernel hardware architecture - # Based on values from https://en.wikipedia.org/wiki/Uname - # - # @return [String] - # - def kernel_arch - arch = kernel_hardware - return ARCH_X64 if arch == 'x86_64' || arch == 'amd64' - return ARCH_AARCH64 if arch == 'aarch64' || arch == 'arm64' - return ARCH_ARMLE if arch.start_with?'arm' - return ARCH_X86 if arch.end_with?'86' - return ARCH_PPC if arch == 'ppc' - return ARCH_PPC64 if arch == 'ppc64' - return ARCH_PPC64LE if arch == 'ppc64le' - return ARCH_MIPS if arch == 'mips' - return ARCH_MIPS64 if arch == 'mips64' - return ARCH_SPARC if arch == 'sparc' - return ARCH_RISCV32LE if arch == 'riscv32' - return ARCH_RISCV64LE if arch == 'riscv64' - return ARCH_LOONGARCH64 if arch == 'loongarch64' - arch - end - - # - # Returns the kernel boot config - # - # @return [Array] - # - def kernel_config - release = kernel_release - output = read_file("/boot/config-#{release}").to_s.strip - return if output.empty? - config = output.split("\n").map(&:strip).reject(&:empty?).reject {|i| i.start_with? '#'} - config - rescue - raise 'Could not retrieve kernel config' - end - - # - # Returns the kernel modules - # - # @return [Array] - # - def kernel_modules - read_file('/proc/modules').to_s.scan(/^[^ ]+/) - rescue - raise 'Could not determine kernel modules' - end - - # - # Returns a list of CPU flags - # - # @return [Array] - # - def cpu_flags - cpuinfo = read_file('/proc/cpuinfo').to_s - - return unless cpuinfo.include? 'flags' - - cpuinfo.scan(/^flags\s*:(.*)$/).flatten.join(' ').split(/\s/).map(&:strip).reject(&:empty?).uniq - rescue - raise'Could not retrieve CPU flags' - end - - # - # Returns true if kernel and hardware supports Supervisor Mode Access Prevention (SMAP), false if not. - # - # @return [Boolean] - # - def smap_enabled? - cpu_flags.include? 'smap' - rescue - raise 'Could not determine SMAP status' - end - - # - # Returns true if kernel and hardware supports Supervisor Mode Execution Protection (SMEP), false if not. - # - # @return [Boolean] - # - def smep_enabled? - cpu_flags.include? 'smep' - rescue - raise 'Could not determine SMEP status' - end - - # - # Returns true if Kernel Address Isolation (KAISER) is enabled - # - # @return [Boolean] - # - def kaiser_enabled? - cpu_flags.include? 'kaiser' - rescue - raise 'Could not determine KAISER status' - end - - # - # Returns true if Kernel Page-Table Isolation (KPTI) is enabled, false if not. - # - # @return [Boolean] - # - def kpti_enabled? - cpu_flags.include? 'pti' - rescue - raise 'Could not determine KPTI status' - end - - # - # Returns true if user namespaces are enabled, false if not. - # - # @return [Boolean] - # - def userns_enabled? - return false if read_file('/proc/sys/user/max_user_namespaces').to_s.strip.eql? '0' - return false if read_file('/proc/sys/kernel/unprivileged_userns_clone').to_s.strip.eql? '0' - true - rescue - raise 'Could not determine userns status' - end - - # - # Returns true if Address Space Layout Randomization (ASLR) is enabled - # - # @return [Boolean] - # - def aslr_enabled? - aslr = read_file('/proc/sys/kernel/randomize_va_space').to_s.strip - (aslr.eql?('1') || aslr.eql?('2')) - rescue - raise 'Could not determine ASLR status' - end - - # - # Returns true if Exec-Shield is enabled - # - # @return [Boolean] - # - def exec_shield_enabled? - exec_shield = read_file('/proc/sys/kernel/exec-shield').to_s.strip - (exec_shield.eql?('1') || exec_shield.eql?('2')) - rescue - raise 'Could not determine exec-shield status' - end - - # - # Returns true if unprivileged bpf is disabled - # - # @return [Boolean] - # - def unprivileged_bpf_disabled? - unprivileged_bpf_disabled = read_file('/proc/sys/kernel/unprivileged_bpf_disabled').to_s.strip - return (unprivileged_bpf_disabled == '1' || unprivileged_bpf_disabled == '2') - rescue - raise 'Could not determine kernel.unprivileged_bpf_disabled status' - end - - # - # Returns true if kernel pointer restriction is enabled - # - # @return [Boolean] - # - def kptr_restrict? - read_file('/proc/sys/kernel/kptr_restrict').to_s.strip.eql? '1' - rescue - raise 'Could not determine kernel.kptr_restrict status' - end - - # - # Returns true if dmesg restriction is enabled - # - # @return [Boolean] - # - def dmesg_restrict? - read_file('/proc/sys/kernel/dmesg_restrict').to_s.strip.eql? '1' - rescue - raise 'Could not determine kernel.dmesg_restrict status' - end - - # - # Returns mmap minimum address - # - # @return [Integer] - # - def mmap_min_addr - mmap_min_addr = read_file('/proc/sys/vm/mmap_min_addr').to_s.strip - return 0 unless mmap_min_addr =~ /\A\d+\z/ - mmap_min_addr - rescue - raise 'Could not determine system mmap_min_addr' - end - - # - # Returns true if Linux Kernel Runtime Guard (LKRG) kernel module is installed - # - def lkrg_installed? - directory?('/proc/sys/lkrg') - rescue - raise 'Could not determine LKRG status' - end - - # - # Returns true if grsecurity is installed - # - def grsec_installed? - File.exists?('/dev/grsec') && File.chardev?('/dev/grsec') - rescue - raise 'Could not determine grsecurity status' - end - - # - # Returns true if PaX is installed - # - def pax_installed? - read_file('/proc/self/status').to_s.include? 'PaX:' - rescue - raise 'Could not determine PaX status' - end - - # - # Returns true if SELinux is installed - # - # @return [Boolean] - # - def selinux_installed? - cmd_exec('id').to_s.include? 'context=' - rescue - raise 'Could not determine SELinux status' - end - - # - # Returns true if SELinux is in enforcing mode - # - # @return [Boolean] - # - def selinux_enforcing? - return false unless selinux_installed? - - sestatus = cmd_exec('/usr/sbin/sestatus').to_s.strip - raise unless sestatus.include?('SELinux') - - return true if sestatus =~ /Current mode:\s*enforcing/ - false - rescue - raise 'Could not determine SELinux status' - end - - # - # Returns true if Yama is installed - # - # @return [Boolean] - # - def yama_installed? - ptrace_scope = read_file('/proc/sys/kernel/yama/ptrace_scope').to_s.strip - return true if ptrace_scope =~ /\A\d\z/ - false - rescue - raise 'Could not determine Yama status' - end - - # - # Returns true if Yama is enabled - # - # @return [Boolean] - # - def yama_enabled? - return false unless yama_installed? - !read_file('/proc/sys/kernel/yama/ptrace_scope').to_s.strip.eql? '0' - rescue - raise 'Could not determine Yama status' - end -end # Kernel -end # Linux -end # Post + class Post + module Linux + module Kernel + include ::Msf::Post::Common + include Msf::Post::File + # + # Returns uname output + # + # @return [String] + # + def uname(opts = '-a') + cmd_exec("uname #{opts}").to_s.strip + rescue StandardError + raise "Failed to run uname #{opts}" + end + + # + # Returns the kernel release + # + # @return [String] + # + def kernel_release + uname('-r') + end + + # + # Returns the kernel version + # + # @return [String] + # + def kernel_version + uname('-v') + end + + # + # Returns the kernel name + # + # @return [String] + # + def kernel_name + uname('-s') + end + + # + # Returns the kernel hardware + # + # @return [String] + # + def kernel_hardware + uname('-m') + end + + # + # Returns the kernel hardware architecture + # Based on values from https://en.wikipedia.org/wiki/Uname + # + # @return [String] + # + def kernel_arch + arch = kernel_hardware + return ARCH_X64 if arch == 'x86_64' || arch == 'amd64' + return ARCH_AARCH64 if arch == 'aarch64' || arch == 'arm64' + return ARCH_ARMLE if arch.start_with? 'arm' + return ARCH_X86 if arch.end_with? '86' + return ARCH_PPC if arch == 'ppc' + return ARCH_PPC64 if arch == 'ppc64' + return ARCH_PPC64LE if arch == 'ppc64le' + return ARCH_MIPS if arch == 'mips' + return ARCH_MIPS64 if arch == 'mips64' + return ARCH_SPARC if arch == 'sparc' + return ARCH_RISCV32LE if arch == 'riscv32' + return ARCH_RISCV64LE if arch == 'riscv64' + return ARCH_LOONGARCH64 if arch == 'loongarch64' + + arch + end + + # + # Returns the kernel boot config + # + # @return [Array] + # + def kernel_config + release = kernel_release + output = read_file("/boot/config-#{release}").to_s.strip + return if output.empty? + + config = output.split("\n").map(&:strip).reject(&:empty?).reject { |i| i.start_with? '#' } + config + rescue StandardError + raise 'Could not retrieve kernel config' + end + + # + # Returns the kernel modules + # + # @return [Array] + # + def kernel_modules + read_file('/proc/modules').to_s.scan(/^[^ ]+/) + rescue StandardError + raise 'Could not determine kernel modules' + end + + # + # Returns a list of CPU flags + # + # @return [Array] + # + def cpu_flags + cpuinfo = read_file('/proc/cpuinfo').to_s + + return unless cpuinfo.include? 'flags' + + cpuinfo.scan(/^flags\s*:(.*)$/).flatten.join(' ').split(/\s/).map(&:strip).reject(&:empty?).uniq + rescue StandardError + raise 'Could not retrieve CPU flags' + end + + # + # Returns true if kernel and hardware supports Supervisor Mode Access Prevention (SMAP), false if not. + # + # @return [Boolean] + # + def smap_enabled? + cpu_flags.include? 'smap' + rescue StandardError + raise 'Could not determine SMAP status' + end + + # + # Returns true if kernel and hardware supports Supervisor Mode Execution Protection (SMEP), false if not. + # + # @return [Boolean] + # + def smep_enabled? + cpu_flags.include? 'smep' + rescue StandardError + raise 'Could not determine SMEP status' + end + + # + # Returns true if Kernel Address Isolation (KAISER) is enabled + # + # @return [Boolean] + # + def kaiser_enabled? + cpu_flags.include? 'kaiser' + rescue StandardError + raise 'Could not determine KAISER status' + end + + # + # Returns true if Kernel Page-Table Isolation (KPTI) is enabled, false if not. + # + # @return [Boolean] + # + def kpti_enabled? + cpu_flags.include? 'pti' + rescue StandardError + raise 'Could not determine KPTI status' + end + + # + # Returns true if user namespaces are enabled, false if not. + # + # @return [Boolean] + # + def userns_enabled? + return false if read_file('/proc/sys/user/max_user_namespaces').to_s.strip.eql? '0' + return false if read_file('/proc/sys/kernel/unprivileged_userns_clone').to_s.strip.eql? '0' + + true + rescue StandardError + raise 'Could not determine userns status' + end + + # + # Returns true if Address Space Layout Randomization (ASLR) is enabled + # + # @return [Boolean] + # + def aslr_enabled? + aslr = read_file('/proc/sys/kernel/randomize_va_space').to_s.strip + aslr.eql?('1') || aslr.eql?('2') + rescue StandardError + raise 'Could not determine ASLR status' + end + + # + # Returns true if Exec-Shield is enabled + # + # @return [Boolean] + # + def exec_shield_enabled? + exec_shield = read_file('/proc/sys/kernel/exec-shield').to_s.strip + exec_shield.eql?('1') || exec_shield.eql?('2') + rescue StandardError + raise 'Could not determine exec-shield status' + end + + # + # Returns true if unprivileged bpf is disabled + # + # @return [Boolean] + # + def unprivileged_bpf_disabled? + unprivileged_bpf_disabled = read_file('/proc/sys/kernel/unprivileged_bpf_disabled').to_s.strip + return unprivileged_bpf_disabled == '1' || unprivileged_bpf_disabled == '2' + rescue StandardError + raise 'Could not determine kernel.unprivileged_bpf_disabled status' + end + + # + # Returns true if kernel pointer restriction is enabled + # + # @return [Boolean] + # + def kptr_restrict? + read_file('/proc/sys/kernel/kptr_restrict').to_s.strip.eql? '1' + rescue StandardError + raise 'Could not determine kernel.kptr_restrict status' + end + + # + # Returns true if dmesg restriction is enabled + # + # @return [Boolean] + # + def dmesg_restrict? + read_file('/proc/sys/kernel/dmesg_restrict').to_s.strip.eql? '1' + rescue StandardError + raise 'Could not determine kernel.dmesg_restrict status' + end + + # + # Returns mmap minimum address + # + # @return [Integer] + # + def mmap_min_addr + mmap_min_addr = read_file('/proc/sys/vm/mmap_min_addr').to_s.strip + return 0 unless mmap_min_addr =~ /\A\d+\z/ + + mmap_min_addr + rescue StandardError + raise 'Could not determine system mmap_min_addr' + end + + # + # Returns true if Linux Kernel Runtime Guard (LKRG) kernel module is installed + # + def lkrg_installed? + directory?('/proc/sys/lkrg') + rescue StandardError + raise 'Could not determine LKRG status' + end + + # + # Returns true if grsecurity is installed + # + def grsec_installed? + cmd_exec('test -c /dev/grsec && echo true').to_s.strip.include? 'true' + rescue StandardError + raise 'Could not determine grsecurity status' + end + + # + # Returns true if PaX is installed + # + def pax_installed? + read_file('/proc/self/status').to_s.include? 'PaX:' + rescue StandardError + raise 'Could not determine PaX status' + end + + # + # Returns true if SELinux is installed + # + # @return [Boolean] + # + def selinux_installed? + cmd_exec('id').to_s.include? 'context=' + rescue StandardError + raise 'Could not determine SELinux status' + end + + # + # Returns true if SELinux is in enforcing mode + # + # @return [Boolean] + # + def selinux_enforcing? + return false unless selinux_installed? + + sestatus = cmd_exec('/usr/sbin/sestatus').to_s.strip + raise unless sestatus.include?('SELinux') + + return true if sestatus =~ /Current mode:\s*enforcing/ + + false + rescue StandardError + raise 'Could not determine SELinux status' + end + + # + # Returns true if Yama is installed + # + # @return [Boolean] + # + def yama_installed? + ptrace_scope = read_file('/proc/sys/kernel/yama/ptrace_scope').to_s.strip + return true if ptrace_scope =~ /\A\d\z/ + + false + rescue StandardError + raise 'Could not determine Yama status' + end + + # + # Returns true if Yama is enabled + # + # @return [Boolean] + # + def yama_enabled? + return false unless yama_installed? + + !read_file('/proc/sys/kernel/yama/ptrace_scope').to_s.strip.eql? '0' + rescue StandardError + raise 'Could not determine Yama status' + end + end # Kernel + end # Linux + end # Post end # Msf diff --git a/modules/exploits/example_linux_priv_esc.rb b/modules/exploits/example_linux_priv_esc.rb index d196040464e1..2818ce7df085 100644 --- a/modules/exploits/example_linux_priv_esc.rb +++ b/modules/exploits/example_linux_priv_esc.rb @@ -97,18 +97,24 @@ def base_dir def check # Check the kernel version to see if its in a vulnerable range + # we guard this because some distros have funky kernel versions https://github.com/rapid7/metasploit-framework/issues/19812 release = kernel_release - if Rex::Version.new(release.split('-').first) > Rex::Version.new('4.14.11') || - Rex::Version.new(release.split('-').first) < Rex::Version.new('4.0') - return CheckCode::Safe("Kernel version #{release} is not vulnerable") + begin + if Rex::Version.new(release.split('-').first) > Rex::Version.new('4.14.11') || + Rex::Version.new(release.split('-').first) < Rex::Version.new('4.0') + return CheckCode::Safe("Kernel version #{release} is not vulnerable") + end + rescue ArgumentError => e + return CheckCode::Safe("Error determining or processing kernel release (#{release}) into known format: #{e}") end vprint_good "Kernel version #{release} appears to be vulnerable" # Check the app is installed and the version, debian based example package = cmd_exec('dpkg -l example | grep \'^ii\'') if package&.include?('1:2015.3.14AR.1-1build1') - CheckCode::Appears("Vulnerable app version #{package} detected") + return CheckCode::Appears("Vulnerable app version #{package} detected") end + CheckCode::Safe("app #{package} is not vulnerable") end diff --git a/modules/exploits/linux/local/docker_cgroup_escape.rb b/modules/exploits/linux/local/docker_cgroup_escape.rb index a54188a6bb47..f8690dcd6041 100644 --- a/modules/exploits/linux/local/docker_cgroup_escape.rb +++ b/modules/exploits/linux/local/docker_cgroup_escape.rb @@ -83,13 +83,17 @@ def check print_status('Unable to determine host OS, this check method is unlikely to be accurate if the host isn\'t Ubuntu') release = kernel_release # https://people.canonical.com/~ubuntu-security/cve/2022/CVE-2022-0492 - release_short = Rex::Version.new(release.split('-').first) - release_long = Rex::Version.new(release.split('-')[0..1].join('-')) - if release_short >= Rex::Version.new('5.13.0') && release_long < Rex::Version.new('5.13.0-37.42') || # Ubuntu 21.10 - release_short >= Rex::Version.new('5.4.0') && release_long < Rex::Version.new('5.4.0-105.119') || # Ubuntu 20.04 LTS - release_short >= Rex::Version.new('4.15.0') && release_long < Rex::Version.new('4.15.0-173.182') || # Ubuntu 18.04 LTS - release_short >= Rex::Version.new('4.4.0') && release_long < Rex::Version.new('4.4.0-222.255') # Ubuntu 16.04 ESM - return CheckCode::Vulnerable("IF host OS is Ubuntu, kernel version #{release} is vulnerable") + begin + release_short = Rex::Version.new(release.split('-').first) + release_long = Rex::Version.new(release.split('-')[0..1].join('-')) + if release_short >= Rex::Version.new('5.13.0') && release_long < Rex::Version.new('5.13.0-37.42') || # Ubuntu 21.10 + release_short >= Rex::Version.new('5.4.0') && release_long < Rex::Version.new('5.4.0-105.119') || # Ubuntu 20.04 LTS + release_short >= Rex::Version.new('4.15.0') && release_long < Rex::Version.new('4.15.0-173.182') || # Ubuntu 18.04 LTS + release_short >= Rex::Version.new('4.4.0') && release_long < Rex::Version.new('4.4.0-222.255') # Ubuntu 16.04 ESM + return CheckCode::Vulnerable("IF host OS is Ubuntu, kernel version #{release} is vulnerable") + end + rescue ArgumentError => e + return CheckCode::Safe("Error determining or processing kernel release (#{release}) into known format: #{e}") end CheckCode::Safe("Kernel version #{release} may not be vulnerable depending on the host OS") diff --git a/modules/exploits/linux/local/tomcat_ubuntu_log_init_priv_esc.rb b/modules/exploits/linux/local/tomcat_ubuntu_log_init_priv_esc.rb index 335a44387007..4786b668f1c8 100644 --- a/modules/exploits/linux/local/tomcat_ubuntu_log_init_priv_esc.rb +++ b/modules/exploits/linux/local/tomcat_ubuntu_log_init_priv_esc.rb @@ -84,7 +84,7 @@ def catalina def check package = cmd_exec('dpkg -l tomcat[6-8] | grep \'^i\'') - if package.nil? || package.empty? + if package.nil? || package.empty? || package.include?('not found') # catch dpkg command not found return CheckCode::Safe('Unable to execute command to determine installed pacakges') end @@ -93,7 +93,11 @@ def check # 0 is ii for installed # 1 is tomcat# for package name # 2 is version number - package = Rex::Version.new(package[2]) + begin + package = Rex::Version.new(package[2]) + rescue ArgumentError => e + return CheckCode::Safe("Error processing Tomcat version (#{package[2]}) into known format: #{e}") + end if (package.to_s.start_with?('8') && package < Rex::Version.new('8.0.32-1ubuntu1.2')) || (package.to_s.start_with?('7') && package < Rex::Version.new('7.0.52-1ubuntu0.7')) || diff --git a/modules/exploits/linux/local/vmwgfx_fd_priv_esc.rb b/modules/exploits/linux/local/vmwgfx_fd_priv_esc.rb index 949d0ddadb6a..3086b643c539 100644 --- a/modules/exploits/linux/local/vmwgfx_fd_priv_esc.rb +++ b/modules/exploits/linux/local/vmwgfx_fd_priv_esc.rb @@ -74,9 +74,13 @@ def base_dir def check # Check the kernel version to see if its in a vulnerable range release = kernel_release - unless Rex::Version.new(release) > Rex::Version.new('4.14-rc1') && - Rex::Version.new(release) < Rex::Version.new('5.17-rc1') - return CheckCode::Safe("Kernel version #{release} is not vulnerable") + begin + unless Rex::Version.new(release) > Rex::Version.new('4.14-rc1') && + Rex::Version.new(release) < Rex::Version.new('5.17-rc1') + return CheckCode::Safe("Kernel version #{release} is not vulnerable") + end + rescue ArgumentError => e + return CheckCode::Safe("Error determining or processing kernel release (#{release}) into known format: #{e}") end vprint_good "Kernel version #{release} appears to be vulnerable"