This is an anonymized log of the authentication, configuration, tunnel data transfer, and logout interactions between a PAN GlobalProtect VPN server and client. The logs below are based on the official Windows client, v3.0.1-10, with some updates from v4.0.5-8.
Client version 4.0 adds IPv6 support and SAML authentication support.
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
New parameters sent by Windows client v4.0.5-8:
clientgpversion: 4.0.5-8
prelogin-cookie:
ipv6-support: yes
client-ip: 34.56.78.90
client-ipv6: .
preferred-ipv6:
The client-ip{,v6}
parameters refer to the client's external internet-facing IP address, while preferred-ip{,v6}
parameters
refer to the expected/desired addresses within the VPN.
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 sent in request</argument>
</application-desc>
</jnlp>
Windows client v4.0.5-8 receives additional input-parroting arguments at the end:
<argument>portal-userauthcookie as sent in request</argument>
<argument>prelogon-userauthcookie as sent in request</argument>
<argument>preferred IPv6 address as sent in request</argument>
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,
Windows client v4.0.5-8 adds additional parameters at the end:
app-version: 4.0.5-8
addr1-v6-1: f00f::/16
addr1-v6-2: f00f:dead:beef::dead:beef/128
preferred-ipv6:
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 ICMP request ("ping") packets to the gateway.
-
These packets are ESP-encapsulated
-
These packets are sent from the client's in-VPN IP address to the IP address specified by the
<gw-address>
from thegetconfig
response.- The destination address is usually the same as the gateway's public internet-facing IP address, but sometimes it is a VPN-internal address ¯\_(ツ)_/¯
-
These ICMP request packets include the following magic payload — though only the first 16 bytes of the payload appear to be necessary to elicit a response from the gateway.
"monitor\x00\x00pan ha 0123456789:;<=>? !\"#$%&\'()*+,-./\x10\x11\x12\x13\x14\x15\x16\x18" "monitor\x00\x00pan ha " (first 16 bytes)
-
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 same "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>
<!-- newer servers include these, older ones don't: -->
<saml-session-index></saml-session-index>
<saml-name-id></saml-name-id>
</response>