summaryrefslogtreecommitdiffstats
path: root/scripts/citrix-enum-apps.nse
blob: 35063aea518dbfa89ecb7b52fcec56e8504243b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"

description = [[
Extracts a list of published applications from the ICA Browser service.
]]

---
-- @usage sudo ./nmap -sU --script=citrix-enum-apps -p 1604 <host>
--
-- @output
-- PORT     STATE SERVICE
-- 1604/udp open  unknown
-- 1604/udp open  unknown
-- | citrix-enum-apps:
-- |   Notepad
-- |   iexplorer
-- |_  registry editor
--

-- Version 0.2

-- Created 11/24/2009 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 11/25/2009 - v0.2 - fixed multiple packet response bug

author = "Patrik Karlsson"

license = "Same as Nmap--See https://nmap.org/book/man-legal.html"

categories = {"discovery","safe"}


portrule = shortport.portnumber(1604, "udp")


-- process the response from the server
-- @param response string, complete server response
-- @return string row delimited with \n containing all published applications
function process_pa_response(response)

  local packet_len, pos = string.unpack("<I2", response)
  local app_name
  local pa_list = {}

  if packet_len < 40 then
    return
  end

  -- the list of published applications starts at offset 40
  local offset = 41

  while offset < packet_len do
    app_name, pos = string.unpack("z", response:sub(offset))
    offset = offset + pos - 1

    table.insert(pa_list, app_name)
  end

  return pa_list

end


action = function(host, port)

  local packet, counter
  local query = {}
  local pa_list = {}

  --
  -- Packets were intercepted from the Citrix Program Neighborhood client
  -- They are used to query a server for its list of servers
  --
  -- We're really not interested in the responses to the first two packets
  -- The third response contains the list of published applications
  -- I couldn't find any documentation on this protocol so I'm providing
  -- some brief information for the bits and bytes this script uses.
  --
  -- Spec. of response to query[2] that contains a list of published apps
  --
  -- offset size   content
  -- -------------------------
  -- 0      16-bit Length
  -- 12     32-bit Server IP (not used here)
  -- 30     8-bit  Last packet(1), More packets(0)
  -- 40     -      null-separated list of applications
  --
  query[0] = string.char(
    0x1e, 0x00, -- Length: 30
    0x01, 0x30, 0x02, 0xfd, 0xa8, 0xe3, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00
  )

  query[1] = string.char(
    0x20, 0x00, -- Length: 32
    0x01, 0x36, 0x02, 0xfd, 0xa8, 0xe3, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  )

  query[2] = string.char(
    0x2a, 0x00, -- Length: 42
    0x01, 0x32, 0x02, 0xfd, 0xa8, 0xe3, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x21, 0x00, 0x02, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  )

  counter = 0

  local socket = nmap.new_socket()
  socket:set_timeout(5000)

  local try = nmap.new_try(function() socket:close() end)

  try( socket:connect(host, port) )

  -- send the two first packets and never look back
  repeat
    try( socket:send(query[counter])  )
    packet = try(socket:receive())
    counter = counter + 1
  until (counter>#query)

  -- process the first response
  pa_list = process_pa_response( packet )

  --
  -- the byte at offset 31 in the response has a really magic function
  -- if it is set to zero (0) we have more response packets to process
  -- if it is set to one (1) we have arrived at the last packet of our journey
  --
  while packet:sub(31,31) ~= "\x01" do
    packet = try( socket:receive() )
    local tmp_table = process_pa_response( packet )

    for _,v in pairs(tmp_table) do
      table.insert(pa_list, v)
    end

  end

  -- set port to open
  if #pa_list>0 then
    nmap.set_port_state(host, port, "open")
  end

  socket:close()

  return stdnse.format_output(true, pa_list)

end