My customer allows a small number of senior engineers to operate their SCADA platform remotely using Citrix Gateway to access Citrix Virtual Apps and Desktops published resources. The customer’s security team asked if a “duress code” could be implemented that would silently trigger alarms and change certain properties of the environment.

The concept, they explained, was that a remote user placed under duress could reverse their RADIUS PIN during login, and Citrix ADC could somehow detect this situation and trigger changes. Ideally, those changes would include Citrix ADC writing entries to syslog for their security team to act on, directing the user to a server with session recording enabled, disabling access to critical applications, or switching the user to an entirely separate decoy environment where actions that might otherwise lead to loss or damage wouldn’t be executed.

With nFactor guidance from Dileep in our engineering team, the “duress code” functionality is now working perfectly, and as I imagine such functionality could be widely applicable, I’ve decided to share it here.

User presentation

The “duress code” logic initially presents the user with a standard MFA login screen requesting the user’s credentials as usual. From the user’s perspective, Citrix ADC appears to validate the credentials as usual with access being allowed or rejected and no visible change in behaviour.

Reversed-PIN (R-PIN) access

Behind the scenes, the initial login screen has a unique schema that uses a custom policy extension to reverse the user’s RADIUS PIN and attempt authentication using a RADIUS action that, on success, places the user into a custom default group. Citrix ADC then grants access if LDAP authentication access is successful.

In a typical situation, the RADIUS server would reject a reversed PIN, and success at this stage indicates the PIN was backwards when submitted by the user and a duress condition exists. By placing the user in a custom group, we can easily communicate the duress condition as Citrix ADC evaluates later policies.

Two responder policies bound to Citrix Gateway check for the group membership. The first calls a custom logging action that sends a definable text string to syslog and alerts the security team, the second places the username into an automatically expiring variable which prevents future logins from using PIN (rather than R-PIN) for four hours.

Session policies bound to the custom group in Unified Gateway’s AAA Groups feature now take precedence. As Smart Access supplies the session policy name to Citrix Virtual Apps and Desktops (formerly XenApp & XenDesktop) policies can now be triggered to disable critical applications or to place the user on a server with session recording enabled. If a decoy environment is preferred, then an alternative StoreFront server URL can be set in the session policy.

See for more details on SmartAccess

Standard PIN access

If R-PIN fails (and it should unless there is a duress condition) a schema to check the PIN in the correct order is applied, and one of two authentication policies trigger to check the PIN. The first policy processes the login as usual whereas the second rejects the login even if the credentials supplied are correct.

Allowing the standard PIN if R-PIN has not been used

The first authentication policy executes if the $slp variable is not set for the user and processes the login as usual. R-PIN use sets the $slp variable, and this check ensures that R-PIN has not used recently and a duress condition does not currently exist for the user.

Blocking the standard PIN if R-PIN has been used

The blocking policy executes if the username has been added to the $slp variable by a recent R-PIN login. This logic creates a time-based lock preventing PIN-based login for a definable period after R-PIN has been used and helps to prevent the duress system from being detected. In my example configuration, I expire the variable (which is acting as a timelock) after four hours.

To block logins the policy directs authentication to a non-existent LDAP server. Alternatively, the blocking logic could direct the user to the same authentication sequence used by R-PIN, set the same custom default group, and thereby cause the user to receive the same restricted access with the PIN forwards or backwards after R-PIN use and variable population.

Authentication sequence


Create the R-PIN policy extension

To check the R-PIN (reversed PIN), Citrix ADC must calculate it using the RADIUS PIN + OTP entered in the “passwd1” field of the login form. A new function is required to achieve this.

New functions can be created using Citrix ADC’s AppExpert Policy Extensions feature. Here, we create a text function called “RPIN” that reverses the first four characters of a string.

The function has been tested using standard RADIUS and challenge-response based authentication. The PIN is expected to be static and four characters in length. The user must be instructed not to use a palindromic number that is the same when written forwards or backwards (such as 1221).

Create the $slp variable used to disable PIN-based authentication after RPIN use

The $slp variable is a map that can contain up to 50 entries. Entries are set to expire if they are not referenced within 4 hours.

add ns variable slp -type “map(text(128),ulong,50)” -init 1 -expires 14400

A responder policy and associated action can now be created to populate the variable when a user in the custom “duress_group” completes a login.

add ns assignment activate_slp -variable “$slp[http.REQ.USER.NAME]” -set 1

add responder policy activate_slp_pol “HTTP.REQ.USER.IS_MEMBER_OF(\”duress_group\”)” activate_slp

bind vpn vserver UG_VPN_myUnifiedGateway -policy activate_slp_pol -priority 100 -gotoPriorityExpression NEXT -type REQUEST

Create the alarm message that will be recorded in Syslog

Custom Syslog messages can be generated using an audit policy and associated action that is triggered by requests made to the Unified Gateway vServer by users within the custom group.

add audit messageaction duress_event_activated EMERGENCY “\”ATTENTION DURESS CODE ACTIVATED BY \” + HTTP.REQ.USER.NAME “

add responder policy log_for_user_pin_pol “HTTP.REQ.USER.IS_MEMBER_OF(\”duress_group\”)” NOOP -logAction duress_event_activated

bind vpn vserver UG_VPN_myUnifiedGateway -policy log_for_user_pin_pol -priority 110 -gotoPriorityExpression NEXT -type REQUEST

The authentication vServer, nFactor profile, and authentication login can now be created.

Create authentication vServer, nFactor profile, and bind this to Unified Gateway

add authentication vserver auth SSL

bind ssl vserver auth -certkeyName “RSA Cert”

bind ssl vserver auth -eccCurveName P_256

bind ssl vserver auth -eccCurveName P_384

bind ssl vserver auth -eccCurveName P_224

bind ssl vserver auth -eccCurveName P_521

add authentication authnProfile DURESS -authnVsName auth -AuthenticationHost host

set vpn vserver UG_VPN_myUnifiedGateway -authnProfile DURESS

Create a login schema for the reversed-PIN, standard PIN, and for LDAP

# The xml schema file used here is built-in and included with Citrix ADC.

add authentication loginSchema dual_auth_using_RPIN -authenticationSchema “/nsconfig/loginschema/LoginSchema/DualAuth.xml” -passwdExpression “HTTP.REQ.BODY(1000).TYPECAST_NVLIST_T(\’=\’,\’&\’).VALUE(\”passwd1\”).RPIN”

add authentication loginSchema noschema_check_PIN -authenticationSchema noschema -passwdExpression “HTTP.REQ.BODY(1000).TYPECAST_NVLIST_T(\’=\’,\’&\’).VALUE(\”passwd1\”)”

add authentication loginSchema ldap_schema -authenticationSchema noschema -passwdExpression “HTTP.REQ.BODY(1000).SET_TEXT_MODE(URLENCODED).TYPECAST_NVLIST_T(\’=\’,\’&\’).VALUE(\”passwd\”).DECODE_USING_TEXT_MODE”

Create two RADIUS policies, one that sets a default duress group and another which does not

add authentication radiusAction radius_server_sets_group -serverIP RADIUS_SERVER_IP -serverPort 1812 -radKey 954f5d31b0ac689644c20cd9fdb874a058d7314374528545ea13dcc660995b0c -encrypted -encryptmethod ENCMTHD_3 -defaultAuthenticationGroup duress_group

add authentication Policy duress_policy -rule true -action radius_server_sets_group

add authentication radiusAction radius_server_wo_set_group -serverIP RADIUS_SERVER_IP -serverPort 1812 -radKey 2fe424b9e55ff6232676badf24aee365f4c6d9bfd82d73c30c92994984e665a1 -encrypted -encryptmethod ENCMTHD_3

add authentication Policy n_duress_radius_policy -rule true -action radius_server_wo_set_group

Create two LDAP authentication policies, one working and another that is broken

add authentication ldapAction AD_servers -serverIP LDAP_SERVER_IP -authTimeout 30 -ldapBase “dc=mycoolcompany,dc=local” -ldapBindDn ns_ldap_service_account@mycoolcompany.local -ldapBindDnPassword 3baef4b867435f37d956171077caf623b4c1a568b8f4169872ef9b9943fdabc7

-encrypted -encryptmethod ENCMTHD_3 -ldapLoginName sAMAccountName -groupAttrName memberOf -subAttributeName CN -requireUser NO

add authentication Policy ldap_policy -rule true -action AD_servers

add authentication ldapAction fake_ldap_server -serverIP -ldapBase broken -ldapBindDn broken -ldapBindDnPassword 1849c37fa390d63c6e768eb5c8e9438634e5088f79f1d613d954cde5e609ff60 -encrypted -encryptmethod ENCMTHD_3

add authentication Policy ldap_broken_policy -rule true -action fake_ldap_server

bind authentication vserver auth -policy dual_auth_using_RPIN -priority 100 -gotoPriorityExpression END

# Create nFactor logic

add authentication policylabel ldap_policylabel -loginSchema ldap_schema

bind authentication policylabel ldap_policylabel -policyName ldap_policy -priority 10 -gotoPriorityExpression NEXT

bind authentication vserver auth -policy duress_policy -priority 10 -nextFactor ldap_policylabel -gotoPriorityExpression NEXT

add authentication Policy noauth_policy -rule “http.req.url.contains(\”\”)” -action NO_AUTHN

add authentication policylabel n_duress_radius_policylabel -loginSchema noschema_check_PIN

bind authentication policylabel n_duress_radius_policylabel -policyName n_duress_radius_policy -priority 100 -gotoPriorityExpression NEXT -nextFactor ldap_policylabel

add authentication policylabel ldap_broken_policylabel -loginSchema noschema_check_PIN

bind authentication policylabel ldap_broken_policylabel -policyName ldap_broken_policy -priority 100 -gotoPriorityExpression NEXT

add authentication Policy allow_not_duress_login -rule “$slp.valueExists(” -action NO_AUTHN

add authentication Policy block_not_duress_policy -rule “$slp.valueExists(” -action NO_AUTHN

add authentication policylabel not_duress_policylabel -loginSchema noschema_check_PIN

bind authentication policylabel not_duress_policylabel -policyName allow_not_duress_login -priority 10 -gotoPriorityExpression NEXT -nextFactor n_duress_radius_policylabel

bind authentication policylabel not_duress_policylabel -policyName block_not_duress_policy -priority 20 -gotoPriorityExpression NEXT -nextFactor ldap_broken_policylabel

bind authentication vserver auth -policy noauth_policy -priority 20 -nextFactor not_duress_policylabel -gotoPriorityExpression NEXT

Citrix TechBytes – Created by Citrix Experts, made for Citrix Technologists! Learn from passionate Citrix Experts and gain technical insights into the latest Citrix Technologies.

Click here for more TechBytes and subscribe.

Want specific TechBytes? Let us know!