145 lines
4.3 KiB
Lua
145 lines
4.3 KiB
Lua
|
---
|
||
|
-- 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
|