summaryrefslogtreecommitdiffstats
path: root/nselib/anyconnect.lua
blob: 5f05d86194537f7ab16032593f2a2e64b446fc27 (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
---
-- This library implements HTTP requests used by the Cisco AnyConnect VPN Client
--
-- @author Patrik Karlsson <patrik@cqure.net>
--
-- @args anyconnect.group AnyConnect tunnel group (default: VPN)
-- @args anyconnect.mac MAC address of connecting client (default: random MAC)
-- @args anyconnect.version Version of connecting client (default: 3.1.05160)
-- @args anyconnect.ua User Agent of connecting client (default: AnyConnect Darwin_i386 3.1.05160)

local http = require('http')
local stdnse = require('stdnse')
local url = require('url')
local table = require('table')
local rand = require "rand"

local args_group= stdnse.get_script_args('anyconnect.group') or "VPN"
local args_mac= stdnse.get_script_args('anyconnect.mac')
local args_ver = stdnse.get_script_args('anyconnect.version') or "3.1.05160"
local args_ua = stdnse.get_script_args('anyconnect.ua') or ("AnyConnect Darwin_i386 %s"):format(args_ver)

_ENV = stdnse.module("anyconnect", stdnse.seeall)

Cisco = {

  Util = {

    generate_mac = function()
      return stdnse.format_mac(rand.random_string(6))
    end,

  },

  AnyConnect = {

    new = function(self, host, port)
      local o = { host = host, port = port }
      setmetatable(o, self)
      self.__index = self
      return o
    end,

    -- generate a random hex-string of length 'length'
    --
    generate_random = function(length)
      return rand.random_string(length * 2, '0123456789ABCDEF')
    end,

    connect = function(self)
      args_mac = args_mac or Cisco.Util.generate_mac()
      local headers = {
        ['User-Agent'] = args_ua,
        ['Accept'] = '*/*',
        ['Accept-Encoding'] = 'identity',
        ['X-Transcend-Version'] = 1,
        ['X-Aggregate-Auth'] = 1,
        ['X-AnyConnect-Platform'] = 'mac-intel'
      }

      local data = ([[<?xml version="1.0" encoding="UTF-8"?>
<config-auth client="vpn" type="init" aggregate-auth-version="2">
<version who="vpn">%s</version>
<device-id device-type="MacBookAir4,1" platform-version="10.9.2" unique-id="%s">mac-intel</device-id>
<mac-address-list>
<mac-address>%s</mac-address></mac-address-list>
<group-select>%s</group-select>
<group-access>https://%s:%s</group-access>
</config-auth>]]):format(args_ver, self.generate_random(64), args_mac, args_group, self.host.ip, self.port.number)

      local options = { header=headers , no_cache=true, redirect_ok = function(host,port)
          local c = 5
          return function(url)
            if ( c==0 ) then return false end
            c = c - 1
            return true
          end
        end
      }

      local path = '/'
      local response = http.head(self.host, self.port, path, options)
      -- account for redirects
      if response.status ~= 200 then
        return false, "Failed to connect to SSL VPN server"
      elseif response.location then
        local u = url.parse(response.location[#response.location])
        if u.host then
          self.host = u.host
        end
        if u.path then
          path = u.path
        end
      end

      response = http.post(self.host, self.port, path, options, nil, data)

      if response.status ~= 200 or response.body == nil then
        return false, "Not a Cisco ASA or unsupported version"
      end

      local xmltags = {
        'version',
        'tunnel-group',
        'group-alias',
        'config-hash',
        'host-scan-ticket',
        'host-scan-token',
        'host-scan-base-uri',
        'host-scan-wait-uri',
        'banner'
      }

      self.conn_attr = {}
      for _, tag in ipairs(xmltags) do
        local body = response.body:gsub('\r?\n', '')
        local filter = ("<%s.->(.*)</%s>"):format(tag:gsub('-', '%%-'), tag:gsub('-', '%%-'))
        local m = body:match(filter)
        if m then
          self.conn_attr[tag] = m
        end
      end

      if not self.conn_attr['version'] then
        return false, "Not a Cisco ASA or unsupported version"
      end

      -- in case we were redirected
      self.conn_attr['host'] = stdnse.get_hostname(self.host)
      return true
    end,

    ---
    -- Returns the version of the remote SSL VPN concentrator
    -- @return table containing major, minor and rev numeric values
    get_version = function(self)
      local ver = {}
      ver['major'], ver['minor'], ver['rev'] = self.conn_attr['version']:match('^(%d-)%.(%d-)%((.*)%)$')
      return ver
    end

  }
}

return _ENV