  _   _                _ 
 | \ | | __ ___  _____(_)
 |  \| |/ _` \ \/ / __| |
 | |\  | (_| |>  <\__ \ |
 |_| \_|\__,_/_/\_\___/_|
 v0.1 alpha

[[[!!! PLEASE REFER TO THE WIKI INSTEAD !!!]]]
Yes, this readme is for v0.1 alpha, now is 0.45

 
|--{ Introduction }------------------------------------------------------------|


NAXSI is a module for nginx, the famous webserver/reverse-proxy/...
Its goal is to help people to secure their web application against attacks 
such as SQL Injection, Cross Site Scripting, Cross Site Request Forgery, 
Local & Remote file inclusions and such. 
The difference with most WAF (Web Applicative Firewalls) out there is that 
it does not rely on signatures to detect attacks. It is using a simpler model, 
where instead of trying to detect "known" attacks, it will detect unexpected 
characters in the HTTP request/arguments. Each kind of unusual character will 
increase the score of the request. If the request reaches a score that's 
considered "too high", the request will be denied, and the user will be 
redirected to a "forbidden" page. Yes, it works a bit like a spam system.


|--{  Why is it different ? }--------------------------------------------------|

NAXSI is different, because it works on a learning mode (read whitelist). 
Set the module in learning mode, scroll your site, and it will generate the 
necessaries whitelists to avoid false positives !
NAXSI doesn't relies on signatures, so it should be capable of defeating 
complex/unknown/obfuscated attack 
patterns.


|--{  How does it work  }------------------------------------------------------|


NAXSI relies on two separate configuration parts. 
		  * Core Rules : Located at HTTP server level configuration.
		  * WhiteLists & Specific Rules : Located at the HTTP location 
		    level configuration.

The first one is what we called 'core rules'. 
It's a set of rules that will contain all characters or regular expression 
that will increase the score of the request, for exemple :

MainRule "rx:<|>" "msg:html tag ?" "mz:ARGS|URL|BODY" "s:$XSS:8" id:1302;

This rule, will match on both < and > characters (rx:<|>), and will increase 
the score associated to the XSS threat (s:$XSS:8). This pattern will be matched
 against various zones of the request : ARGS (GET arguments), 
URL (the full URI), and BODY (POST arguments). Each rule is associated to 
unique ID (here, 1302), that is used for whitelisting. 
There is not "many" core rules (34 at the time of writting), and this set 
should normally not evolve.


On the other hand, we have a "local" configuration, which is to be defined 
"per site" (as NAXSI main goal is to work with NGINX as a RP), and which will 
define "how strict" the security policy of the site will be, as well as 
putting exceptions (whitelists) according the site specificities :

-----------------------------------8<-------------------------------------------
# Define to which "location" the user will be redirected when a request is
# denied.
DeniedUrl "/RequestDenied";

# Whitelist '|', as it's used on the /report/ page, in argument 'd'
BasicRule wl:1005 "mz:$URL:/report/|$ARGS_VAR:d";
# Whitelist ',' on URL zone as it's massively used for URL rewritting !
BasicRule wl:1008 "mz:URL";

# Check rules
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
-----------------------------------8<-------------------------------------------

Let's see this configuration again to clarify things :

* 'BasicRule' : This directive is used here to whitelist some rules. 
   As you can see (and expect !) you can be more or less laxist.
   For exemple, there is a directive (BasicRule wl:1008 ...) that will totally 
   disable the rule 1008 checking against URL. This rule is normally matching 
   the ',' character.

On the other hand, you can make extremly precise rules, as this one :
   BasicRule wl:1005 "mz:$URL:/report/|$ARGS_VAR:d";
   In the last exemple, we are whitelisting a rule, but only on one specific 
   argument of one specific webpage (the argument named 'd' of the page 
   '/report/').

As stated earlier, "local" configuration is also used to decide how 
"strict" one site (or one part of one site) will be, that is, what is 
the maximal tolerated page score before a request is denied :

CheckRule "$SQL >= 8" BLOCK;

This directive tells NAXSI that every request that has a 'SQL' score superior 
or equal to 8 will be denied.


|--{  Denied requests and whitelist generation  }------------------------------|

When a user's request is denied, he will be (internally) redirected to the page
defined in the configuration :
DeniedUrl "/RequestDenied";
This page will receive detailed informations on the rules that matched 
(it's logued in log files too), as well as both the request and the user 
context, without giving information to the user (thanks to nginx internal 
redirects). Actually the page at /RequestDenied will be called with arguments, 
like :

server=xxxx&uri=/vulnerable.php&ip=127.0.0.1&zone0=ARGS&id0=1010&var_name0=foo1&zone1=ARGS&id1=1011&...

If you have a closer look to the URL above, you will understand that the 
following information will be transmitted to the forbidden page :
 - Which rule matched on which argument, in which part of the request (ARGS, 
   BODY, URL, HEADERS ...)
 - Original URL and hostname
 - Client IP adress

Those information are given for both statistics generation purpose, as well as
for whitelist generation purposes.

Yes, actually that's a very important aspect of NAXSI : As it is heavily 
relying on whitelist for configuration, the easiest the whitelist generation is
, the easier the configuration is ! The thing being that the DeniedUrl page 
receives enough information to generate a set of whitelisting rules that will 
allow the request that was blocked to be allowed in the future.

To make it clear : you can generate the whole NAXSI configuration, only by 
naviguating to the site, or even better, by using a clever crawler !
When you will do a navigation session on the website you want to create rules 
for, if you are in "LearningMode", some requests might be (and will be) tagued
 as blocked. They should be blocked, because, for example, the developpers 
(damn!) decided to massively use '|' for URL rewritting, and NAXSI 
will dislike this.

So, as you are in learning mode (but the idea is the same when LearningMode is
disabled), NAXSI will log the fact that the request was blocked because of the
presence of multiples '|' in the URL. Then, with a simple script (a python 
script is provided), you can parse the log files and generate the appropriate 
whiterules to allow legitimate (false positive) requests.

The more tricky part when talking about NAXSI and its whitelist, is when we 
come to sites that allows a LOT of wide user input : Comments, Registration 
forms and things like this. For this, either a carefull navigation, submitting 
real content is required (so that NAXSI will trigger every plausible rule on 
each kind of form field) or the usage of a clever crawler is required. 
In the worst, case, it will require to do a real navigation session to generate
the appropriate whitelists.

|--{  Statistics, Reporting and so on ...  }-----------------------------------|

This is a crucial part of any WAF, and this is not done yet ! 
But the good point is that, thanks to the principle of calling an external page
 that receives all the informations about every denied request, it is fairly 
simple to write your custom <insert your favorite language here> webpage to 
take care of the statistics / reporting.


|--{  3 .. 2 .. 1 .. practice !  }---------------------------------------------|

Ok, now, let's have a look at the practice ! Let's admit we want to create a 
setup for a website. I won't cover the basics of setting up nginx as a reverse 
proxy, but rather focus on NAXSI configuration. If you have a "normal" web 
site, with no fancy URL rewritting or strange things, the default configuration
should do the work, but let's have a look at website with fancy rewritting, 
and complex user forms.


To make things easier, a good point is that we can 'fool' nginx and the OS into
 thinking that he is already the reverse proxy for the website, so that we can
setup the configuration without any risk of impacting the production servers, 
so here we go :


/etc/nginx/site-enabled/default:
-----------------------------------8<-------------------------------------------
server {
proxy_set_header Proxy-Connection "";
resolver X.Y.Z;
listen       *:80;
access_log  /tmp/nginx_access.log;
error_log  /tmp/nginx_error.log debug;

location / {
# specific site config
  LearningMode;
  SecRulesEnabled;
  DeniedUrl "/RequestDenied";

  ## check rules
  CheckRule "$SQL >= 8" BLOCK;
  CheckRule "$RFI >= 8" BLOCK;
  CheckRule "$TRAVERSAL >= 4" BLOCK;
  CheckRule "$XSS >= 8" BLOCK;
# /specific site config
  proxy_pass http://xx.xx.xx.xx;
  proxy_set_header Host "www.xxx.com";
}

location /RequestDenied {
     proxy_pass http://127.0.0.1:4242/denied_page.php;
   }
}
-----------------------------------8<-------------------------------------------


Our configuration file is extremly similar to any nginx configuration, except:
- We defined a NAXSI "per site" configuration. This is what will determine  how
  NAXSI will behave for this site.
- We define a location that will be used to redirect fobidden pages. Here, 
  I have an apache instance listening on lo:4242

The NAXSI "per location" configuration, simply defines :
- CheckRule : The maximum score for each kind of "threat"
- The "LearningMode" directive is here to make the learning easier. By default,
  NAXSI will stop processing a request as soon as it hits one of the 'BLOCK' 
  scores. With this directive, it will go through every rules, making 
  whitelist generation easier, while allowing the request to pass, even if 
  tagued as "BLOCKED", to make learning easier.

- The 'SecRulesEnabled' directives tells that NAXSI should be activated for 
  this location. In this way, you can decide to active / desactivate it easily
  for location X or Y. (For exemple, you might not want a WAF on your 
  back-office ?)
- DeniedUrl : We tell NAXSI were to redirect the user when a request is blocked.

and we need to add this line in the http section of nginx.conf :
--------------------------------8<--------------------------------------
include	   /etc/nginx/sec-rules/core.rules;
--------------------------------8<--------------------------------------
The "core.rules" file is provided with NAXSI, and contains all the "patterns".

So, here we go ! Let's start

We can now fool the OS into thinking that xxx.com is on 127.0.0.1, edit /etc/hosts. We are ready for configuration ! Fire up your favorite browser at xxx.com and start navigation. 

<roll roll>

As you should be in "learningmode", NAXSI will allow all the request, even if 
they reach a blocking score. As well as letting them pass, it will, as well, 
"forward" the request (like nginx's post_action directive) to your DeniedUrl,
as well as the original blocked URL (in headers) and the generated blocking 
details. In this way, the web backend is abble to generate the white-lists, 
and reload nginx 'on the fly' with the new generated whitelist rules ;)

The 'web' part is not written yet (I suck at html), but you can yet proceed
in a different way :


In your nginx's log file (if set as debug), you will see a lot of lines like
this one appear :
-----------------------------------8<-------------------------------------------
2011/07/11 17:12:27 [debug] 18653#0: *7 NAXSI_FMT: server=&uri=/skin/frontend/default/xxx/images/interface/fleche-grise.gif&ip=127.0.0.1&zone0=HEADERS&id0=1005&var_name0=cookie&zone1=HEADERS&id1=1008&var_name1=cookie&zone2=HEADERS&id2=1009&var_name2=cookie&zone3=HEADERS&id3=1010&var_name3=cookie&zone4=HEADERS&id4=1011&var_name4=cookie&zone5=HEADERS&id5=1308&var_name5=cookie&zone6=HEADERS&id6=1309&var_name6=cookie&zone7=HEADERS&id7=1313&var_name7=cookie
-----------------------------------8<-------------------------------------------

So, once you think you've done a reasonable crawling on your site, you can 
launch the "rules generator" [destination rules file] [nginx's log file]:
-----------------------------------8<-------------------------------------------
bui@zeroed:~$ ./rules_generator.py
RANGE for ID=1000,ZONE=URL, range=0-4
# for rule 1000, we have 4 elements in zone URL
#duplicate for id 1000, delete (4 elems)
RANGE for ID=1002,ZONE=URL, range=1-89
...
{'id': '1313', 'uri': '', 'var_name': 'cookie', 'zone': 'HEADERS'}]
-----------------------------------8<-------------------------------------------

Rules generator default output file is /tmp/RT_naxsi.tmp, and looks like :
-----------------------------------8<-------------------------------------------
bui@zeroed:/home/bui/secdev/nginx/web: cat /tmp/RT_naxsi.tmp  | grep ^Basic
BasicRule wl:1000 "mz:$URL:/skin/frontend/default/lepape/images/titre_notre_selection2.gif|URL";
BasicRule wl:1002 "mz:URL";
BasicRule wl:1008 "mz:URL";
BasicRule wl:1005 "mz:$HEADERS_VAR:cookie";
BasicRule wl:1008 "mz:$HEADERS_VAR:cookie";
BasicRule wl:1009 "mz:$HEADERS_VAR:cookie";
BasicRule wl:1010 "mz:$HEADERS_VAR:cookie";
BasicRule wl:1011 "mz:$HEADERS_VAR:cookie";
BasicRule wl:1308 "mz:$HEADERS_VAR:cookie";
BasicRule wl:1309 "mz:$HEADERS_VAR:cookie";
BasicRule wl:1313 "mz:$HEADERS_VAR:cookie";
-----------------------------------8<-------------------------------------------

What happened just here ?  The Rule Generator parsed nginx log file,
extracted all the "denied urls", and generated (and then factorized)
whitelist rules from your session !  If you did an exhaustive enough
crawling / browsing session, your ruleset might be complete enough and
ready for production !


|--{  Detail configuration  }--------------------------------------------------|

Here, I will go through all the directives supported by NAXSI.

|--{  Location configuration  }------------------------------------------------|

LearningMode : By default, NAXSI will stop parsing a request as soon
	     as it reaches a score that will block the request.  When
	     LearningMode is enabled, the request will be matched
	     against every possible rules. This directive should be
	     used when configuring NAXSI ONLY (a.k.a : generating
	     whitelists)

SecRulesEnabled : If the directive is not present, NAXSI will not
inspect anything.  SecRulesDisabled : If the directive is present,
NAXSI will not inspect anything.  (The two rules above are here for
easier usage in production environnment, when you want to be abble to
simply enable / disable the module)



DeniedUrl "/location" : This directive is used to tell NAXSI where the
	  	        user should be redirected when a request is
	  	        blocked.  The location should be present in
	  	        NGINX configuration for this to work, as we
	  	        are relying on nginx's internal redirect
	  	        feature.


BasicRule : The 'main' thing you should care about. This can be used,
	    either to add some whitelist, or to create some specific
	    rules for a location (might be usefull if you have super
	    crappy websites).  Here are some examples of possible
	    BasicRule syntaxes :

- BasicRule wl:1005 "mz:$URL:/bar/|$ARGS_VAR:foo" : Whitelist rule 1005 on 
  the GET "foo" arg of page /bar/
- BasicRule wl:1005 "mz:$URL:/bar/|ARGS" : Whitelist rule 1005 on the every 
  GET arg of page /bar/
- BasicRule wl:1005 : Globally disable rule 1005 on the location
- BasicRule wl:1005 "mz:HEADERS" : Whitelist rule 1005 for all HEADERS
- mz supports keywords like ARGS (global GET args), BODY (global POST args), 
  URL (url, rly), $ARGS_VAR (named GET arg), $BODY_VAR (named POST arg), 
  HEADERS (HTTP headers), $HEADERS_VAR (named header var)


CheckRule : Used to determine when the request should be blocked, for
example : CheckRule "$SQL >= 8" BLOCK : If the $SQL score is superior
or equal to 8, the request will be blocked.


|--{  Main configuration  }----------------------------------------------------|


In main configuration, a.k.a core rules, there is only one directive :

MainRule "str:/*" "msg:mysql comment (/*)" "mz:BODY|URL|ARGS" "s:$SQL:8" id:1003;
This specific rule for example, will be matched against GET,POST
arguments, as well as the URL. If the '/*' string is found, the $SQL
score will be increased by 8.  Sounds pretty simple right ? But you
can as well do some tricky things !

MainRule negative "rx:application/x-www-form-urlencoded|multipart/form-data" "msg:foobar test pattern" "mz:$HEADER_VAR:Content-type" "s:$SQL:42" id:1999; 
This one is a negative rule (means that the score will be applied if the rule 
DOES NOT match). This one is used to prevent strange content types on POSTs.
To litteraly translate it, it means : If a "Content-type" HTTP header is 
present, check weither it's 'application/x-www-form-urlencoded' or 
'multipart/form-data' (you noticed the rx: keyword, yes it means that it's a 
regular expression). If the content-type is different, then increase $SQL score
by 42.


