NetScaler Cookies and Tracking EPA Scans

NetScaler Cookies

Let's start with examining the currently existing cookies and move towards some nFactor cookies, why we need them, how to implement them, what we should and shouldn't use them for.

Depending on your particular usage of the AAA Protection feature of the NetScaler one set of cookies or another is utilized. We'll not discuss any LB Persistance, AppFW or Temporary error cookies, but only cookies related to Session & Authentication.

Below you can find a table for reference:

Cookies

Cookie NameNetScaler UsageDerived
NSC_TMACSets "action" attribute URL for submition. This should defaults to "/cgi/tmlogin"
NSC_TASSUsed in Auth flows for providing internal authorization code plus initial FQDN\URL or URL store redirection during SAML\OTP login flows.
NSC_EPACEPA Scan cookie, EPA session identificationSame value as NSC_TMAS Cookie
NSC_TMASUsed during the nFactor authentication, indicating AAA Authorized Session of TM Type.Authentication State. Session ID.
NSC_AAACUsed during Clientless or Native Client authentication, indicating AAA Authorized Session of SSL VPN Type.User session cookie. Session ID.

The NetScaler uses a Cookie manager with two types of cookies Citrix.Fei.ClientCookies and Citrix.Fei.ServerCookies, creating esentially a internal cookie map list (storing it in a cookie jar). The below diagrams illustrate this with Proxied Server 106, but the concept is the same with Gateway implementations:

Image
Image

Custom Cookies

Creation of custom cookies have been covered in the following Citrix doc. The article covers a cookie with validity of 1000 days and a Static Value "CookieValue". We're going to change that, although in some cases this is absolutely sufficient or even considered an overkill.

The custom cookie creation is implemented via the Storefront Authentication SDK javascript API with custom credential handler provided (a hook). CTXS.ExtensionAPI.addCustomCredentialHandler - github.com.

There are a few articles on the web illustrating how to use the credential handler:

One thing is for sure, I'm definitely late to the party.

Observability

The current discussion is coming from an issue I had with the NetScaler EPA policy-based scanning.

EPA Scanning

The Citrix NetScaler has the ability to check various conditions on a user device when it attempts to connect to a AAA Protected Resource. Based on the results of those conditions the NetScaler decides if a client is permitted to attempt a login, if the client is blocked or if the client is to be quarantined. Citrix calls this "Endpoint Analysis", or EPA.

The NetScaler does this by running a client on the user’s machine. It connects to the NetScaler, receives a list of conditions, checks those conditions on the client device and then sends the NetScaler a result of pass or fail.

Client-side checks required for the access should be kept secret for minimizing threat actors probing.

When the result is fail (client is blocked, because it can be also quarantined) the end-user is presented with an error message that could be reported to the IT department for assistance.

Image

The error message contains a Case ID uniquely identifying the scan.

cat /var/log/ns.log | grep <CASE_ID> 

ns.log:

SSLVPN Message 1183 0 :  "CaseID: 0399b - Client_security_expression "sys_0_REG-NUM_PATH_==_HKEY\\_LOCAL\\_MACHINE\\\\SOFTWARE\\\\Citrix\\\\Secure\ Access\ Client\\\\Internal\\\\AOConnected_VALUE_==_1" -Client_security_check Fail"
SSLVPN Message 1184 0 :  "CaseID: 0399b - Client_security_expression "sys_0_REG-NUM_PATH_==_HKEY\\_LOCAL\\_MACHINE\\\\SOFTWARE\\\\Citrix\\\\Secure\ Access\ Client\\\\Internal\\\\AllowEPA_VALUE_==_0" -Client_security_check Fail"

Tracked (on NetScaler side) the Case ID will give you the exact requirement the device does not meet. (hardly matters in our example, but the above is from registry value checks)

To see the logs you'll need debug logging enabled and the security log param set:

set audit syslogParams -logLevel ALL
set aaa parameter -aaaSessionLoglevel DEBUG -aaadLoglevel DEBUG
set vpn param -clientSecurityLog ON

Client side Logging is located at:

AppData\Local\Citrix\AGEE\nsepa.txt

The issue we faced is observability & data analysis related. The EPA logs from the NetScaler lack any user context information. We would like to have reporting on device compliance having username association with the EPA Session. Moreover that our authentication flows consist of several cascading EPA scans each resulting in different application privileged access (Smart Access from Virtual Apps & Desktops). Especially when you have tiered access (in these cases the users cannot report Case ID).

Who, when and what tiered level of access has received based on device compliance.

There are two options for introducing username context to EPA scans:

  • Adding Only Username Schema before the scan
  • Creating a custom cookie flow

Note: In some configuration scenarios username is not available depending if EPA terminates the nFactor flow. EPA position in the nFactor makes a difference as well (first, middle, last factor). I thought the EPA could be only first and last factor, but it appears to be working in the middle of the nFactor as well. In the scenario where EPA is first action, no username exist during the scan (username 'anonymous' is used).

Authentication Flow

Below is example authentication flow for reference:

Image

A few things to note:

  • Above is a logical Auth Flow, not a nFactor binding diagram
  • For the nFactor, Quarantine groups were utilized with weight assignment

Session Flow

You can use the below Audit Policy to track a login session:

add audit messageaction AUDIT_EPA INFORMATIONAL "\"LOGTYPE=[AAA_EPA] NSC_EPAC=[\" +  HTTP.REQ.COOKIE.VALUE(\"NSC_EPAC\") + \"] CASEID=[\" +  HTTP.REQ.COOKIE.VALUE(\"NSC_EPAC\").REGEX_SELECT(re/.{5}$/) + \"] NSC_TMAS=[\" +  HTTP.REQ.COOKIE.VALUE(\"NSC_TMAS\") + \"] SESSIONID=[\" +  AAA.USER.SESSIONID + \"] NSC_AAAC=[\" +  HTTP.REQ.COOKIE.VALUE(\"NSC_AAAC\") + \"]  AAA_USERNAME=[\" + AAA.USER.NAME + \"] SRC_IP=[\" +  CLIENT.IP.SRC  + \"] DST_IP=[\" +  CLIENT.IP.DST  + \"] NSC_TRAA=[\" +  HTTP.REQ.COOKIE.VALUE(\"NSC_TRAA\") + \"] \"" -logtoNewnslog YES
add responder policy RES_POL_AUDIT_EPA "\nAAA.USER.SESSIONID.LENGTH.EQ(32)||HTTP.REQ.COOKIE.VALUE(\"NSC_TMAS\").LENGTH.EQ(32)||HTTP.REQ.COOKIE.VALUE(\"NSC_EPAC\").LENGTH.EQ(32)||AAA.USER.NAME.LENGTH.EQ(0).NOT" NOOP -logAction AUDIT_EPA
bind vpn vserver GW_VS_GATEWAY -policy RES_POL_AUDIT_EPA -priority 100 -gotoPriorityExpression END -type REQUEST

We're tracking the following components:

  • NSC_EPAC
  • CASEID
  • NSC_TMAS
  • SESSIONID
  • NSC_AAAC
  • USERNAME
  • SRC_IP
  • DST_IP

You can tail the logs with the below CLI:

tail -f /var/log/ns.log | grep -E "nFactor|AAA_EPA|expression"
Image

From the NS Logging we have username and the failed or succeeded checks. Our custom logging will reveal part of the process. We can see the EPA cookie used by the scan has the same value as the TMAS Cookie and the Case ID is the last 5 symbols of that same cookie. After successfull scan and further completed authentication, the TMAS Cookie will be removed and NSC_AAAC Cookie will be created. The SessionID on the NetScaler will be derived from NSC_AAAC cookie's value.

Although nFactor has user context, the audit logging cannot report it until NSC_AAAC Cookie is set.

Image

As a result the session has the following cookie transformations.

This is strictly regarding vpn vserver implementation. Web Resources protected by AAA will not have the NSC_AAAC cookie.

Image

Because the NSC_AAAC & the NSC_TMAS are not overlapping anywhere and no username exists in audit logging before the NSC_AAAC, and we couldn't use "Username" schema (EPA being first factor), we took a different approach. Creating a session cookie that will remain through the whole nFactor flow. We picked the name NSC_TRAA, but of course you can change the name as you like. Base on this cookie we'll be able to track our session.

Image

nFactor Cookie

Let's get to the point:

  • Create LoginSchema with custom Credential ID nsg_cookie (the hook will listen for that ID)
  • Create Custom Theme
  • Add the script to the script.js path (/var/netscaler/logon/themes/RfWebUI_custom/script.js)

Schema:

<?xml version="1.0" encoding="UTF-8"?>
<AuthenticateResponse xmlns="http://citrix.com/authentication/response/1">
<Status>success</Status>
<Result>more-info</Result>
<StateContext></StateContext>
<AuthenticationRequirements>
<PostBack>/nf/auth/doAuthentication.do</PostBack>
<CancelPostBack>/nf/auth/doLogoff.do</CancelPostBack>
<CancelButtonText>Cancel</CancelButtonText>
<Requirements>

<Requirement>
<Credential><ID>nsg_cookie</ID><Type>nsg_cookie</Type></Credential><Label><Text>Logon Type:</Text><Type>Plain</Type></Label>
</Requirement>

<Requirement>
<Credential><ID>loginBtn</ID><Type>none</Type></Credential><Label><Type>none</Type></Label><Input><Button>Log On</Button></Input>
</Requirement>

</Requirements>
</AuthenticationRequirements>
</AuthenticateResponse>

Script.js:

function makeid(length) {
  let result = "";
  const characters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  const charactersLength = characters.length;
  let counter = 0;
  while (counter < length) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
    counter += 1;
  }
  return result;
}
CTXS.ExtensionAPI.addCustomCredentialHandler({
  // The name of the credential, must match the type returned by the server
  getCredentialTypeName: function () {
    return "nsg_cookie";
  },
  // Generate HTML for the custom credential
  getCredentialTypeMarkup: function (requirements) {
    var div = $("<div></div>");
    $(document).ready(function () {
      var CookieValue = makeid(32);

      //Set cookie expiration
      var exhours = 1;
      var d = new Date();
      d.setTime(d.getTime() + exhours * 60 * 60 * 1000);
      var expires = "expires=" + d.toUTCString();
      document.cookie =
        "NSC_TRAA=" + CookieValue + ";" + expires + ";Secure;path=/";

      //Submit form
      document.getElementById("loginBtn").click();
    });
    return div;
  },
});

We're creating a 32 character cookie with expiration of an hour.

Image

I would create the cookie in a separate 'cookie' PolicyLabel making it in fact a second factor, while the first factor remains NO_AUTHN.

add authentication Policy AAA_POL_START_NOAUTH -rule HTTP.REQ.IS_VALID -action NO_AUTHN

add authentication policylabel AAA_LBL_SET_COOKIES -loginSchema AAA_LGS_COOKIE
bind authentication policylabel AAA_LBL_SET_COOKIES -policyName AAA_POL_SET_COOKIE -priority 100 -gotoPriorityExpression NEXT -nextFactor ###########

bind authentication vserver AAA_VS_COOKIE -policy AAA_POL_START_NOAUTH -priority 100 -nextFactor AAA_LBL_SET_COOKIES -gotoPriorityExpression NEXT

This does not work with SSL VPN implementations! The Workspace App has a chromium based browser in it (that's why it works), while Citrix Secure Access Client doesn't know how to handle the javascript.

Log data will look a bit different:

Image
  • NSC_TRAA tracing the whole session even before having a username
  • Username logged during and after the security posture scan, easily identifying NSC_TAMS session and NSC_AAAC session.

Now this can be send to the ELK stack and structured for data analytics!

Tips & Usage

It is not recommended to use any authorization or access logic based on this cookie, although it could be rewritten & encrypted with session info by the NetScaler, and consequently used afterwards. Have in mind that the script.js will be sent to client (browser) side.

Image

Cookie Flags

Secure flag is set on our cookie, although not gaining much from it. The HttpOnly is not possible with the current method. An HttpOnly cookie means that it's not available to scripting languages like JavaScript (which we use and defeats the purpose), allowing access only over HTTP. I would recommend it for all session cookies. The majority of XSS attacks target theft of session cookies not using the HttpOnly flag.

Fun fact: HttpOnly flag for cookies was designed by the Internet Explorer developers back in 2002 and implemented in IE 6sp1 for the first time.

Configuring the HttpOnly flag Citrix doc & Citrix doc

set aaa parameter -httpOnlyCookie ENABLED

Conclusion

There is more than one way to accomplish a task and the mere process of researching is teaching you so much!

I hope this has been helpful to you and I would like to thank you for reading!

I'll end this with a quote from The Sopranos.

Log off, that cookie shit makes me nervous! - Anthony Soprano