Commit 4e35d505 authored by Daniel Lenski's avatar Daniel Lenski Committed by David Woodhouse

Add support for checking and submitting HIP reports

Unlike CSD, the HIP security checker runs during the connection	phase, not
during the authentication phase.

Therefore we need to build the CSD token (an MD5 digest identifying the
client) without relying on the authentication phase having run in the same
process.

We build it from the cookie containing authentication information,
but exclude the volatile field (which changes from session to session)
and the preferred-ip field (which may not be present in all cases, or may
change from session to session).
Signed-off-by: default avatarDaniel Lenski <dlenski@gmail.com>
Signed-off-by: default avatarDavid Woodhouse <dwmw2@infradead.org>
parent 1ad22fc3
......@@ -26,6 +26,9 @@
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#ifndef _WIN32
#include <sys/wait.h>
#endif
#include <stdarg.h>
#ifdef HAVE_LZ4
#include <lz4.h>
......@@ -717,6 +720,200 @@ out:
return ret;
}
static int parse_hip_report_check(struct openconnect_info *vpninfo, xmlNode *xml_node)
{
char *s;
int result = -EINVAL;
if (!xml_node || !xmlnode_is_named(xml_node, "response"))
goto out;
for (xml_node = xml_node->children; xml_node; xml_node=xml_node->next) {
if (!xmlnode_get_text(xml_node, "hip-report-needed", &s)) {
if (!strcmp(s, "no"))
result = 0;
else if (!strcmp(s, "yes"))
result = -EAGAIN;
else
result = -EINVAL;
free(s);
goto out;
}
}
out:
return result;
}
/* Unlike CSD, the HIP security checker runs during the connection
* phase, not during the authentication phase.
*
* The HIP security checker will (probably) ask us to resubmit the
* HIP report if either of the following changes:
* - Client IP address
* - Client HIP report md5sum
*
* I'm not sure what the md5sum is computed over in the official
* client, but it doesn't really matter.
*
* We just need an identifier for the combination of the local host
* and the VPN gateway which won't change when our IP address
* or authcookie are changed.
*/
static int build_csd_token(struct openconnect_info *vpninfo)
{
struct oc_text_buf *buf;
unsigned char md5[16];
int i;
if (vpninfo->csd_token)
return 0;
vpninfo->csd_token = malloc(MD5_SIZE * 2 + 1);
if (!vpninfo->csd_token)
return -ENOMEM;
/* use localname and cookie (excluding volatile authcookie and preferred-ip) to build md5sum */
buf = buf_alloc();
append_opt(buf, "computer", vpninfo->localname);
filter_opts(buf, vpninfo->cookie, "authcookie,preferred-ip", 0);
if (buf_error(buf))
goto out;
/* save as csd_token */
openconnect_md5(md5, buf->data, buf->pos);
for (i=0; i < MD5_SIZE; i++)
sprintf(&vpninfo->csd_token[i*2], "%02x", md5[i]);
out:
return buf_free(buf);
}
/* check if HIP report is needed (to ssl-vpn/hipreportcheck.esp) or submit HIP report contents (to ssl-vpn/hipreport.esp) */
static int check_or_submit_hip_report(struct openconnect_info *vpninfo, const char *report)
{
int result;
struct oc_text_buf *request_body = buf_alloc();
const char *request_body_type = "application/x-www-form-urlencoded";
const char *method = "POST";
char *xml_buf=NULL, *orig_path;
/* cookie gives us these fields: authcookie, portal, user, domain, and (maybe the unnecessary) preferred-ip */
buf_append(request_body, "client-role=global-protect-full&%s", vpninfo->cookie);
append_opt(request_body, "computer", vpninfo->localname);
append_opt(request_body, "client-ip", vpninfo->ip_info.addr);
if (report) {
/* XML report contains many characters requiring URL-encoding (%xx) */
buf_ensure_space(request_body, strlen(report)*3);
append_opt(request_body, "report", report);
} else {
result = build_csd_token(vpninfo);
if (result)
goto out;
append_opt(request_body, "md5", vpninfo->csd_token);
}
if ((result = buf_error(request_body)))
goto out;
orig_path = vpninfo->urlpath;
vpninfo->urlpath = strdup(report ? "ssl-vpn/hipreport.esp" : "ssl-vpn/hipreportcheck.esp");
result = do_https_request(vpninfo, method, request_body_type, request_body,
&xml_buf, 0);
free(vpninfo->urlpath);
vpninfo->urlpath = orig_path;
result = gpst_xml_or_error(vpninfo, result, xml_buf, report ? NULL : parse_hip_report_check, NULL, NULL);
out:
buf_free(request_body);
free(xml_buf);
return result;
}
static int run_hip_script(struct openconnect_info *vpninfo)
{
#if !defined(_WIN32) && !defined(__native_client__)
int pipefd[2];
int ret;
pid_t child;
#endif
if (!vpninfo->csd_wrapper) {
vpn_progress(vpninfo, PRG_ERR,
_("WARNING: Server asked us to submit HIP report with md5sum %s.\n"
"VPN connectivity may be disabled or limited without HIP report submission.\n"
"You need to provide a --csd-wrapper argument with the HIP report submission script.\n"),
vpninfo->csd_token);
/* XXX: Many GlobalProtect VPNs work fine despite allegedly requiring HIP report submission */
return 0;
}
#if defined(_WIN32) || defined(__native_client__)
vpn_progress(vpninfo, PRG_ERR,
_("Error: Running the 'HIP Report' script on this platform is not yet implemented.\n"));
return -EPERM;
#else
if (pipe(pipefd) == -1)
goto out;
child = fork();
if (child == -1) {
goto out;
} else if (child > 0) {
/* in parent: read report from child */
struct oc_text_buf *report_buf = buf_alloc();
char b[256];
int i, status;
close(pipefd[1]);
buf_truncate(report_buf);
while ((i = read(pipefd[0], b, sizeof(b))) > 0)
buf_append_bytes(report_buf, b, i);
waitpid(child, &status, 0);
if (status != 0) {
vpn_progress(vpninfo, PRG_ERR,
_("HIP script returned non-zero status: %d\n"), status);
ret = -EINVAL;
} else {
ret = check_or_submit_hip_report(vpninfo, report_buf->data);
if (ret < 0)
vpn_progress(vpninfo, PRG_ERR, _("HIP report submission failed.\n"));
else {
vpn_progress(vpninfo, PRG_INFO, _("HIP report submitted successfully.\n"));
ret = 0;
}
}
buf_free(report_buf);
return ret;
} else {
/* in child: run HIP script */
char *hip_argv[32];
int i = 0;
close(pipefd[0]);
dup2(pipefd[1], 1);
hip_argv[i++] = openconnect_utf8_to_legacy(vpninfo, vpninfo->csd_wrapper);
hip_argv[i++] = (char *)"--cookie";
hip_argv[i++] = vpninfo->cookie;
hip_argv[i++] = (char *)"--computer";
hip_argv[i++] = vpninfo->localname;
hip_argv[i++] = (char *)"--client-ip";
hip_argv[i++] = (char *)vpninfo->ip_info.addr;
hip_argv[i++] = (char *)"--md5";
hip_argv[i++] = vpninfo->csd_token;
hip_argv[i++] = NULL;
execv(hip_argv[0], hip_argv);
out:
vpn_progress(vpninfo, PRG_ERR,
_("Failed to exec HIP script %s\n"), hip_argv[0]);
exit(1);
}
#endif /* !_WIN32 && !__native_client__ */
}
int gpst_setup(struct openconnect_info *vpninfo)
{
int ret;
......@@ -728,7 +925,19 @@ int gpst_setup(struct openconnect_info *vpninfo)
/* Get configuration */
ret = gpst_get_config(vpninfo);
if (ret)
return ret;
goto out;
/* Check HIP */
ret = check_or_submit_hip_report(vpninfo, NULL);
if (ret == -EAGAIN) {
vpn_progress(vpninfo, PRG_DEBUG,
_("Gateway says HIP report submission is needed.\n"));
ret = run_hip_script(vpninfo);
if (ret != 0)
goto out;
} else if (ret == 0)
vpn_progress(vpninfo, PRG_DEBUG,
_("Gateway says no HIP report submission is needed.\n"));
/* We do NOT actually start the HTTPS tunnel yet if we want to
* use ESP, because the ESP tunnel won't work if the HTTPS tunnel
......@@ -737,6 +946,7 @@ int gpst_setup(struct openconnect_info *vpninfo)
if (vpninfo->dtls_state == DTLS_DISABLED || vpninfo->dtls_state == DTLS_NOSECRET)
ret = gpst_connect(vpninfo);
out:
return ret;
}
......
#!/bin/sh
# openconnect will call this script with the follow command-line
# arguments, which are needed to populate the contents of the
# HIP report:
#
# --cookie: a URL-encoded string, as output by openconnect
# --authenticate --protocol=gp, which includes parameters
# --from the /ssl-vpn/login.esp response
#
# --computer: local hostname, which can be overriden with
# --openconnect local-hostname=HOSTNAME
#
# --client-ip: IPv4 address allocated by the GlobalProtect VPN for
# this client (included in /ssl-vpn/getconfig.esp
# response)
#
# --md5: The md5 digest to encode into this HIP report. I'm not sure
# exactly what this is the md5 digest *of*, but all that
# really matters is that the value in the HIP report
# submission should match the value in the HIP report check.
# Read command line arguments into variables
COOKIE=
COMPUTER=
IP=
MD5=
while [ "$1" ]; do
if [ "$1" = "--cookie" ]; then shift; COOKIE="$1"; fi
if [ "$1" = "--computer" ]; then shift; COMPUTER="$1"; fi
if [ "$1" = "--client-ip" ]; then shift; IP="$1"; fi
if [ "$1" = "--md5" ]; then shift; MD5="$1"; fi
shift
done
if [ -z "$COOKIE" -o -z "$COMPUTER" -o -z "$IP" -o -z "$MD5" ]; then
echo "Parameters --cookie, --computer, --client-ip, and --md5 are required" >&2
exit 1;
fi
# Extract username and domain from cookie
USER=$(echo "$COOKIE" | sed -rn 's/(.+&|^)user=([^&]+)(&.+|$)/\2/p')
DOMAIN=$(echo "$COOKIE" | sed -rn 's/(.+&|^)domain=([^&]+)(&.+|$)/\2/p')
# Timestamp in the format expected by GlobalProtect server
NOW=$(date +'%m/%d/%Y %H:%M:%S')
# This value may need to be extracted from the official HIP report, if a made-up value is not accepted.
HOSTID="deadbeef-dead-beef-dead-beefdeadbeef"
cat <<EOF
<hip-report name="hip-report">
<md5-sum>$MD5</md5-sum>
<user-name>$USER</user-name>
<domain>$DOMAIN</domain>
<host-name>$COMPUTER</host-name>
<host-id>$HOSTID</host-id>
<ip-address>$IP</ip-address>
<ipv6-address></ipv6-address>
<generate-time>$NOW</generate-time>
<categories>
<entry name="host-info">
<client-version>4.0.2-19</client-version>
<os>Microsoft Windows 10 Pro , 64-bit</os>
<os-vendor>Microsoft</os-vendor>
<domain>$DOMAIN.internal</domain>
<host-name>$COMPUTER</host-name>
<host-id>$HOSTID</host-id>
<network-interface>
<entry name="{DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF}">
<description>PANGP Virtual Ethernet Adapter #2</description>
<mac-address>01-02-03-00-00-01</mac-address>
<ip-address>
<entry name="$IP"/>
</ip-address>
<ipv6-address>
<entry name="dead::beef:dead:beef:dead"/>
</ipv6-address>
</entry>
</network-interface>
</entry>
<entry name="antivirus">
<list>
<entry>
<ProductInfo>
<Prod name="McAfee VirusScan Enterprise" version="8.8.0.1804" defver="8682.0" prodType="1" engver="5900.7806" osType="1" vendor="McAfee, Inc." dateday="12" dateyear="2017" datemon="10">
</Prod>
<real-time-protection>yes</real-time-protection>
<last-full-scan-time>10/11/2017 15:23:41</last-full-scan-time>
</ProductInfo>
</entry>
<entry>
<ProductInfo>
<Prod name="Windows Defender" version="4.11.15063.332" defver="1.245.683.0" prodType="1" engver="1.1.13804.0" osType="1" vendor="Microsoft Corp." dateday="8" dateyear="2017" datemon="6">
</Prod>
<real-time-protection>no</real-time-protection>
<last-full-scan-time>n/a</last-full-scan-time>
</ProductInfo>
</entry>
</list>
</entry>
<entry name="anti-spyware">
<list>
<entry>
<ProductInfo>
<Prod name="McAfee VirusScan Enterprise" version="8.8.0.1804" defver="8682.0" prodType="2" engver="5900.7806" osType="1" vendor="McAfee, Inc." dateday="12" dateyear="2017" datemon="10">
</Prod>
<real-time-protection>yes</real-time-protection>
<last-full-scan-time>10/11/2017 15:23:41</last-full-scan-time>
</ProductInfo>
</entry>
<entry>
<ProductInfo>
<Prod name="Windows Defender" version="4.11.15063.332" defver="1.245.683.0" prodType="2" engver="1.1.13804.0" osType="1" vendor="Microsoft Corp." dateday="8" dateyear="2017" datemon="6">
</Prod>
<real-time-protection>no</real-time-protection>
<last-full-scan-time>n/a</last-full-scan-time>
</ProductInfo>
</entry>
</list>
</entry>
<entry name="disk-backup">
<list>
<entry>
<ProductInfo>
<Prod name="Windows Backup and Restore" version="10.0.15063.0" vendor="Microsoft Corp.">
</Prod>
<last-backup-time>n/a</last-backup-time>
</ProductInfo>
</entry>
</list>
</entry>
<entry name="disk-encryption">
<list>
<entry>
<ProductInfo>
<Prod name="Windows Drive Encryption" version="10.0.15063.0" vendor="Microsoft Corp.">
</Prod>
<drives>
<entry>
<drive-name>C:</drive-name>
<enc-state>full</enc-state>
</entry>
</drives>
</ProductInfo>
</entry>
</list>
</entry>
<entry name="firewall">
<list>
<entry>
<ProductInfo>
<Prod name="Microsoft Windows Firewall" version="10.0" vendor="Microsoft Corp.">
</Prod>
<is-enabled>yes</is-enabled>
</ProductInfo>
</entry>
</list>
</entry>
<entry name="patch-management">
<list>
<entry>
<ProductInfo>
<Prod name="McAfee ePolicy Orchestrator Agent" version="5.0.5.658" vendor="McAfee, Inc.">
</Prod>
<is-enabled>yes</is-enabled>
</ProductInfo>
</entry>
<entry>
<ProductInfo>
<Prod name="Microsoft Windows Update Agent" version="10.0.15063.0" vendor="Microsoft Corp.">
</Prod>
<is-enabled>yes</is-enabled>
</ProductInfo>
</entry>
</list>
<missing-patches/>
</entry>
<entry name="data-loss-prevention">
<list/>
</entry>
</categories>
</hip-report>
EOF
......@@ -3,7 +3,7 @@
SUBDIRS = styles inc images
CONV = "$(srcdir)/html.py"
FTR_PAGES = csd.html charset.html token.html pkcs11.html tpm.html features.html gui.html nonroot.html
FTR_PAGES = csd.html charset.html token.html pkcs11.html tpm.html features.html gui.html nonroot.html hip.html
START_PAGES = building.html connecting.html manual.html vpnc-script.html
INDEX_PAGES = changelog.html download.html index.html packages.html platforms.html
PROTO_PAGES = anyconnect.html juniper.html globalprotect.html
......
......@@ -24,7 +24,7 @@
<li>Automatic update of VPN server list / configuration.</li>
<li>Roaming support, allowing reconnection when the local IP address changes.</li>
<li>Run without root privileges <i>(see <a href="nonroot.html">here</a>)</i>.</li>
<li>"Cisco Secure Desktop" support <i>(see <a href="csd.html">here</a>)</i>.</li>
<li>Support for "Cisco Secure Desktop" <i>(see <a href="csd.html">here</a>)</i> and "GlobalProtect HIP report" <i>(see <a href="hip.html">here</a>)</i>.</li>
<li>Graphical connection tools for various environments <i>(see <a href="gui.html">here</a>)</i>.</li>
</ul>
......
......@@ -16,15 +16,22 @@
href="https://tools.ietf.org/html/rfc3948">ESP</a>, with routing and
configuration information distributed in XML format.</p>
<h3>Authentication</h3>
<p>To authenticate, you connect to the secure web server (<tt>POST
/ssl-vpn/login.esp</tt>), provide a username, password, and (optionally) a
certificate, and receive an authcookie. The username, authcookie, and a
couple other bits of information obtained at login are combined into the
OpenConnect cookie.</p>
<h3>Tunnel configuration</h3>
<p>To connect to the secure tunnel, the cookie is used to read routing and
tunnel configuration information (<tt>POST /ssl-vpn/getconfig.esp</tt>).</p>
<p>Next, a <a href="hip.html">HIP report</a> (security scanner report) is
generated by the client and submitted to the server, if required.</p>
<p>Finally, either an HTTPS-based or ESP-based tunnel is setup:</p>
<ol>
......
<PAGE>
<INCLUDE file="inc/header.tmpl" />
<VAR match="VAR_SEL_FEATURES" replace="selected" />
<VAR match="VAR_SEL_FEATURE_CSD" replace="selected" />
<PARSE file="menu1.xml" />
<PARSE file="menu2-features.xml" />
<INCLUDE file="inc/content.tmpl" />
<h1>PAN GlobalProtect HIP</h1>
<p>The HIP ('Host Integrity Protection') mechanism is a security
scanner for the <a href="globalprotect.html">PAN GlobalProtect</a>
VPNs, in the same vein as <a href="csd.html">Cisco's CSD</a> and <a
href="juniper.html">Juniper's Host Checker (tncc.jar)</a>.</p>
<h2>How it works</h2>
<p>It is somewhat <i>less</i> intrusive than CSD or TNCC, because it
does not appear to work by downloading a trojan binary from the VPN
server. Instead, it runs a HIP report generator (built-in as part of
the official GlobalProtect VPN client software), which generates an
"HIP report" XML file.</p>
<p>HIP flow used in the official clients:</p>
<ol>
<li>Client authenticates and fetches the tunnel configuration from the GlobalProtect gateway.</li>
<li>Client runs HIP report generator and computes MD5 digest of report.</li>
<li>Client checks whether a HIP report is required (<code>/ssl-vpn/hipreportcheck.esp</code>), including its MD5 digest and gateway-assigned IP address in the report.</li>
<li>Gateway responds whether or not a HIP report is required (normally, it doesn't require a new one if a report with the same MD5 digest and same IP address have been submitted recently).</li>
<li>Client uploads the complete HIP report to (<code>/ssl-vpn/hipreport.esp</code>).</li>
<li>Server confirms acceptance of HIP report with a success message.</li>
</ol>
<p>If all goes well, the client should have the expected level of
access to resources on the network after these steps are
complete. However, two things can go wrong:</p>
<ul>
<li>Many GlobalProtect servers report that they require HIP reports
(#3 above), but don't actually enforce this requirement. (For this
reason, OpenConnect does not currently fail if a HIP report is
required but no HIP report script is provided.)</li>
<li>Many GlobalProtect servers will claim that the HIP report was
accepted successfully (#6 above) but silently fail to enable the
expected network access, presumably because some aspect of the
HIP report contents were not approved.</li>
</ul>
<h2>HIP support in openconnect</h2>
<p>OpenConnect supports HIP report generation and submission by passing the <code>--csd-wrapper=SCRIPT</code> argument with a shell script to generate a HIP report in the format expected by the
server. This shell script must output the HIP report to standard output and exit successfully (status code 0). The HIP script is called with the following command-line arguments:</p>
<pre>
--cookie: a URL-encoded string, as output by openconnect
--authenticate --protocol=gp, which includes parameters
--from the /ssl-vpn/login.esp response
--computer: local hostname, which can be overriden with
--openconnect local-hostname=HOSTNAME
--client-ip: IPv4 address allocated by the GlobalProtect VPN for
this client (included in /ssl-vpn/getconfig.esp
response)
--md5: The md5 digest to encode into this HIP report. All that
really matters is that the value in the HIP report
submission should match the value in the HIP report check.
</pre>
<h2>Generating/spoofing a HIP report</h2>
<p>An example <code>hipreport.sh</code> script is included in the
openconnect distribution.</p>
<p>Depending on how picky your GlobalProtect
VPN is, it may be necessary to spoof or alter some of the parameters
of the HIP report to match the output of one of the official
clients. In order to capture the contents of the official Windows
client's HIP reports, enable the highest logging level for the "PanGPS
Service", and then sift through the giant <code>PanGPS.log</code> file
(which should be in the same directory as the executables, normally
<code>c:\Program Files\PaloAlto Networks\GlobalProtect</code>) to find
the HIP report submission.</p>
<INCLUDE file="inc/footer.tmpl" />
</PAGE>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment