This is an anonymized log of the authentication, configuration, tunnel data transfer, and logout interactions between a PAN GlobalProtect VPN server and client (Windows client, v3.0.1-10).
The correct user-agent (User-Agent: PAN Globalprotect
) is required for all HTTP interactions with the GlobalProtect VPN. It treats any other user-agent as a web browser, not a VPN client.
Some of the form fields are required (user and password
obviously, ok=Login
inexplicably) while others can apparently be
omitted.
POST https://gateway.company.com/ssl-vpn/login.esp
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
User-Agent: PAN GlobalProtect
Host: gateway.company.com
URLEncoded form:
prot: https:
server: gateway.company.com
inputStr:
jnlpReady: jnlpReady
user: Myusername
passwd: DEADBEEF
computer: DEADBEEF01
ok: Login
direct: yes
clientVer: 4100
os-version: Microsoft Windows Server 2012, 64-bit
preferred-ip: 12.34.56.78
clientos: Windows
clientgpversion: 3.0.1-10
portal-userauthcookie: empty
portal-prelogonuserauthcookie: empty
host-id: deadbeef-dead-beef-dead-beefdeadbeef
This response contains a delicious 32-digit cookie. The second hexadecimal blob is a persistent identifier associated with the combination of user account and gateway (probably the sha1
hash of something, since it's 40 digits long).
In order to handle the getconfig, tunnel-connect, and logon requests properly (described below), the client needs to save some other parts of this response besides the cookie:
- username: the server may return a slightly modified version of the username provided upon login (e.g.
steve.JoNes
transformed into the canonicalsteve.jones
) - domain name and portal name: the correct values for these are—inexplicably—required to log out of the VPN session successfully ¯\_(ツ)_/¯
- authentication type is something like
LDAP-auth
orAUTH-RADIUS_RSA_OTP
, and appears to reflect the mechanism by which the user was authenticated - preferred IP address is set by some VPN gateways even if it was omitted from the login request; if it is not empty or
(null)
, its value should be used in the subsequent getconfig request 4100
appears to identify the VPN protocol version. I've never seen another value.
<?xml version='1.0' encoding='utf-8'?>
<jnlp>
<application-desc>
<argument>(null)</argument>
<argument>delicious 32 digits hex cookie</argument>
<argument>another 40 mysterious hexadecimal digits</argument>
<argument>Gateway-Name</argument>
<argument>username provided above</argument>
<argument>authentication type</argument>
<argument>vsys1</argument>
<argument>company domain name</argument>
<argument>(null)</argument>
<argument/>
<argument/>
<argument/>
<argument>tunnel</argument>
<argument>-1</argument>
<argument>4100</argument>
<argument>preferred ip address as provided above</argument>
</application-desc>
</jnlp>
Similar to above, some of the parameters are
required, others are not. addr1
seems to be the current IPv4 subnet
of the client machine, and is apparently optional.
If a client has obtained a valid and unexpired authcookie, it's possible to re-run the getconfig request/response flow. This can be used to reconnect the tunnel after a network outage, without reauthenticating.
POST https://gateway.company.com/ssl-vpn/getconfig.esp
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
User-Agent: PAN GlobalProtect
Host: gateway.company.com
URLEncoded form
user: Myusername
addr1: 4.5.6.78/24 (current IPv4 network, I think?)
preferred-ip: 12.34.56.78 (use value from login response)
portal: Gateway-Name (use value from login response)
authcookie: cookie (32 hex digits from above)
client-type: 1
os-version: Microsoft Windows Server 2012, 64-bit
app-version: 3.0.1-10
protocol-version: p1
clientos: Windows
enc-algo: aes-256-gcm,aes-128-gcm,aes-128-cbc,
hmac-algo: sha1,
Here's where it gets interesting:
- Routing information seems almost identical to what Cisco AnyConnect provides, except in XML form
- IPsec configuration specifies the exist SPI indexes to use, as well as the client-to-server (c2s) and server-to-client (s2c) encryption keys and authentication keys. Note that the upstream and downstream keys and SPIs do not match; this is intentional.
- SSL tunnel URL (
<ssl-tunnel-url>/ssl-tunnel-connect.sslvpn</ssl-tunnel-url>
) is the same on all servers I've seen - MTU is sent as zero (
<mtu>0</mtu>
) on all servers I've seen. This seems broken and useless.
<?xml version='1.0' encoding='UTF-8'?>
<response status="success">
<need-tunnel>yes</need-tunnel>
<ssl-tunnel-url>/ssl-tunnel-connect.sslvpn</ssl-tunnel-url>
<portal>Gateway-Name</portal>
<user>Myusername</user>
<lifetime>86400</lifetime>
<timeout>3600</timeout>
<disconnect-on-idle>3600</disconnect-on-idle>
<bw-c2s>1000</bw-c2s>
<bw-s2c>1000</bw-s2c>
<gw-address>IP address of gateway.company.com in my case</gw-address>
<ip-address>the preferred IP address from above</ip-address>
<netmask>255.255.255.255</netmask>
<dns>
<member>8.8.8.8</member>
<member>4.4.4.4</member>
</dns>
<wins>
<member>8.8.8.9</member>
<member>4.4.4.5</member>
</wins>
<default-gateway>gateway for default internal route</default-gateway>
<mtu>0</mtu>
<dns-suffix>
<member>company.com</member>
<member>company.internal</member>
<member>stuff.company.com</member>
</dns-suffix>
<no-direct-access-to-local-network>no</no-direct-access-to-local-network>
<access-routes>
<member>10.0.0.0/8</member>
<member>192.168.0.0/16</member>
</access-routes>
<ipsec>
<udp-port>4501</udp-port>
<ipsec-mode>esp-tunnel</ipsec-mode>
<enc-algo>aes-128-cbc</enc-algo>
<hmac-algo>sha1</hmac-algo>
<c2s-spi>0xDEADBEEF</c2s-spi>
<s2c-spi>0xFEEDBACC</s2c-spi>
<akey-s2c>
<bits>160</bits>
<val>deadbeefdeadbeefdeadbeefdeadbeefdeadbeef</val>
</akey-s2c>
<ekey-s2c>
<bits>128</bits>
<val>feedbaccfeedbaccfeedbaccfeedbacc</val>
</ekey-s2c>
<akey-c2s>
<bits>160</bits>
<val>deadbeefdeadbeefdeadbeefdeadbeefdeadbeef</val>
</akey-c2s>
<ekey-c2s>
<bits>128</bits>
<val>feedbaccfeedbaccfeedbaccfeedbacc</val>
</ekey-c2s>
</ipsec>
</response>
In the back-and-forth flows shown below, <
means sent by the gateway, >
means sent by the client.
Uses the keying information obtained in response to the getconfig
request. In order to initiate the connection, the client sends 3 ESP-encapsulated ICMP request ("ping") packets to the gateway. They are sent from the client's in-VPN IP address to the IP address specified by the <gw-address>
from the getconfig
response (this is normally the same as the gateway's public IP address, but is sometimes a VPN-internal address ¯\_(ツ)_/¯). These ICMP request packets include the following magic payload:
"monitor\x00\x00pan ha 0123456789:;<=>? !\"#$%&\'()*+,-./\x10\x11\x12\x13\x14\x15\x16\x18"
"monitor\x00\x00pan ha " (first 16 bytes)
Only the first 16 bytes of the payload appear to be necessary to elicit a response from the gateway. Once the gateway has responded with a corresponding ICMP reply, the client and server send and receive arbitrary ESP-encapsulated traffic.
The client continues to periodically send the "magic ping" packets as a keepalive.
The tunnel starts when the client issues a CONNECT
-disguised-as-GET
command, to the tunnel URL path specified in the getconfig
response. The gateway responds with the ASCII string START_TUNNEL
instead of a standard HTTP response code:
> 'GET /ssl-tunnel-connect.sslvpn?user=Myusername&authcookie=deadbeef HTTP/1.1\r\n\r\n'
< 'START_TUNNEL'
Now the client and gateway proceed to communicate by sending encapsulated IPv4 packets back and forth. Here is an example snippet of the IP-over-TLS stream format, as initiated by the client's GET
command:
> 'GET /ssl-tunnel-connect.sslvpn?user=Myusername&authcookie=deadbeef HTTP/1.1\r\n\r\n'
< 'START_TUNNEL'
< 1a2b3c4d0800005401000000000000004500005461e400007e11f5520a100f030a12c23d[...]
> 1a2b3c4d08000034010000000000000045000034038f0000011108df0a12c23de00000fc[...]
...
Here is the packet format:
- 4 magic bytes:
1a2b3c4d
- Next 2 bytes are probably the Ethertype (as uint16_be):
0800
is IPv4 - Next 2 bytes are the packet length (as uint16_be) excluding this header, or
0000
for a keepalive packet - Next 8 bytes are always
0100000000000000
for a real IP packet, or0000000000000000
for a keepalive packet - Remaining bytes are the actual Layer 3 packet (IPv4 packets starting with
45
in the examples above)
The DPD/keepalive packets can be sent by either the client or the server, and the other should immediately respond with an identical packet.
The server will drop the client connection if it doesn't receive anything from the client (after about 120 seconds in my testing) and the client should send the DPD/keepalive if it hasn't received anything from the server in a while. The official client appears to always send keepalive packets every 10 seconds.
If/when the SSL tunnel is connected, the ESP tunnel cannot be used any longer. The VPN server appears to invalidate the SPIs/keys that it sent, and will not respond to ESP-over-UDP packets. The only way to re-enable the ESP connection is to disconnect the SSL tunnel, re-run the getconfig request, and start over with new ESP keys sent in the new getconfig response.
This means that a client that prefers to use ESP must not try to connect the SSL tunnel until after an ESP connection has failed. (The official Windows client waits 10 seconds for ICMP reply packets over ESP, before failing over to the SSL tunnel.)
The client must send the exact domain, computer name, portal, and OS version from the login request or response… otherwise the logout request will fail and the tunnel can be reconnected using the same authcookie.
POST https://gateway.company.com/ssl-vpn/logout.esp
Accept: */*
Content-Type: application/x-www-form-urlencoded
Host: gateway.company.com
URLEncoded form:
user: Myusername
portal: Gateway-Name
authcookie: as above
domain: company domain name
computer: DEADBEEF01
os-version: Microsoft Windows Server 2012, 64-bit
<?xml version='1.0' encoding='UTF-8'?>
<response status="success">
<portal>Gateway-Name</portal>
<domain>company domain name</domain>
<user>Myusername</user>
<computer>DEADBEEF01</computer>
</response>