How to block URL’s on a Netscaler/ADC

All the vip’s you have on a Netscaler/ADC are probably web apps or web sites that do different things. All these are written in different languages and/or use different technologies. Anything that is software has bugs or vulnerabilities (discovered or not yet discovered).
Because of these , it became pretty often the situation where you need to block access to different URL’s because the app in the backend has some vulnerability.

On Citrix Netscaler/ADC is pretty easy to do using responder policies. But over time, your blocked url’s will increase in number and having a policy for each one will put pressure on the CPU. Or having 1 policy with a lot of “or’s” it is hard to read/manage.
Using patsets, your code is much cleaner ,easy to read or manage and it is not CPU intensive as the policy is evaluated only one time per request.
The URL’s you need to block might get pretty fancy and use special characters like “~”, “.” , “\” or multiple “/”. You will need a way to strip these characters because they could repeat many times in an url, and evaluate the url path. Also it is a good ideea that the policies are case insensitive.
Based on situation you might use a policy that use either “STARTSWITH” or “CONTAINS” , or even have a policy for each one of them.

So let’s go to an example as it is easier to understand.

Let’s say we want to block any URL that:

Startswith :
“/~/wp-json”
“/../wordpress//status”
“/status/\/ServerInfo”

and any URL that :

Contains :
“/abc-xyz”
“/manager/abc/Xyz”
“/Web-console//status”
“/~/../console//status”

For logging purposes i’ve used this:

add audit messageaction log-act1 ALERT "\"Block policies - SourceIP: \"+CLIENT.IP.SRC+\" VIP=\" + CLIENT.IP.DST +\" accessed URL:\" +HTTP.REQ.HOSTNAME.SERVER + HTTP.REQ.URL.HTTP_URL_SAFE"

I’ve created two patsets , one for each policy:

add policy patset pol_block_contains
bind policy patset pol_block_contains "abc-xyz" -index 10
bind policy patset pol_block_contains "managerabc-xyz" -index 20
bind policy patset pol_block_contains "web-consolestatus" -index 30
bind policy patset pol_block_contains "consolestatus" -index 40

add policy patset pol_block_startwith
bind policy patset pol_block_startwith "wp-json" -index 10
bind policy patset pol_block_startwith "wordpressstatus" -index 20
bind policy patset pol_block_startwith "statusserverinfo" -index 30

The above patsets are defined taking into account the special characters we are removing in the policy. Also because we wanted the policy to be case insensitive we need to use lower case in the patsets.

And two responder policies:

add responder policy pol_block_startswith q{HTTP.REQ.URL.PATH.TO_LOWER.STRIP_CHARS("/.~\\").STARTSWITH_ANY("pol_block_startwith")} DROP -logAction log-act1
add responder policy pol_block_contains  q{HTTP.REQ.URL.PATH.TO_LOWER.CONTAINS_ANY("pol_block_contains")} DROP -logAction log-act1

In the above policies you can see that for the startwith policy , we remove special characters we choose and it is also case insensitive.
In the contains policy we only make it case insensitive.
Both have as an action “DROP” (you can also have a custom 404 or whatever you want) and we also use a log action so we can monitor via syslog what url ‘s are dropped, source ip and the vip they are hitting.

Next , we have to bind these policies. If you want these to be applied to all vips you will have to bind it globally and as a type you use REQ_OVERRIDE, so these policies are the first responder policies that are evaluated. This makes sense when you want to drop traffic and protect all the vips/applications. You also can bind them on specific vip or vips.

bind responder global global_block_startswith 10 END -type REQ_OVERRIDE
bind responder pol pol_block_contains 20  END -type REQ_OVERRIDE

For testing you ca use curl or a browser, but if you have many URL’s to test, using a script would be easier and faster. You can write it on any language you like, i just think that for this particular job a Bash one would be just fine. So here it is:

#!/bin/bash
declare -a URL=( "/abc-xyz"
                 "/manager/abc/Xyz"
                 "/Web-console//status"
                 "/~/../console//status"
                 "/~/wp-json"
                 "/../wordpress//status"
                 "/status/\/ServerInfo"                  
               )
for i in "${URL[@]}"
do
P=$(/usr/bin/curl -k -sillent --path-as-is http://your-vip-ip$i )
echo $P
done

About the author

Mihai is a Network Aficionado with more than 10 years experience