summaryrefslogtreecommitdiffstats
path: root/scripts/db2-das-info.nse
blob: 22b305f259413af7798836891da61ad3409c09a5 (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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"

description = [[
Connects to the IBM DB2 Administration Server (DAS) on TCP or UDP port 523 and
exports the server profile.  No authentication is required for this request.

The script will also set the port product and version if a version scan is
requested.
]]

-- rev 1.1 (2010-01-28)

---
-- @output
-- PORT    STATE SERVICE VERSION
-- 523/tcp open  ibm-db2 IBM DB2 Database Server 9.07.0
-- | db2-das-info: DB2 Administration Server Settings
-- | ;DB2 Server Database Access Profile
-- | ;Use BINARY file transfer
-- | ;Comment lines start with a ";"
-- | ;Other lines must be one of the following two types:
-- | ;Type A: [section_name]
-- | ;Type B: keyword=value
-- |
-- | [File_Description]
-- | Application=DB2/LINUX 9.7.0
-- | Platform=18
-- | File_Content=DB2 Server Definitions
-- | File_Type=CommonServer
-- | File_Format_Version=1.0
-- | DB2System=MYBIGDATABASESERVER
-- | ServerType=DB2LINUX
-- |
-- | [adminst>dasusr1]
-- | NodeType=1
-- | DB2Comm=TCPIP
-- | Authentication=SERVER
-- | HostName=MYBIGDATABASESERVER
-- | PortNumber=523
-- | IpAddress=127.0.1.1
-- |
-- | [inst>db2inst1]
-- | NodeType=1
-- | DB2Comm=TCPIP
-- | Authentication=SERVER
-- | HostName=MYBIGDATABASESERVER
-- | ServiceName=db2c_db2inst1
-- | PortNumber=50000
-- | IpAddress=127.0.1.1
-- | QuietMode=No
-- | TMDatabase=1ST_CONN
-- |
-- | [db>db2inst1:TOOLSDB]
-- | DBAlias=TOOLSDB
-- | DBName=TOOLSDB
-- | Drive=/home/db2inst1
-- | Dir_entry_type=INDIRECT
-- |_Authentication=NOTSPEC

author = {"Patrik Karlsson", "Tom Sellers"}

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

categories = {"safe", "discovery", "version"}


--- Research Notes:
--
-- Little documentation on the protocol used to communicate with the IBM DB2 Admin Server
-- service exists.  The packets and methods here were developed based on data captured
-- in the wild.  Interviews with knowledgeable individuals indicates that the following
-- information can be used to recreate the traffic.
--
-- Requirements:
--   IBM DB2 Administrative Server (DAS) version >= 7.x instance, typically on port 523 tcp or udp
--   IBM DB2 Control Center (Java application, workings on Linux, Windows, etc)
--
-- Steps to reproduce:
--   Ensure network connectivity from test host to DB2 DAS instance on 523
--   In the Control Center, right click on All Systems and click Add
--   Enter the DB2 server IP or hostname in the System Name field and click OK
--   Start packet capture
--   Under All Systems right click on your DB2 server, choose export profile, enter file location, click OK
--   Stop packet capture
--
--   Details on how to reproduce these steps with the CLI are welcome.

portrule = shortport.version_port_or_service({523}, nil,
                                            {"tcp","udp"},
                                            {"open", "open|filtered"})

--- Extracts the server profile from an already parsed db2 packet
--
-- This function assumes that the data contains the server profile and does
-- no attempts to verify whether it does or not. The response from the function
-- is simply a substring starting at offset 37.
--
-- @param data string containing the "info" section as parsed by parse_db2_packet
-- @return string containing the complete server profile
function extract_server_profile(data)

  local server_profile_offset = 37

  if server_profile_offset > data:len() then
    return
  end

  return data:sub(server_profile_offset)

end

--- Does *very* basic parsing of a DB2 packet
--
-- Due to the limited documentation of the protocol this function is guesswork
-- The section called info is essentially the data part of the db2das data response
-- The length of this section is found at offset 158 in the db2das.data section
--
--
-- @param packet table as returned from read_db2_packet
-- @return table with parsed data
function parse_db2_packet(packet)

  local info_length_offset = 158
  local info_offset = 160
  local version_offset = 97
  local response = {}

  if packet.header.data_len < info_length_offset then
    stdnse.debug1("packet too short to be DB2 response...")
    return
  end

  local len = string.unpack(">I2", packet.data, info_length_offset)
  response.version = string.unpack("z", packet.data, version_offset)
  response.info_length = len - 4
  response.info = packet.data:sub(info_offset, info_offset + response.info_length - (info_offset-info_length_offset))

  if(nmap.debugging() > 3)  then
    stdnse.debug1("version: %s", response.version)
    stdnse.debug1("info_length: %d", response.info_length)
    stdnse.debug1("response.info:len(): %d", response.info:len())
  end

  return response

end

--- Reads a DB2 packet from the socket
--
-- Due to the limited documentation of the protocol this function is guesswork
-- The first 41 bytes of the db2das response are considered to be the header
-- The bytes following the header are considered to be the data
--
-- Offset 38 of the header contains an integer with the length of the data section
-- The length of the data section can unfortunately be of either endianness
-- There's
--
-- @param socket connected to the server
-- @return table with header and data
function read_db2_packet(socket)

  local packet = {}
  local header_len = 41
  local total_len = 0
  local buf

  local DATA_LENGTH_OFFSET = 38
  local ENDIANESS_OFFSET = 23

  local catch = function()
    stdnse.debug1("ERROR communicating with DB2 server")
    socket:close()
  end

  local try = nmap.new_try(catch)
  packet.header = {}

  buf = try( socket:receive_bytes(header_len) )

  packet.header.raw = buf:sub(1, header_len)

  if packet.header.raw:sub(1, 10) == "\x00\x00\x00\x00\x44\x42\x32\x44\x41\x53" then

    stdnse.debug1("Got DB2DAS packet")

    local endian = string.unpack( "c2", packet.header.raw, ENDIANESS_OFFSET )

    if endian == "9z" then
      packet.header.data_len = string.unpack("<I4", packet.header.raw, DATA_LENGTH_OFFSET )
    else
      packet.header.data_len = string.unpack(">I4", packet.header.raw, DATA_LENGTH_OFFSET )
    end

    total_len = header_len + packet.header.data_len

    if(nmap.debugging() > 3) then
      stdnse.debug1("data_len: %d", packet.header.data_len)
      stdnse.debug1("buf_len: %d", buf:len())
      stdnse.debug1("total_len: %d", total_len)
    end

    -- do we have all data as specified by data_len?
    while total_len > buf:len() do
      -- if not read additional bytes
      if(nmap.debugging() > 3)  then
        stdnse.debug1("Reading %d additional bytes", total_len - buf:len())
      end
      local tmp = try( socket:receive_bytes( total_len - buf:len() ) )
      if(nmap.debugging() > 3)  then
        stdnse.debug1("Read %d bytes", tmp:len())
      end
      buf = buf .. tmp
    end

    packet.data = buf:sub(header_len + 1)

  else
    stdnse.debug1("Unknown packet, aborting ...")
    return
  end

  return packet

end

--- Sends a db2 packet table over the wire
--
-- @param socket already connected to the server
-- @param packet table as returned from <code>create_das_packet</code>
--
function send_db2_packet( socket, packet )

  local catch = function()
    stdnse.debug1("ERROR communicating with DB2 server")
    socket:close()
  end

  local try = nmap.new_try(catch)

  local buf = packet.header.raw .. packet.data

  try( socket:send(buf) )

end

--- Creates a db2 packet table using the magic byte and data
--
-- The function returns a db2 packet table:
-- packet.header - contains header specific values
-- packet.header.raw - contains the complete un-parsed header (string)
-- packet.header.data_len - contains the length of the data block
-- packet.data - contains the complete un-parsed data block (string)
--
-- @param magic byte containing a value of unknown function (could be type)
-- @param data string containing the db2 packet data
-- @return table as described above
--
function create_das_packet( magic, data )

  local packet = {}
  local data_len = data:len()

  packet.header = {}

  packet.header.raw = "\x00\x00\x00\x00\x44\x42\x32\x44\x41\x53\x20\x20\x20\x20\x20\x20"
  .. "\x01\x04\x00\x00\x00\x10\x39\x7a\x00\x05\x00\x00\x00\x00\x00\x00"
  .. "\x00\x00\x00\x00"
  .. string.pack("<B I2", magic, data_len)
  .. "\x00\x00"

  packet.header.data_len = data_len
  packet.data = data

  return packet
end

action = function(host, port)


  -- create the socket used for our connection
  local socket = nmap.new_socket()

  -- set a reasonable timeout value
  socket:set_timeout(10000)

  -- do some exception handling / cleanup
  local catch = function()
    stdnse.debug1("ERROR communicating with " .. host.ip .. " on port " .. port.number)
    socket:close()
  end

  local try = nmap.new_try(catch)


  try(socket:connect(host, port))

  local query

  -- ************************************************************************************
  -- Transaction block 1
  -- ************************************************************************************
  local data = "\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x4a\x00"

  --try(socket:send(query))
  local db2packet = create_das_packet(0x02, data)

  send_db2_packet( socket, db2packet )
  read_db2_packet( socket )

  -- ************************************************************************************
  -- Transaction block 2
  -- ************************************************************************************
  data = "\x00\x00\x00\x2c\x00\x00\x00"
  .. "\x0c\x00\x00\x00\x08\x59\xe7\x1f\x4b\x79\xf0\x90\x72\x85\xe0\x8f"
  .. "\x3e\x38\x45\x38\xe3\xe5\x12\xc4\x3b\xe9\x7d\xe2\xf5\xf0\x78\xcc"
  .. "\x81\x6f\x87\x5f\x91"

  db2packet = create_das_packet(0x05, data)

  send_db2_packet( socket, db2packet )
  read_db2_packet( socket )

  -- ************************************************************************************
  -- Transaction block 3
  -- ************************************************************************************
  data = "\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x4a\x01\x00\x00\x00"
  .. "\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00"
  .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x64\x62\x32"
  .. "\x64\x61\x73\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x00\x00\x00\x00"
  .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x64\x62\x32"
  .. "\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x53\x72\x76\x00"

  db2packet = create_das_packet(0x0a, data)
  send_db2_packet( socket, db2packet )
  read_db2_packet( socket )

  -- ************************************************************************************
  -- Transaction block 4
  -- ************************************************************************************
  data = "\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x4a\x01\x00\x00\x00"
  .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x03"
  .. "\x48\x00\x00\x00\x00\x4a\xfb\x42\x90\x00\x00\x24\x93\x00\x00\x00"
  .. "\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00"
  .. "\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00"
  .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x64\x62\x32"
  .. "\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x53\x72\x76\x00\x00\x00\x00"
  .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x64\x62\x32"
  .. "\x64\x61\x73\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x00\x00\x00\x00"
  .. "\x0c\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x00\x10\x00\x00\x00"
  .. "\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00\x10\x00\x00\x00"
  .. "\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00\x11\x00\x00\x00"
  .. "\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x00"

  db2packet = create_das_packet(0x06, data)
  send_db2_packet( socket, db2packet )

  data =  "\x00\x00\x00\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00"
  .. "\x00\x04\xb8\x64\x62\x32\x64\x61\x73\x4b\x6e\x6f\x77\x6e\x44\x73"
  .. "\x63\x76\x00\x00\x00\x00\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00"
  .. "\x00\x04\xb8\x64\x62\x32\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x53"
  .. "\x72\x76\x00\x00\x00\x00\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\x00"
  .. "\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\x00"
  .. "\x00\x00\x01\x00\x00\x00\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\x00"
  .. "\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x0c\x00\x00\x00\x08\x00"
  .. "\x00\x00\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\x00\x00\x00\x01\x00"
  .. "\x00\x00\x18\x00\x00\x00\x0c\x00\x00\x00\x08\x00\x00\x00\x0c\x00"
  .. "\x00\x00\x0c\x00\x00\x00\x18"

  db2packet = create_das_packet(0x06, data)
  send_db2_packet( socket, db2packet )

  local packet = read_db2_packet( socket )
  local db2response = parse_db2_packet(packet)

  socket:close()

  -- The next block of code is essentially the version extraction code from db2-info.nse
  local server_version
  if string.sub(db2response.version,1,3) == "SQL" then
    local major_version = string.sub(db2response.version,4,5)

    -- strip the leading 0 from the major version, for consistency with
    -- nmap-service-probes results
    if string.sub(major_version,1,1) == "0" then
      major_version = string.sub(major_version,2)
    end
    local minor_version = string.sub(db2response.version,6,7)
    local hotfix = string.sub(db2response.version,8)
    server_version = major_version .. "." .. minor_version .. "." .. hotfix
  end

  -- Try to determine which of the two values (probe version vs script) has more
  -- precision.  A couple DB2 versions send DB2 UDB 7.1 vs SQL090204 (9.02.04)
  local _
  local current_count = 0
  if port.version.version ~= nil then
    _, current_count = string.gsub(port.version.version, "%.", ".")
  end

  local new_count = 0
  if server_version ~= nil then
    _, new_count = string.gsub(server_version, "%.", ".")
  end

  if current_count < new_count then
    port.version.version = server_version
  end

  local result = false

  local db2profile = extract_server_profile( db2response.info )

  if (db2profile ~= nil ) then
    result = "DB2 Administration Server Settings\r\n"
    .. extract_server_profile( db2response.info )

    -- Set port information
    port.version.name = "ibm-db2"
    port.version.product = "IBM DB2 Database Server"
    port.version.name_confidence = 10
    nmap.set_port_version(host, port)
    nmap.set_port_state(host, port, "open")
  end

  return result

end