Initial Commit

This commit is contained in:
root
2017-02-25 23:55:24 +01:00
commit 1fe2e8ab62
4868 changed files with 1487355 additions and 0 deletions

1
naxsi-0.55.3/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
t/* linguist-vendored

45
naxsi-0.55.3/.travis.yml Normal file
View File

@@ -0,0 +1,45 @@
language: C
os:
- linux
cache:
- apt
- pip
env:
global:
- VER_NGINX=1.9.11
- COV=1
compiler:
- clang
- gcc
addons:
apt:
packages:
- lcov
- cpanminus
install:
- gem install coveralls-lcov
- cd ./naxsi_src
- if [ "$CC" == "clang" ]; then COV=0; fi
- make
- cpanm -v --notest Test::Nginx
before_script:
- lcov --directory "../nginx-${VER_NGINX}" --zerocounters
script: make test
after_failure:
- cat ../t/servroot/logs/error.log
- cat /tmp/ngx_error.log
- cat /tmp/ngx_access.log
after_success:
- lcov --list naxsi.info
- coveralls-lcov --repo-token ${COVERALLS_TOKEN} naxsi.info

108
naxsi-0.55.3/Makefile Normal file
View File

@@ -0,0 +1,108 @@
CORE_VERS := $(shell grep NAXSI_VERSION naxsi.h | cut -d '"' -f 2)
MOD_PATH := $(shell pwd)
TMP_DIR := /tmp/nginx/
# Keys for coverity
CAN :=
CAK :=
#Set to 1 if you want coverage report
COV ?= 1
#Allows to force for specific UT only
#TEST := "29*.t"
NGINX_VERS := "1.9.11"
NGINX_OPTIONS="--error-log-path=/tmp/naxsi_ut/error.log"
NGINX_OPTIONS+="--conf-path=/tmp/naxsi_ut/nginx.conf"
NGINX_OPTIONS+="--http-client-body-temp-path=/tmp/naxsi_ut/body/"
NGINX_OPTIONS+="--http-fastcgi-temp-path=/tmp/naxsi_ut/fastcgi/"
NGINX_OPTIONS+="--http-log-path=/tmp/naxsi_ut/access.log"
NGINX_OPTIONS+="--http-proxy-temp-path=/tmp/naxsi_ut/proxy/"
NGINX_OPTIONS+="--lock-path=/tmpnginx.lock"
NGINX_OPTIONS+="--pid-path=/tmp/naxsi_ut/nginx.pid"
NGINX_OPTIONS+="--modules-path=/tmp/naxsi_ut/modules/"
NGINX_OPTIONS+="--with-http_ssl_module"
NGINX_OPTIONS+="--without-mail_pop3_module"
NGINX_OPTIONS+="--without-mail_smtp_module"
NGINX_OPTIONS+="--without-mail_imap_module"
NGINX_OPTIONS+="--without-http_uwsgi_module"
NGINX_OPTIONS+="--without-http_scgi_module"
NGINX_OPTIONS+="--add-dynamic-module=$(MOD_PATH)"
NGINX_OPTIONS+="--with-ipv6"
NGINX_OPTIONS+="--prefix=/tmp"
NGINX_OPTIONS+="--with-debug"
CFLAGS:="-Wall -Wextra"
all: nginx_download configure build install deploy
re: clean all test
clean:
rm -f "nginx-"$(NGINX_VERS)".tar.gz"
rm -f "nginx-"$(NGINX_VERS)".tar.gz.asc"
rm -rf /tmp/naxsi_ut/
rm -rf $(TMP_DIR)/
nginx_download:
wget --no-clobber "http://nginx.org/download/nginx-"$(NGINX_VERS)".tar.gz" || exit 1
wget --no-clobber "http://nginx.org/download/nginx-"$(NGINX_VERS)".tar.gz.asc" || exit 1
gpg --keyserver pgp.key-server.io --recv-keys 0x251a28de2685aed4 0x520A9993A1C052F8
gpg --verify "nginx-"$(NGINX_VERS)".tar.gz.asc" "nginx-"$(NGINX_VERS)".tar.gz" || exit 1
mkdir -p $(TMP_DIR)/
tar -C $(TMP_DIR)/ -xzf nginx-$(NGINX_VERS).tar.gz --strip-components=1
configure:
ifeq ($(COV),1)
cd $(TMP_DIR)/ && ./configure --with-cc-opt="--coverage -g3 -gstabs" --with-ld-opt="-lgcov" $(NGINX_OPTIONS)
else
cd $(TMP_DIR)/ && ./configure --with-cc-opt="-g3 -ggdb" $(NGINX_OPTIONS)
endif
build:
cd $(TMP_DIR)/ && make
if [ -d "/tmp/naxsi_ut" ] ; then cp $(TMP_DIR)/objs/ngx_http_naxsi_module.so /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so ; fi
install:
cd $(TMP_DIR)/ && make install
deploy:
@cp ./nginx.conf.example /tmp/naxsi_ut/nginx.conf
@cp ../naxsi_config/naxsi_core.rules /tmp/naxsi_ut/naxsi_core.rules
# RUN UNIT TESTS
test:
ifeq ($(COV),1)
lcov --directory $(TMP_DIR) --zerocounters
endif
if [ ! $(TEST) ] ; then TEST="*.t" ; fi
export PATH="$(TMP_DIR)/objs/:"$(PATH) ; \
export PERL5LIB="~/perl5/lib/perl5/:/home/travis/perl5/lib/perl5/" ; \
cd .. ; prove -r "t/$(TEST)"
ifeq ($(COV),1)
lcov --directory $(TMP_DIR)/objs/addon/naxsi_src/ --capture --output-file naxsi.info --base-directory $(TMP_DIR)
genhtml -s -o /tmp/naxsicov.html naxsi.info
endif
#Build for coverity and submit build !
coverity:
@CAK=$(shell cat ../../coverity.key | cut -d ':' -f2) ; \
CAN=$(shell cat ../../coverity.key | cut -d ':' -f1) ; \
echo "Coverity token/login : $$CAK and $$CAN"; \
wget -nc https://scan.coverity.com/download/linux-64 --post-data "token=$$CAK&project=nbs-system%2Fnaxsi" -O /tmp/coverity.tgz ; \
mkdir /tmp/cov && cd /tmp/cov && cat ../coverity.tgz | tar --strip-components=1 -xvzf - ; \
cd $(TMP_DIR) ; \
./configure $(NGINX_OPTIONS) && \
/tmp/cov/bin/cov-build --dir cov-int make && \
tar cvzf coverity-res-naxsi.tgz cov-int/ ; \
curl --form token="$$CAK" \
--form email="$$CAN" \
--form file=@$(TMP_DIR)/coverity-res-naxsi.tgz \
--form version="$(CORE_VERS)" \
--form description="Automatically submitted" \
https://scan.coverity.com/builds?project=nbs-system%2Fnaxsi

52
naxsi-0.55.3/README.md Normal file
View File

@@ -0,0 +1,52 @@
<img alt="naxsi logo" src="https://www.nbs-system.com/wp-content/uploads/nbs-logo-naxsi1.png" align="center"/>
[![coverity](https://scan.coverity.com/projects/1883/badge.svg)](https://scan.coverity.com/projects/1883)
[![travis-ci](https://travis-ci.org/nbs-system/naxsi.svg?branch=master)](https://travis-ci.org/nbs-system/naxsi)
[![coveralls](https://coveralls.io/repos/github/nbs-system/naxsi/badge.svg?branch=master)](https://coveralls.io/github/nbs-system/naxsi?branch=master)
[![codecov](http://codecov.io/github/nbs-system/naxsi/coverage.svg?branch=master)](http://codecov.io/github/nbs-system/naxsi?branch=master)
### We need your help
[Please fill in this little feedback survey](https://docs.google.com/spreadsheet/viewform?formkey=dG9UWDFuTEhiWWt4UF9fZEtwWFVJUlE6MQ), 2 minutes of your time, great help for us !
## What is Naxsi?
NAXSI means [Nginx]( http://nginx.org/ ) Anti [XSS]( https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29 ) & [SQL Injection]( https://www.owasp.org/index.php/SQL_injection ).
Technically, it is a third party nginx module, available as a package for
many UNIX-like platforms. This module, by default, reads a small subset of
[simple (and readable) rules]( https://github.com/nbs-system/naxsi/blob/master/naxsi_config/naxsi_core.rules )
containing 99% of known patterns involved in
website vulnerabilities. For example, `<`, `|` or `drop` are not supposed
to be part of a URI.
Being very simple, those patterns may match legitimate queries, it is
the Naxsi's administrator duty to add specific rules that will whitelist
legitimate behaviours. The administrator can either add whitelists manually
by analyzing nginx's error log, or (recommended) start the project with an
intensive auto-learning phase that will automatically generate whitelisting
rules regarding a website's behaviour.
In short, Naxsi behaves like a DROP-by-default firewall, the only task
is to add required ACCEPT rules for the target website to work properly.
## Why is it different?
Contrary to most Web Application Firewalls, Naxsi doesn't rely on a
signature base like an antivirus, and thus cannot be circumvented by an
"unknown" attack pattern. Another main difference between Naxsi and other
WAFs, Naxsi filters only GET and POST requests,
is [Free software]( https://www.gnu.org/licenses/gpl.html ) (as in freedom)
and free (as in free beer) to use.
## What does it run on?
Naxsi is compatible with any nginx version, although it currently doesn't play well with the new HTTPv2 protocol added in recent nginx versions. See [issue #227]( https://github.com/nbs-system/naxsi/issues/227 ) for more details.
It depends on `libpcre` for its regexp support, and is reported to work great on NetBSD, FreeBSD, OpenBSD, Debian, Ubuntu and CentOS.
### Getting started
- The [documentation](https://github.com/nbs-system/naxsi/wiki)
- Some [rules]( https://github.com/nbs-system/naxsi-rules ) for mainstream software
- The [nxapi/nxtool]( https://github.com/nbs-system/naxsi/tree/master/nxapi ) to generate rules

12
naxsi-0.55.3/config Normal file
View File

@@ -0,0 +1,12 @@
ngx_addon_name=ngx_http_naxsi_module
if test -n "$ngx_module_link"; then
ngx_module_type=HTTP
ngx_module_name=ngx_http_naxsi_module
ngx_module_srcs="$ngx_addon_dir/naxsi_runtime.c $ngx_addon_dir/naxsi_config.c $ngx_addon_dir/naxsi_utils.c $ngx_addon_dir/naxsi_skeleton.c $ngx_addon_dir/naxsi_json.c $ngx_addon_dir/naxsi_raw.c $ngx_addon_dir/ext/libinjection/libinjection_sqli.c $ngx_addon_dir/ext/libinjection/libinjection_xss.c $ngx_addon_dir/ext/libinjection/libinjection_html5.c"
. auto/module
else
HTTP_MODULES="$HTTP_MODULES ngx_http_naxsi_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/naxsi_runtime.c $ngx_addon_dir/naxsi_config.c $ngx_addon_dir/naxsi_utils.c $ngx_addon_dir/naxsi_skeleton.c $ngx_addon_dir/naxsi_json.c $ngx_addon_dir/naxsi_raw.c $ngx_addon_dir/ext/libinjection/libinjection_sqli.c $ngx_addon_dir/ext/libinjection/libinjection_xss.c $ngx_addon_dir/ext/libinjection/libinjection_html5.c"
NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/naxsi.h"
fi

View File

@@ -0,0 +1,30 @@
#include <stdio.h>
#include <string.h>
#include "libinjection.h"
int main(int argc, const char* argv[])
{
char fingerprint[8];
const char* input;
size_t slen;
int issqli;
if (argc < 2) {
fprintf(stderr, "Usage: %s inputstring\n", argv[0]);
return -1;
}
input = argv[1];
slen = strlen(input);
issqli = libinjection_sqli(input, slen, fingerprint);
if (issqli) {
printf("sqli with fingerprint of '%s'\n", fingerprint);
} else {
printf("not sqli\n");
}
return issqli;
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright 2012, 2013 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* This is for testing against files in ../data/ *.txt
* Reads from stdin or a list of files, and emits if a line
* is a SQLi attack or not, and does basic statistics
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
int main(int argc, const char* argv[])
{
size_t slen;
int ok;
int single = 0;
int offset = 1;
sfilter sf;
if (argc < 2) {
fprintf(stderr, "need more args\n");
return 1;
}
while (1) {
if (strcmp(argv[offset], "-0") == 0) {
single = 1;
offset += 1;
} else {
break;
}
}
slen = strlen(argv[offset]);
if (slen == 0) {
return 1;
}
/*
* "plain" context.. test string "as-is"
*/
libinjection_sqli_init(&sf, argv[offset], slen, 0);
if (single) {
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_NONE | FLAG_SQL_ANSI);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "%s\n", sf.fingerprint);
return 0;
}
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_NONE | FLAG_SQL_ANSI);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "plain-asni\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_NONE | FLAG_SQL_MYSQL);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "plain-mysql\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_SINGLE | FLAG_SQL_ANSI);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "single-ansi\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_SINGLE | FLAG_SQL_MYSQL);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "single-mysql\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_DOUBLE | FLAG_SQL_MYSQL);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "double-mysql\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
return 0;
}

View File

@@ -0,0 +1,168 @@
/**
* Copyright 2012, 2013, 2014 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* This is for testing against files in ../data/ *.txt
* Reads from stdin or a list of files, and emits if a line
* is a SQLi attack or not, and does basic statistics
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "libinjection_html5.h"
#include "libinjection_xss.h"
#include "libinjection.h"
int urlcharmap(char ch);
size_t modp_url_decode(char* dest, const char* s, size_t len);
const char* h5_type_to_string(enum html5_type x);
void print_html5_token(h5_state_t* hs);
int urlcharmap(char ch) {
switch (ch) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
default:
return 256;
}
}
size_t modp_url_decode(char* dest, const char* s, size_t len)
{
const char* deststart = dest;
size_t i = 0;
int d = 0;
while (i < len) {
switch (s[i]) {
case '+':
*dest++ = ' ';
i += 1;
break;
case '%':
if (i+2 < len) {
d = (urlcharmap(s[i+1]) << 4) | urlcharmap(s[i+2]);
if ( d < 256) {
*dest = (char) d;
dest++;
i += 3; /* loop will increment one time */
} else {
*dest++ = '%';
i += 1;
}
} else {
*dest++ = '%';
i += 1;
}
break;
default:
*dest++ = s[i];
i += 1;
}
}
*dest = '\0';
return (size_t)(dest - deststart); /* compute "strlen" of dest */
}
const char* h5_type_to_string(enum html5_type x)
{
switch (x) {
case DATA_TEXT: return "DATA_TEXT";
case TAG_NAME_OPEN: return "TAG_NAME_OPEN";
case TAG_NAME_CLOSE: return "TAG_NAME_CLOSE";
case TAG_NAME_SELFCLOSE: return "TAG_NAME_SELFCLOSE";
case TAG_DATA: return "TAG_DATA";
case TAG_CLOSE: return "TAG_CLOSE";
case ATTR_NAME: return "ATTR_NAME";
case ATTR_VALUE: return "ATTR_VALUE";
case TAG_COMMENT: return "TAG_COMMENT";
case DOCTYPE: return "DOCTYPE";
default:
assert(0);
}
}
void print_html5_token(h5_state_t* hs)
{
char* tmp = (char*) malloc(hs->token_len + 1);
memcpy(tmp, hs->token_start, hs->token_len);
/* TODO.. encode to be printable */
tmp[hs->token_len] = '\0';
printf("%s,%d,%s\n",
h5_type_to_string(hs->token_type),
(int) hs->token_len,
tmp);
free(tmp);
}
int main(int argc, const char* argv[])
{
size_t slen;
h5_state_t hs;
char* copy;
int offset = 1;
int flag = 0;
int urldecode = 0;
if (argc < 2) {
fprintf(stderr, "need more args\n");
return 1;
}
while (offset < argc) {
if (strcmp(argv[offset], "-u") == 0) {
offset += 1;
urldecode = 1;
} else if (strcmp(argv[offset], "-f") == 0) {
offset += 1;
flag = atoi(argv[offset]);
offset += 1;
} else {
break;
}
}
/* ATTENTION: argv is a C-string, null terminated. We copy this
* to it's own location, WITHOUT null byte. This way, valgrind
* can see if we run past the buffer.
*/
slen = strlen(argv[offset]);
copy = (char* ) malloc(slen);
memcpy(copy, argv[offset], slen);
if (urldecode) {
slen = modp_url_decode(copy, copy, slen);
}
libinjection_h5_init(&hs, copy, slen, (enum html5_flags) flag);
while (libinjection_h5_next(&hs)) {
print_html5_token(&hs);
}
if (libinjection_is_xss(copy, slen, flag)) {
printf("is injection!\n");
}
free(copy);
return 0;
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright 2012, 2013 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* https://libinjection.client9.com/
*
*/
#ifndef _LIBINJECTION_H
#define _LIBINJECTION_H
#ifdef __cplusplus
# define LIBINJECTION_BEGIN_DECLS extern "C" {
# define LIBINJECTION_END_DECLS }
#else
# define LIBINJECTION_BEGIN_DECLS
# define LIBINJECTION_END_DECLS
#endif
LIBINJECTION_BEGIN_DECLS
/*
* Pull in size_t
*/
#include <string.h>
/*
* Version info.
*
* This is moved into a function to allow SWIG and other auto-generated
* binding to not be modified during minor release changes. We change
* change the version number in the c source file, and not regenerated
* the binding
*
* See python's normalized version
* http://www.python.org/dev/peps/pep-0386/#normalizedversion
*/
const char* libinjection_version(void);
/**
* Simple API for SQLi detection - returns a SQLi fingerprint or NULL
* is benign input
*
* \param[in] s input string, may contain nulls, does not need to be null-terminated
* \param[in] slen input string length
* \param[out] fingerprint buffer of 8+ characters. c-string,
* \return 1 if SQLi, 0 if benign. fingerprint will be set or set to empty string.
*/
int libinjection_sqli(const char* s, size_t slen, char fingerprint[]);
/** ALPHA version of xss detector.
*
* NOT DONE.
*
* \param[in] s input string, may contain nulls, does not need to be null-terminated
* \param[in] slen input string length
* \return 1 if XSS found, 0 if benign
*
*/
int libinjection_xss(const char* s, size_t slen);
LIBINJECTION_END_DECLS
#endif /* _LIBINJECTION_H */

View File

@@ -0,0 +1,847 @@
#include "libinjection_html5.h"
#include <string.h>
#include <assert.h>
#ifdef DEBUG
#include <stdio.h>
#define TRACE() printf("%s:%d\n", __FUNCTION__, __LINE__)
#else
#define TRACE()
#endif
#define CHAR_EOF -1
#define CHAR_NULL 0
#define CHAR_BANG 33
#define CHAR_DOUBLE 34
#define CHAR_PERCENT 37
#define CHAR_SINGLE 39
#define CHAR_DASH 45
#define CHAR_SLASH 47
#define CHAR_LT 60
#define CHAR_EQUALS 61
#define CHAR_GT 62
#define CHAR_QUESTION 63
#define CHAR_RIGHTB 93
#define CHAR_TICK 96
/* prototypes */
static int h5_skip_white(h5_state_t* hs);
static int h5_is_white(char c);
static int h5_state_eof(h5_state_t* hs);
static int h5_state_data(h5_state_t* hs);
static int h5_state_tag_open(h5_state_t* hs);
static int h5_state_tag_name(h5_state_t* hs);
static int h5_state_tag_name_close(h5_state_t* hs);
static int h5_state_end_tag_open(h5_state_t* hs);
static int h5_state_self_closing_start_tag(h5_state_t* hs);
static int h5_state_attribute_name(h5_state_t* hs);
static int h5_state_after_attribute_name(h5_state_t* hs);
static int h5_state_before_attribute_name(h5_state_t* hs);
static int h5_state_before_attribute_value(h5_state_t* hs);
static int h5_state_attribute_value_double_quote(h5_state_t* hs);
static int h5_state_attribute_value_single_quote(h5_state_t* hs);
static int h5_state_attribute_value_back_quote(h5_state_t* hs);
static int h5_state_attribute_value_no_quote(h5_state_t* hs);
static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs);
static int h5_state_comment(h5_state_t* hs);
static int h5_state_cdata(h5_state_t* hs);
/* 12.2.4.44 */
static int h5_state_bogus_comment(h5_state_t* hs);
static int h5_state_bogus_comment2(h5_state_t* hs);
/* 12.2.4.45 */
static int h5_state_markup_declaration_open(h5_state_t* hs);
/* 8.2.4.52 */
static int h5_state_doctype(h5_state_t* hs);
/**
* public function
*/
void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags flags)
{
memset(hs, 0, sizeof(h5_state_t));
hs->s = s;
hs->len = len;
switch (flags) {
case DATA_STATE:
hs->state = h5_state_data;
break;
case VALUE_NO_QUOTE:
hs->state = h5_state_before_attribute_name;
break;
case VALUE_SINGLE_QUOTE:
hs->state = h5_state_attribute_value_single_quote;
break;
case VALUE_DOUBLE_QUOTE:
hs->state = h5_state_attribute_value_double_quote;
break;
case VALUE_BACK_QUOTE:
hs->state = h5_state_attribute_value_back_quote;
break;
}
}
/**
* public function
*/
int libinjection_h5_next(h5_state_t* hs)
{
assert(hs->state != NULL);
return (*hs->state)(hs);
}
/**
* Everything below here is private
*
*/
static int h5_is_white(char ch)
{
/*
* \t = htab = 0x09
* \n = newline = 0x0A
* \v = vtab = 0x0B
* \f = form feed = 0x0C
* \r = cr = 0x0D
*/
return strchr(" \t\n\v\f\r", ch) != NULL;
}
static int h5_skip_white(h5_state_t* hs)
{
char ch;
while (hs->pos < hs->len) {
ch = hs->s[hs->pos];
switch (ch) {
case 0x00: /* IE only */
case 0x20:
case 0x09:
case 0x0A:
case 0x0B: /* IE only */
case 0x0C:
case 0x0D: /* IE only */
hs->pos += 1;
break;
default:
return ch;
}
}
return CHAR_EOF;
}
static int h5_state_eof(h5_state_t* hs)
{
/* eliminate unused function argument warning */
(void)hs;
return 0;
}
static int h5_state_data(h5_state_t* hs)
{
const char* idx;
TRACE();
assert(hs->len >= hs->pos);
idx = (const char*) memchr(hs->s + hs->pos, CHAR_LT, hs->len - hs->pos);
if (idx == NULL) {
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = DATA_TEXT;
hs->state = h5_state_eof;
if (hs->token_len == 0) {
return 0;
}
} else {
hs->token_start = hs->s + hs->pos;
hs->token_type = DATA_TEXT;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 1;
hs->state = h5_state_tag_open;
if (hs->token_len == 0) {
return h5_state_tag_open(hs);
}
}
return 1;
}
/**
* 12 2.4.8
*/
static int h5_state_tag_open(h5_state_t* hs)
{
char ch;
TRACE();
ch = hs->s[hs->pos];
if (ch == CHAR_BANG) {
hs->pos += 1;
return h5_state_markup_declaration_open(hs);
} else if (ch == CHAR_SLASH) {
hs->pos += 1;
hs->is_close = 1;
return h5_state_end_tag_open(hs);
} else if (ch == CHAR_QUESTION) {
hs->pos += 1;
return h5_state_bogus_comment(hs);
} else if (ch == CHAR_PERCENT) {
/* this is not in spec.. alternative comment format used
by IE <= 9 and Safari < 4.0.3 */
hs->pos += 1;
return h5_state_bogus_comment2(hs);
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
return h5_state_tag_name(hs);
} else if (ch == CHAR_NULL) {
/* IE-ism NULL characters are ignored */
return h5_state_tag_name(hs);
} else {
/* user input mistake in configuring state */
if (hs->pos == 0) {
return h5_state_data(hs);
}
hs->token_start = hs->s + hs->pos - 1;
hs->token_len = 1;
hs->token_type = DATA_TEXT;
hs->state = h5_state_data;
return 1;
}
}
/**
* 12.2.4.9
*/
static int h5_state_end_tag_open(h5_state_t* hs)
{
char ch;
TRACE();
if (hs->pos >= hs->len) {
return 0;
}
ch = hs->s[hs->pos];
if (ch == CHAR_GT) {
return h5_state_data(hs);
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
return h5_state_tag_name(hs);
}
hs->is_close = 0;
return h5_state_bogus_comment(hs);
}
/*
*
*/
static int h5_state_tag_name_close(h5_state_t* hs)
{
TRACE();
hs->is_close = 0;
hs->token_start = hs->s + hs->pos;
hs->token_len = 1;
hs->token_type = TAG_NAME_CLOSE;
hs->pos += 1;
if (hs->pos < hs->len) {
hs->state = h5_state_data;
} else {
hs->state = h5_state_eof;
}
return 1;
}
/**
* 12.2.4.10
*/
static int h5_state_tag_name(h5_state_t* hs)
{
char ch;
size_t pos;
TRACE();
pos = hs->pos;
while (pos < hs->len) {
ch = hs->s[pos];
if (ch == 0) {
/* special non-standard case */
/* allow nulls in tag name */
/* some old browsers apparently allow and ignore them */
pos += 1;
} else if (h5_is_white(ch)) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = TAG_NAME_OPEN;
hs->pos = pos + 1;
hs->state = h5_state_before_attribute_name;
return 1;
} else if (ch == CHAR_SLASH) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = TAG_NAME_OPEN;
hs->pos = pos + 1;
hs->state = h5_state_self_closing_start_tag;
return 1;
} else if (ch == CHAR_GT) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
if (hs->is_close) {
hs->pos = pos + 1;
hs->is_close = 0;
hs->token_type = TAG_CLOSE;
hs->state = h5_state_data;
} else {
hs->pos = pos;
hs->token_type = TAG_NAME_OPEN;
hs->state = h5_state_tag_name_close;
}
return 1;
} else {
pos += 1;
}
}
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_NAME_OPEN;
hs->state = h5_state_eof;
return 1;
}
/**
* 12.2.4.34
*/
static int h5_state_before_attribute_name(h5_state_t* hs)
{
int ch;
TRACE();
ch = h5_skip_white(hs);
switch (ch) {
case CHAR_EOF: {
return 0;
}
case CHAR_SLASH: {
hs->pos += 1;
return h5_state_self_closing_start_tag(hs);
}
case CHAR_GT: {
hs->state = h5_state_data;
hs->token_start = hs->s + hs->pos;
hs->token_len = 1;
hs->token_type = TAG_NAME_CLOSE;
hs->pos += 1;
return 1;
}
default: {
return h5_state_attribute_name(hs);
}
}
}
static int h5_state_attribute_name(h5_state_t* hs)
{
char ch;
size_t pos;
TRACE();
pos = hs->pos + 1;
while (pos < hs->len) {
ch = hs->s[pos];
if (h5_is_white(ch)) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_after_attribute_name;
hs->pos = pos + 1;
return 1;
} else if (ch == CHAR_SLASH) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_self_closing_start_tag;
hs->pos = pos + 1;
return 1;
} else if (ch == CHAR_EQUALS) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_before_attribute_value;
hs->pos = pos + 1;
return 1;
} else if (ch == CHAR_GT) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_tag_name_close;
hs->pos = pos;
return 1;
} else {
pos += 1;
}
}
/* EOF */
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_eof;
hs->pos = hs->len;
return 1;
}
/**
* 12.2.4.36
*/
static int h5_state_after_attribute_name(h5_state_t* hs)
{
int c;
TRACE();
c = h5_skip_white(hs);
switch (c) {
case CHAR_EOF: {
return 0;
}
case CHAR_SLASH: {
hs->pos += 1;
return h5_state_self_closing_start_tag(hs);
}
case CHAR_EQUALS: {
hs->pos += 1;
return h5_state_before_attribute_value(hs);
}
case CHAR_GT: {
return h5_state_tag_name_close(hs);
}
default: {
return h5_state_attribute_name(hs);
}
}
}
/**
* 12.2.4.37
*/
static int h5_state_before_attribute_value(h5_state_t* hs)
{
int c;
TRACE();
c = h5_skip_white(hs);
if (c == CHAR_EOF) {
hs->state = h5_state_eof;
return 0;
}
if (c == CHAR_DOUBLE) {
return h5_state_attribute_value_double_quote(hs);
} else if (c == CHAR_SINGLE) {
return h5_state_attribute_value_single_quote(hs);
} else if (c == CHAR_TICK) {
/* NON STANDARD IE */
return h5_state_attribute_value_back_quote(hs);
} else {
return h5_state_attribute_value_no_quote(hs);
}
}
static int h5_state_attribute_value_quote(h5_state_t* hs, char qchar)
{
const char* idx;
TRACE();
/* skip initial quote in normal case.
* dont do this is pos == 0 since it means we have started
* in a non-data state. given an input of '><foo
* we want to make 0-length attribute name
*/
if (hs->pos > 0) {
hs->pos += 1;
}
idx = (const char*) memchr(hs->s + hs->pos, qchar, hs->len - hs->pos);
if (idx == NULL) {
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = ATTR_VALUE;
hs->state = h5_state_eof;
} else {
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->token_type = ATTR_VALUE;
hs->state = h5_state_after_attribute_value_quoted_state;
hs->pos += hs->token_len + 1;
}
return 1;
}
static
int h5_state_attribute_value_double_quote(h5_state_t* hs)
{
TRACE();
return h5_state_attribute_value_quote(hs, CHAR_DOUBLE);
}
static
int h5_state_attribute_value_single_quote(h5_state_t* hs)
{
TRACE();
return h5_state_attribute_value_quote(hs, CHAR_SINGLE);
}
static
int h5_state_attribute_value_back_quote(h5_state_t* hs)
{
TRACE();
return h5_state_attribute_value_quote(hs, CHAR_TICK);
}
static int h5_state_attribute_value_no_quote(h5_state_t* hs)
{
char ch;
size_t pos;
TRACE();
pos = hs->pos;
while (pos < hs->len) {
ch = hs->s[pos];
if (h5_is_white(ch)) {
hs->token_type = ATTR_VALUE;
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->pos = pos + 1;
hs->state = h5_state_before_attribute_name;
return 1;
} else if (ch == CHAR_GT) {
hs->token_type = ATTR_VALUE;
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->pos = pos;
hs->state = h5_state_tag_name_close;
return 1;
}
pos += 1;
}
TRACE();
/* EOF */
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = ATTR_VALUE;
return 1;
}
/**
* 12.2.4.41
*/
static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs)
{
char ch;
TRACE();
if (hs->pos >= hs->len) {
return 0;
}
ch = hs->s[hs->pos];
if (h5_is_white(ch)) {
hs->pos += 1;
return h5_state_before_attribute_name(hs);
} else if (ch == CHAR_SLASH) {
hs->pos += 1;
return h5_state_self_closing_start_tag(hs);
} else if (ch == CHAR_GT) {
hs->token_start = hs->s + hs->pos;
hs->token_len = 1;
hs->token_type = TAG_NAME_CLOSE;
hs->pos += 1;
hs->state = h5_state_data;
return 1;
} else {
return h5_state_before_attribute_name(hs);
}
}
/**
* 12.2.4.43
*/
static int h5_state_self_closing_start_tag(h5_state_t* hs)
{
char ch;
TRACE();
if (hs->pos >= hs->len) {
return 0;
}
ch = hs->s[hs->pos];
if (ch == CHAR_GT) {
assert(hs->pos > 0);
hs->token_start = hs->s + hs->pos -1;
hs->token_len = 2;
hs->token_type = TAG_NAME_SELFCLOSE;
hs->state = h5_state_data;
hs->pos += 1;
return 1;
} else {
return h5_state_before_attribute_name(hs);
}
}
/**
* 12.2.4.44
*/
static int h5_state_bogus_comment(h5_state_t* hs)
{
const char* idx;
TRACE();
idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos);
if (idx == NULL) {
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->pos = hs->len;
hs->state = h5_state_eof;
} else {
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 1;
hs->state = h5_state_data;
}
hs->token_type = TAG_COMMENT;
return 1;
}
/**
* 12.2.4.44 ALT
*/
static int h5_state_bogus_comment2(h5_state_t* hs)
{
const char* idx;
size_t pos;
TRACE();
pos = hs->pos;
while (1) {
idx = (const char*) memchr(hs->s + pos, CHAR_PERCENT, hs->len - pos);
if (idx == NULL || (idx + 1 >= hs->s + hs->len)) {
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->pos = hs->len;
hs->token_type = TAG_COMMENT;
hs->state = h5_state_eof;
return 1;
}
if (*(idx +1) != CHAR_GT) {
pos = (size_t)(idx - hs->s) + 1;
continue;
}
/* ends in %> */
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 2;
hs->state = h5_state_data;
hs->token_type = TAG_COMMENT;
return 1;
}
}
/**
* 8.2.4.45
*/
static int h5_state_markup_declaration_open(h5_state_t* hs)
{
size_t remaining;
TRACE();
remaining = hs->len - hs->pos;
if (remaining >= 7 &&
/* case insensitive */
(hs->s[hs->pos + 0] == 'D' || hs->s[hs->pos + 0] == 'd') &&
(hs->s[hs->pos + 1] == 'O' || hs->s[hs->pos + 1] == 'o') &&
(hs->s[hs->pos + 2] == 'C' || hs->s[hs->pos + 2] == 'c') &&
(hs->s[hs->pos + 3] == 'T' || hs->s[hs->pos + 3] == 't') &&
(hs->s[hs->pos + 4] == 'Y' || hs->s[hs->pos + 4] == 'y') &&
(hs->s[hs->pos + 5] == 'P' || hs->s[hs->pos + 5] == 'p') &&
(hs->s[hs->pos + 6] == 'E' || hs->s[hs->pos + 6] == 'e')
) {
return h5_state_doctype(hs);
} else if (remaining >= 7 &&
/* upper case required */
hs->s[hs->pos + 0] == '[' &&
hs->s[hs->pos + 1] == 'C' &&
hs->s[hs->pos + 2] == 'D' &&
hs->s[hs->pos + 3] == 'A' &&
hs->s[hs->pos + 4] == 'T' &&
hs->s[hs->pos + 5] == 'A' &&
hs->s[hs->pos + 6] == '['
) {
hs->pos += 7;
return h5_state_cdata(hs);
} else if (remaining >= 2 &&
hs->s[hs->pos + 0] == '-' &&
hs->s[hs->pos + 1] == '-') {
hs->pos += 2;
return h5_state_comment(hs);
}
return h5_state_bogus_comment(hs);
}
/**
* 12.2.4.48
* 12.2.4.49
* 12.2.4.50
* 12.2.4.51
* state machine spec is confusing since it can only look
* at one character at a time but simply it's comments end by:
* 1) EOF
* 2) ending in -->
* 3) ending in -!>
*/
static int h5_state_comment(h5_state_t* hs)
{
char ch;
const char* idx;
size_t pos;
size_t offset;
const char* end = hs->s + hs->len;
TRACE();
pos = hs->pos;
while (1) {
idx = (const char*) memchr(hs->s + pos, CHAR_DASH, hs->len - pos);
/* did not find anything or has less than 3 chars left */
if (idx == NULL || idx > hs->s + hs->len - 3) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_COMMENT;
return 1;
}
offset = 1;
/* skip all nulls */
while (idx + offset < end && *(idx + offset) == 0) {
offset += 1;
}
if (idx + offset == end) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_COMMENT;
return 1;
}
ch = *(idx + offset);
if (ch != CHAR_DASH && ch != CHAR_BANG) {
pos = (size_t)(idx - hs->s) + 1;
continue;
}
/* need to test */
#if 0
/* skip all nulls */
while (idx + offset < end && *(idx + offset) == 0) {
offset += 1;
}
if (idx + offset == end) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_COMMENT;
return 1;
}
#endif
offset += 1;
if (idx + offset == end) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_COMMENT;
return 1;
}
ch = *(idx + offset);
if (ch != CHAR_GT) {
pos = (size_t)(idx - hs->s) + 1;
continue;
}
offset += 1;
/* ends in --> or -!> */
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx + offset - hs->s);
hs->state = h5_state_data;
hs->token_type = TAG_COMMENT;
return 1;
}
}
static int h5_state_cdata(h5_state_t* hs)
{
const char* idx;
size_t pos;
TRACE();
pos = hs->pos;
while (1) {
idx = (const char*) memchr(hs->s + pos, CHAR_RIGHTB, hs->len - pos);
/* did not find anything or has less than 3 chars left */
if (idx == NULL || idx > hs->s + hs->len - 3) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = DATA_TEXT;
return 1;
} else if ( *(idx+1) == CHAR_RIGHTB && *(idx+2) == CHAR_GT) {
hs->state = h5_state_data;
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 3;
hs->token_type = DATA_TEXT;
return 1;
} else {
pos = (size_t)(idx - hs->s) + 1;
}
}
}
/**
* 8.2.4.52
* http://www.w3.org/html/wg/drafts/html/master/syntax.html#doctype-state
*/
static int h5_state_doctype(h5_state_t* hs)
{
const char* idx;
TRACE();
hs->token_start = hs->s + hs->pos;
hs->token_type = DOCTYPE;
idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos);
if (idx == NULL) {
hs->state = h5_state_eof;
hs->token_len = hs->len - hs->pos;
} else {
hs->state = h5_state_data;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 1;
}
return 1;
}

View File

@@ -0,0 +1,54 @@
#ifndef LIBINJECTION_HTML5
#define LIBINJECTION_HTML5
#ifdef __cplusplus
extern "C" {
#endif
/* pull in size_t */
#include <stddef.h>
enum html5_type {
DATA_TEXT
, TAG_NAME_OPEN
, TAG_NAME_CLOSE
, TAG_NAME_SELFCLOSE
, TAG_DATA
, TAG_CLOSE
, ATTR_NAME
, ATTR_VALUE
, TAG_COMMENT
, DOCTYPE
};
enum html5_flags {
DATA_STATE
, VALUE_NO_QUOTE
, VALUE_SINGLE_QUOTE
, VALUE_DOUBLE_QUOTE
, VALUE_BACK_QUOTE
};
struct h5_state;
typedef int (*ptr_html5_state)(struct h5_state*);
typedef struct h5_state {
const char* s;
size_t len;
size_t pos;
int is_close;
ptr_html5_state state;
const char* token_start;
size_t token_len;
enum html5_type token_type;
} h5_state_t;
void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags);
int libinjection_h5_next(h5_state_t* hs);
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
/**
* Copyright 2012, 2013 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* https://libinjection.client9.com/
*
*/
#ifndef _LIBINJECTION_SQLI_H
#define _LIBINJECTION_SQLI_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Pull in size_t
*/
#include <string.h>
enum sqli_flags {
FLAG_NONE = 0
, FLAG_QUOTE_NONE = 1 /* 1 << 0 */
, FLAG_QUOTE_SINGLE = 2 /* 1 << 1 */
, FLAG_QUOTE_DOUBLE = 4 /* 1 << 2 */
, FLAG_SQL_ANSI = 8 /* 1 << 3 */
, FLAG_SQL_MYSQL = 16 /* 1 << 4 */
};
enum lookup_type {
LOOKUP_WORD = 1
, LOOKUP_TYPE = 2
, LOOKUP_OPERATOR = 3
, LOOKUP_FINGERPRINT = 4
};
struct libinjection_sqli_token {
#ifdef SWIG
%immutable;
#endif
char type;
char str_open;
char str_close;
/*
* position and length of token
* in original string
*/
size_t pos;
size_t len;
/* count:
* in type 'v', used for number of opening '@'
* but maybe unsed in other contexts
*/
int count;
char val[32];
};
typedef struct libinjection_sqli_token stoken_t;
/**
* Pointer to function, takes cstr input,
* returns '\0' for no match, else a char
*/
struct libinjection_sqli_state;
typedef char (*ptr_lookup_fn)(struct libinjection_sqli_state*, int lookuptype, const char* word, size_t len);
struct libinjection_sqli_state {
#ifdef SWIG
%immutable;
#endif
/*
* input, does not need to be null terminated.
* it is also not modified.
*/
const char *s;
/*
* input length
*/
size_t slen;
/*
* How to lookup a word or fingerprint
*/
ptr_lookup_fn lookup;
void* userdata;
/*
*
*/
int flags;
/*
* pos is index in string we are at when tokenizing
*/
size_t pos;
#ifndef SWIG
/* for SWIG.. don't use this.. use functional API instead */
/* MAX TOKENS + 1 since we use one extra token
* to determine the type of the previous token
*/
struct libinjection_sqli_token tokenvec[8];
#endif
/*
* Pointer to token position in tokenvec, above
*/
struct libinjection_sqli_token *current;
/*
* fingerprint pattern c-string
* +1 for ending null
* Mimimum of 8 bytes to add gcc's -fstack-protector to work
*/
char fingerprint[8];
/*
* Line number of code that said decided if the input was SQLi or
* not. Most of the time it's line that said "it's not a matching
* fingerprint" but there is other logic that sometimes approves
* an input. This is only useful for debugging.
*
*/
int reason;
/* Number of ddw (dash-dash-white) comments
* These comments are in the form of
* '--[whitespace]' or '--[EOF]'
*
* All databases treat this as a comment.
*/
int stats_comment_ddw;
/* Number of ddx (dash-dash-[notwhite]) comments
*
* ANSI SQL treats these are comments, MySQL treats this as
* two unary operators '-' '-'
*
* If you are parsing result returns FALSE and
* stats_comment_dd > 0, you should reparse with
* COMMENT_MYSQL
*
*/
int stats_comment_ddx;
/*
* c-style comments found /x .. x/
*/
int stats_comment_c;
/* '#' operators or mysql EOL comments found
*
*/
int stats_comment_hash;
/*
* number of tokens folded away
*/
int stats_folds;
/*
* total tokens processed
*/
int stats_tokens;
};
typedef struct libinjection_sqli_state sfilter;
struct libinjection_sqli_token* libinjection_sqli_get_token(
struct libinjection_sqli_state* sqlistate, int i);
/*
* Version info.
*
* This is moved into a function to allow SWIG and other auto-generated
* binding to not be modified during minor release changes. We change
* change the version number in the c source file, and not regenerated
* the binding
*
* See python's normalized version
* http://www.python.org/dev/peps/pep-0386/#normalizedversion
*/
const char* libinjection_version(void);
/**
*
*/
void libinjection_sqli_init(struct libinjection_sqli_state* sql_state,
const char* s, size_t slen,
int flags);
/**
* Main API: tests for SQLi in three possible contexts, no quotes,
* single quote and double quote
*
* \param sql_state core data structure
*
* \return 1 (true) if SQLi, 0 (false) if benign
*/
int libinjection_is_sqli(struct libinjection_sqli_state* sql_state);
/* FOR H@CKERS ONLY
*
*/
void libinjection_sqli_callback(struct libinjection_sqli_state* sql_state,
ptr_lookup_fn fn,
void* userdata);
/*
* Resets state, but keeps initial string and callbacks
*/
void libinjection_sqli_reset(struct libinjection_sqli_state* sql_state,
int flags);
/**
*
*/
/**
* This detects SQLi in a single context, mostly useful for custom
* logic and debugging.
*
* \param sql_state Main data structure
* \param flags flags to adjust parsing
*
* \returns a pointer to sfilter.fingerprint as convenience
* do not free!
*
*/
const char* libinjection_sqli_fingerprint(struct libinjection_sqli_state* sql_state,
int flags);
/**
* The default "word" to token-type or fingerprint function. This
* uses a ASCII case-insensitive binary tree.
*/
char libinjection_sqli_lookup_word(struct libinjection_sqli_state* sql_state,
int lookup_type,
const char* s,
size_t slen);
/* Streaming tokenization interface.
*
* sql_state->current is updated with the current token.
*
* \returns 1, has a token, keep going, or 0 no tokens
*
*/
int libinjection_sqli_tokenize(struct libinjection_sqli_state * sql_state);
/**
* parses and folds input, up to 5 tokens
*
*/
int libinjection_sqli_fold(struct libinjection_sqli_state * sql_state);
/** The built-in default function to match fingerprints
* and do false negative/positive analysis. This calls the following
* two functions. With this, you over-ride one part or the other.
*
* return libinjection_sqli_blacklist(sql_state) &&
* libinject_sqli_not_whitelist(sql_state);
*
* \param sql_state should be filled out after libinjection_sqli_fingerprint is called
*/
int libinjection_sqli_check_fingerprint(struct libinjection_sqli_state * sql_state);
/* Given a pattern determine if it's a SQLi pattern.
*
* \return TRUE if sqli, false otherwise
*/
int libinjection_sqli_blacklist(struct libinjection_sqli_state* sql_state);
/* Given a positive match for a pattern (i.e. pattern is SQLi), this function
* does additional analysis to reduce false positives.
*
* \return TRUE if sqli, false otherwise
*/
int libinjection_sqli_not_whitelist(struct libinjection_sqli_state * sql_state);
#ifdef __cplusplus
}
#endif
#endif /* _LIBINJECTION_SQLI_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,531 @@
#include "libinjection.h"
#include "libinjection_xss.h"
#include "libinjection_html5.h"
#include <assert.h>
#include <stdio.h>
typedef enum attribute {
TYPE_NONE
, TYPE_BLACK /* ban always */
, TYPE_ATTR_URL /* attribute value takes a URL-like object */
, TYPE_STYLE
, TYPE_ATTR_INDIRECT /* attribute *name* is given in *value* */
} attribute_t;
static attribute_t is_black_attr(const char* s, size_t len);
static int is_black_tag(const char* s, size_t len);
static int is_black_url(const char* s, size_t len);
static int cstrcasecmp_with_null(const char *a, const char *b, size_t n);
static int html_decode_char_at(const char* src, size_t len, size_t* consumed);
static int htmlencode_startswith(const char* prefix, const char *src, size_t n);
typedef struct stringtype {
const char* name;
attribute_t atype;
} stringtype_t;
static const int gsHexDecodeMap[256] = {
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 256, 256,
256, 256, 256, 256, 256, 10, 11, 12, 13, 14, 15, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 10, 11, 12, 13, 14, 15, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256
};
static int html_decode_char_at(const char* src, size_t len, size_t* consumed)
{
int val = 0;
size_t i;
int ch;
if (len == 0 || src == NULL) {
*consumed = 0;
return -1;
}
*consumed = 1;
if (*src != '&' || len < 2) {
return (unsigned char)(*src);
}
if (*(src+1) != '#') {
/* normally this would be for named entities
* but for this case we don't actually care
*/
return '&';
}
if (*(src+2) == 'x' || *(src+2) == 'X') {
ch = (unsigned char) (*(src+3));
ch = gsHexDecodeMap[ch];
if (ch == 256) {
/* degenerate case '&#[?]' */
return '&';
}
val = ch;
i = 4;
while (i < len) {
ch = (unsigned char) src[i];
if (ch == ';') {
*consumed = i + 1;
return val;
}
ch = gsHexDecodeMap[ch];
if (ch == 256) {
*consumed = i;
return val;
}
val = (val * 16) + ch;
if (val > 0x1000FF) {
return '&';
}
++i;
}
*consumed = i;
return val;
} else {
i = 2;
ch = (unsigned char) src[i];
if (ch < '0' || ch > '9') {
return '&';
}
val = ch - '0';
i += 1;
while (i < len) {
ch = (unsigned char) src[i];
if (ch == ';') {
*consumed = i + 1;
return val;
}
if (ch < '0' || ch > '9') {
*consumed = i;
return val;
}
val = (val * 10) + (ch - '0');
if (val > 0x1000FF) {
return '&';
}
++i;
}
*consumed = i;
return val;
}
}
/*
* view-source:
* data:
* javascript:
*/
static stringtype_t BLACKATTR[] = {
{ "ACTION", TYPE_ATTR_URL } /* form */
, { "ATTRIBUTENAME", TYPE_ATTR_INDIRECT } /* SVG allow indirection of attribute names */
, { "BY", TYPE_ATTR_URL } /* SVG */
, { "BACKGROUND", TYPE_ATTR_URL } /* IE6, O11 */
, { "DATAFORMATAS", TYPE_BLACK } /* IE */
, { "DATASRC", TYPE_BLACK } /* IE */
, { "DYNSRC", TYPE_ATTR_URL } /* Obsolete img attribute */
, { "FILTER", TYPE_STYLE } /* Opera, SVG inline style */
, { "FORMACTION", TYPE_ATTR_URL } /* HTML5 */
, { "FOLDER", TYPE_ATTR_URL } /* Only on A tags, IE-only */
, { "FROM", TYPE_ATTR_URL } /* SVG */
, { "HANDLER", TYPE_ATTR_URL } /* SVG Tiny, Opera */
, { "HREF", TYPE_ATTR_URL }
, { "LOWSRC", TYPE_ATTR_URL } /* Obsolete img attribute */
, { "POSTER", TYPE_ATTR_URL } /* Opera 10,11 */
, { "SRC", TYPE_ATTR_URL }
, { "STYLE", TYPE_STYLE }
, { "TO", TYPE_ATTR_URL } /* SVG */
, { "VALUES", TYPE_ATTR_URL } /* SVG */
, { "XLINK:HREF", TYPE_ATTR_URL }
, { NULL, TYPE_NONE }
};
/* xmlns */
/* xml-stylesheet > <eval>, <if expr=> */
/*
static const char* BLACKATTR[] = {
"ATTRIBUTENAME",
"BACKGROUND",
"DATAFORMATAS",
"HREF",
"SCROLL",
"SRC",
"STYLE",
"SRCDOC",
NULL
};
*/
static const char* BLACKTAG[] = {
"APPLET"
/* , "AUDIO" */
, "BASE"
, "COMMENT" /* IE http://html5sec.org/#38 */
, "EMBED"
/* , "FORM" */
, "FRAME"
, "FRAMESET"
, "HANDLER" /* Opera SVG, effectively a script tag */
, "IFRAME"
, "IMPORT"
, "ISINDEX"
, "LINK"
, "LISTENER"
/* , "MARQUEE" */
, "META"
, "NOSCRIPT"
, "OBJECT"
, "SCRIPT"
, "STYLE"
/* , "VIDEO" */
, "VMLFRAME"
, "XML"
, "XSS"
, NULL
};
static int cstrcasecmp_with_null(const char *a, const char *b, size_t n)
{
char ca;
char cb;
/* printf("Comparing to %s %.*s\n", a, (int)n, b); */
while (n-- > 0) {
cb = *b++;
if (cb == '\0') continue;
ca = *a++;
if (cb >= 'a' && cb <= 'z') {
cb -= 0x20;
}
/* printf("Comparing %c vs %c with %d left\n", ca, cb, (int)n); */
if (ca != cb) {
return 1;
}
}
if (*a == 0) {
/* printf(" MATCH \n"); */
return 0;
} else {
return 1;
}
}
/*
* Does an HTML encoded binary string (const char*, lenght) start with
* a all uppercase c-string (null terminated), case insenstive!
*
* also ignore any embedded nulls in the HTML string!
*
* return 1 if match / starts with
* return 0 if not
*/
static int htmlencode_startswith(const char *a, const char *b, size_t n)
{
size_t consumed;
int cb;
int first = 1;
/* printf("Comparing %s with %.*s\n", a,(int)n,b); */
while (n > 0) {
if (*a == 0) {
/* printf("Match EOL!\n"); */
return 1;
}
cb = html_decode_char_at(b, n, &consumed);
b += consumed;
n -= consumed;
if (first && cb <= 32) {
/* ignore all leading whitespace and control characters */
continue;
}
first = 0;
if (cb == 0) {
/* always ignore null characters in user input */
continue;
}
if (cb == 10) {
/* always ignore vtab characters in user input */
/* who allows this?? */
continue;
}
if (cb >= 'a' && cb <= 'z') {
/* upcase */
cb -= 0x20;
}
if (*a != (char) cb) {
/* printf(" %c != %c\n", *a, cb); */
/* mismatch */
return 0;
}
a++;
}
return (*a == 0) ? 1 : 0;
}
static int is_black_tag(const char* s, size_t len)
{
const char** black;
if (len < 3) {
return 0;
}
black = BLACKTAG;
while (*black != NULL) {
if (cstrcasecmp_with_null(*black, s, len) == 0) {
/* printf("Got black tag %s\n", *black); */
return 1;
}
black += 1;
}
/* anything SVG related */
if ((s[0] == 's' || s[0] == 'S') &&
(s[1] == 'v' || s[1] == 'V') &&
(s[2] == 'g' || s[2] == 'G')) {
/* printf("Got SVG tag \n"); */
return 1;
}
/* Anything XSL(t) related */
if ((s[0] == 'x' || s[0] == 'X') &&
(s[1] == 's' || s[1] == 'S') &&
(s[2] == 'l' || s[2] == 'L')) {
/* printf("Got XSL tag\n"); */
return 1;
}
return 0;
}
static attribute_t is_black_attr(const char* s, size_t len)
{
stringtype_t* black;
if (len < 2) {
return TYPE_NONE;
}
/* javascript on.* */
if ((s[0] == 'o' || s[0] == 'O') && (s[1] == 'n' || s[1] == 'N')) {
/* printf("Got javascript on- attribute name\n"); */
return TYPE_BLACK;
}
if (len >= 5) {
/* XMLNS can be used to create arbitrary tags */
if (cstrcasecmp_with_null("XMLNS", s, 5) == 0 || cstrcasecmp_with_null("XLINK", s, 5) == 0) {
/* printf("Got XMLNS and XLINK tags\n"); */
return TYPE_BLACK;
}
}
black = BLACKATTR;
while (black->name != NULL) {
if (cstrcasecmp_with_null(black->name, s, len) == 0) {
/* printf("Got banned attribute name %s\n", black->name); */
return black->atype;
}
black += 1;
}
return TYPE_NONE;
}
static int is_black_url(const char* s, size_t len)
{
static const char* data_url = "DATA";
static const char* viewsource_url = "VIEW-SOURCE";
/* obsolete but interesting signal */
static const char* vbscript_url = "VBSCRIPT";
/* covers JAVA, JAVASCRIPT, + colon */
static const char* javascript_url = "JAVA";
/* skip whitespace */
while (len > 0 && (*s <= 32 || *s >= 127)) {
/*
* HEY: this is a signed character.
* We are intentionally skipping high-bit characters too
* since they are not ascii, and Opera sometimes uses UTF8 whitespace.
*
* Also in EUC-JP some of the high bytes are just ignored.
*/
++s;
--len;
}
if (htmlencode_startswith(data_url, s, len)) {
return 1;
}
if (htmlencode_startswith(viewsource_url, s, len)) {
return 1;
}
if (htmlencode_startswith(javascript_url, s, len)) {
return 1;
}
if (htmlencode_startswith(vbscript_url, s, len)) {
return 1;
}
return 0;
}
int libinjection_is_xss(const char* s, size_t len, int flags)
{
h5_state_t h5;
attribute_t attr = TYPE_NONE;
libinjection_h5_init(&h5, s, len, (enum html5_flags) flags);
while (libinjection_h5_next(&h5)) {
if (h5.token_type != ATTR_VALUE) {
attr = TYPE_NONE;
}
if (h5.token_type == DOCTYPE) {
return 1;
} else if (h5.token_type == TAG_NAME_OPEN) {
if (is_black_tag(h5.token_start, h5.token_len)) {
return 1;
}
} else if (h5.token_type == ATTR_NAME) {
attr = is_black_attr(h5.token_start, h5.token_len);
} else if (h5.token_type == ATTR_VALUE) {
/*
* IE6,7,8 parsing works a bit differently so
* a whole <script> or other black tag might be hiding
* inside an attribute value under HTML5 parsing
* See http://html5sec.org/#102
* to avoid doing a full reparse of the value, just
* look for "<". This probably need adjusting to
* handle escaped characters
*/
/*
if (memchr(h5.token_start, '<', h5.token_len) != NULL) {
return 1;
}
*/
switch (attr) {
case TYPE_NONE:
break;
case TYPE_BLACK:
return 1;
case TYPE_ATTR_URL:
if (is_black_url(h5.token_start, h5.token_len)) {
return 1;
}
break;
case TYPE_STYLE:
return 1;
case TYPE_ATTR_INDIRECT:
/* an attribute name is specified in a _value_ */
if (is_black_attr(h5.token_start, h5.token_len)) {
return 1;
}
break;
/*
default:
assert(0);
*/
}
attr = TYPE_NONE;
} else if (h5.token_type == TAG_COMMENT) {
/* IE uses a "`" as a tag ending char */
if (memchr(h5.token_start, '`', h5.token_len) != NULL) {
return 1;
}
/* IE conditional comment */
if (h5.token_len > 3) {
if (h5.token_start[0] == '[' &&
(h5.token_start[1] == 'i' || h5.token_start[1] == 'I') &&
(h5.token_start[2] == 'f' || h5.token_start[2] == 'F')) {
return 1;
}
if ((h5.token_start[0] == 'x' || h5.token_start[1] == 'X') &&
(h5.token_start[1] == 'm' || h5.token_start[1] == 'M') &&
(h5.token_start[2] == 'l' || h5.token_start[2] == 'L')) {
return 1;
}
}
if (h5.token_len > 5) {
/* IE <?import pseudo-tag */
if (cstrcasecmp_with_null("IMPORT", h5.token_start, 6) == 0) {
return 1;
}
/* XML Entity definition */
if (cstrcasecmp_with_null("ENTITY", h5.token_start, 6) == 0) {
return 1;
}
}
}
}
return 0;
}
/*
* wrapper
*/
int libinjection_xss(const char* s, size_t len)
{
if (libinjection_is_xss(s, len, DATA_STATE)) {
return 1;
}
if (libinjection_is_xss(s, len, VALUE_NO_QUOTE)) {
return 1;
}
if (libinjection_is_xss(s, len, VALUE_SINGLE_QUOTE)) {
return 1;
}
if (libinjection_is_xss(s, len, VALUE_DOUBLE_QUOTE)) {
return 1;
}
if (libinjection_is_xss(s, len, VALUE_BACK_QUOTE)) {
return 1;
}
return 0;
}

View File

@@ -0,0 +1,21 @@
#ifndef LIBINJECTION_XSS
#define LIBINJECTION_XSS
#ifdef __cplusplus
extern "C" {
#endif
/**
* HEY THIS ISN'T DONE
*/
/* pull in size_t */
#include <string.h>
int libinjection_is_xss(const char* s, size_t len, int flags);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,314 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
#include "libinjection_xss.h"
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
static int g_test_ok = 0;
static int g_test_fail = 0;
typedef enum {
MODE_SQLI,
MODE_XSS
} detect_mode_t;
static void usage(const char* argv[]);
size_t modp_rtrim(char* str, size_t len);
void modp_toprint(char* str, size_t len);
void test_positive(FILE * fd, const char *fname, detect_mode_t mode,
int flag_invert, int flag_true, int flag_quiet);
int urlcharmap(char ch);
size_t modp_url_decode(char* dest, const char* s, size_t len);
int urlcharmap(char ch) {
switch (ch) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
default:
return 256;
}
}
size_t modp_url_decode(char* dest, const char* s, size_t len)
{
const char* deststart = dest;
size_t i = 0;
int d = 0;
while (i < len) {
switch (s[i]) {
case '+':
*dest++ = ' ';
i += 1;
break;
case '%':
if (i+2 < len) {
d = (urlcharmap(s[i+1]) << 4) | urlcharmap(s[i+2]);
if ( d < 256) {
*dest = (char) d;
dest++;
i += 3; /* loop will increment one time */
} else {
*dest++ = '%';
i += 1;
}
} else {
*dest++ = '%';
i += 1;
}
break;
default:
*dest++ = s[i];
i += 1;
}
}
*dest = '\0';
return (size_t)(dest - deststart); /* compute "strlen" of dest */
}
void modp_toprint(char* str, size_t len)
{
size_t i;
for (i = 0; i < len; ++i) {
if (str[i] < 32 || str[i] > 126) {
str[i] = '?';
}
}
}
size_t modp_rtrim(char* str, size_t len)
{
while (len) {
char c = str[len -1];
if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
str[len -1] = '\0';
len -= 1;
} else {
break;
}
}
return len;
}
void test_positive(FILE * fd, const char *fname, detect_mode_t mode,
int flag_invert, int flag_true, int flag_quiet)
{
char linebuf[8192];
int issqli;
int linenum = 0;
size_t len;
sfilter sf;
while (fgets(linebuf, sizeof(linebuf), fd)) {
linenum += 1;
len = modp_rtrim(linebuf, strlen(linebuf));
if (len == 0) {
continue;
}
if (linebuf[0] == '#') {
continue;
}
len = modp_url_decode(linebuf, linebuf, len);
issqli = 0;
switch (mode) {
case MODE_SQLI: {
libinjection_sqli_init(&sf, linebuf, len, 0);
issqli = libinjection_is_sqli(&sf);
break;
}
case MODE_XSS: {
issqli = libinjection_xss(linebuf, len);
break;
}
default:
assert(0);
}
if (issqli) {
g_test_ok += 1;
} else {
g_test_fail += 1;
}
if (!flag_quiet) {
if ((issqli && flag_true && ! flag_invert) ||
(!issqli && flag_true && flag_invert) ||
!flag_true) {
modp_toprint(linebuf, len);
switch (mode) {
case MODE_SQLI: {
/*
* if we didn't find a SQLi and fingerprint from
* sqlstats is is 'sns' or 'snsns' then redo using
* plain context
*/
if (!issqli && (strcmp(sf.fingerprint, "sns") == 0 ||
strcmp(sf.fingerprint, "snsns") == 0)) {
libinjection_sqli_fingerprint(&sf, 0);
}
fprintf(stdout, "%s\t%d\t%s\t%s\t%s\n",
fname, linenum,
(issqli ? "True" : "False"), sf.fingerprint, linebuf);
break;
}
case MODE_XSS: {
fprintf(stdout, "%s\t%d\t%s\t%s\n",
fname, linenum,
(issqli ? "True" : "False"), linebuf);
break;
}
default:
assert(0);
}
}
}
}
}
static void usage(const char* argv[])
{
fprintf(stdout, "usage: %s [flags] [files...]\n", argv[0]);
fprintf(stdout, "%s\n", "");
fprintf(stdout, "%s\n", "-q --quiet : quiet mode");
fprintf(stdout, "%s\n", "-m --max-fails : number of failed cases need to fail entire test");
fprintf(stdout, "%s\n", "-s INTEGER : repeat each test N time "
"(for performance testing)");
fprintf(stdout, "%s\n", "-t : only print positive matches");
fprintf(stdout, "%s\n", "-x --mode-xss : test input for XSS");
fprintf(stdout, "%s\n", "-i --invert : invert test logic "
"(input is tested for being safe)");
fprintf(stdout, "%s\n", "");
fprintf(stdout, "%s\n", "-? -h -help --help : this page");
fprintf(stdout, "%s\n", "");
}
int main(int argc, const char *argv[])
{
/*
* invert output, by
*/
int flag_invert = FALSE;
/*
* don't print anything.. useful for
* performance monitors, gprof.
*/
int flag_quiet = FALSE;
/*
* only print postive results
* with invert, only print negative results
*/
int flag_true = FALSE;
detect_mode_t mode = MODE_SQLI;
int flag_slow = 1;
int count = 0;
int max = -1;
int i, j;
int offset = 1;
while (offset < argc) {
if (strcmp(argv[offset], "-?") == 0 ||
strcmp(argv[offset], "-h") == 0 ||
strcmp(argv[offset], "-help") == 0 ||
strcmp(argv[offset], "--help") == 0) {
usage(argv);
exit(0);
}
if (strcmp(argv[offset], "-i") == 0) {
offset += 1;
flag_invert = TRUE;
} else if (strcmp(argv[offset], "-q") == 0 ||
strcmp(argv[offset], "--quiet") == 0) {
offset += 1;
flag_quiet = TRUE;
} else if (strcmp(argv[offset], "-t") == 0) {
offset += 1;
flag_true = TRUE;
} else if (strcmp(argv[offset], "-s") == 0) {
offset += 1;
flag_slow = 100;
} else if (strcmp(argv[offset], "-m") == 0 ||
strcmp(argv[offset], "--max-fails") == 0) {
offset += 1;
max = atoi(argv[offset]);
offset += 1;
} else if (strcmp(argv[offset], "-x") == 0 ||
strcmp(argv[offset], "--mode-xss") == 0) {
mode = MODE_XSS;
offset += 1;
} else {
break;
}
}
if (offset == argc) {
test_positive(stdin, "stdin", mode, flag_invert, flag_true, flag_quiet);
} else {
for (j = 0; j < flag_slow; ++j) {
for (i = offset; i < argc; ++i) {
FILE* fd = fopen(argv[i], "r");
if (fd) {
test_positive(fd, argv[i], mode, flag_invert, flag_true, flag_quiet);
fclose(fd);
}
}
}
}
if (!flag_quiet) {
fprintf(stdout, "%s", "\n");
fprintf(stdout, "SQLI : %d\n", g_test_ok);
fprintf(stdout, "SAFE : %d\n", g_test_fail);
fprintf(stdout, "TOTAL : %d\n", g_test_ok + g_test_fail);
}
if (max == -1) {
return 0;
}
count = g_test_ok;
if (flag_invert) {
count = g_test_fail;
}
if (count > max) {
printf("\nThreshold is %d, got %d, failing.\n", max, count);
return 1;
} else {
printf("\nThreshold is %d, got %d, passing.\n", max, count);
return 0;
}
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright 2012, 2013 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* This is for testing against files in ../data/ *.txt
* Reads from stdin or a list of files, and emits if a line
* is a SQLi attack or not, and does basic statistics
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
void print_string(stoken_t* t);
void print_var(stoken_t* t);
void print_token(stoken_t *t);
void print_string(stoken_t* t)
{
/* print opening quote */
if (t->str_open != '\0') {
printf("%c", t->str_open);
}
/* print content */
printf("%s", t->val);
/* print closing quote */
if (t->str_close != '\0') {
printf("%c", t->str_close);
}
}
void print_var(stoken_t* t)
{
if (t->count >= 1) {
printf("%c", '@');
}
if (t->count == 2) {
printf("%c", '@');
}
print_string(t);
}
void print_token(stoken_t *t) {
printf("%c ", t->type);
switch (t->type) {
case 's':
print_string(t);
break;
case 'v':
print_var(t);
break;
default:
printf("%s", t->val);
}
printf("%s", "\n");
}
int main(int argc, const char* argv[])
{
size_t slen;
char* copy;
int flags = 0;
int fold = 0;
int detect = 0;
int i;
int count;
int offset = 1;
int issqli;
sfilter sf;
if (argc < 2) {
fprintf(stderr, "need more args\n");
return 1;
}
while (1) {
if (strcmp(argv[offset], "-m") == 0) {
flags |= FLAG_SQL_MYSQL;
offset += 1;
}
else if (strcmp(argv[offset], "-f") == 0 || strcmp(argv[offset], "--fold") == 0) {
fold = 1;
offset += 1;
} else if (strcmp(argv[offset], "-d") == 0 || strcmp(argv[offset], "--detect") == 0) {
detect = 1;
offset += 1;
} else if (strcmp(argv[offset], "-ca") == 0) {
flags |= FLAG_SQL_ANSI;
offset += 1;
} else if (strcmp(argv[offset], "-cm") == 0) {
flags |= FLAG_SQL_MYSQL;
offset += 1;
} else if (strcmp(argv[offset], "-q0") == 0) {
flags |= FLAG_QUOTE_NONE;
offset += 1;
} else if (strcmp(argv[offset], "-q1") == 0) {
flags |= FLAG_QUOTE_SINGLE;
offset += 1;
} else if (strcmp(argv[offset], "-q2") == 0) {
flags |= FLAG_QUOTE_DOUBLE;
offset += 1;
} else {
break;
}
}
/* ATTENTION: argv is a C-string, null terminated. We copy this
* to it's own location, WITHOUT null byte. This way, valgrind
* can see if we run past the buffer.
*/
slen = strlen(argv[offset]);
copy = (char* ) malloc(slen);
memcpy(copy, argv[offset], slen);
libinjection_sqli_init(&sf, copy, slen, flags);
if (detect == 1) {
issqli = libinjection_is_sqli(&sf);
if (issqli) {
printf("%s\n", sf.fingerprint);
}
} else if (fold == 1) {
count = libinjection_sqli_fold(&sf);
for (i = 0; i < count; ++i) {
print_token(&(sf.tokenvec[i]));
}
} else {
while (libinjection_sqli_tokenize(&sf)) {
print_token(sf.current);
}
}
free(copy);
return 0;
}

View File

@@ -0,0 +1,68 @@
/*
* A not very good test for performance. This is mostly useful in
* testing performance -regressions-
*
*/
#include <time.h>
#include <string.h>
#include <stdio.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
int testIsSQL(void);
int testIsSQL(void)
{
const char* const s[] = {
"123 LIKE -1234.5678E+2;",
"APPLE 19.123 'FOO' \"BAR\"",
"/* BAR */ UNION ALL SELECT (2,3,4)",
"1 || COS(+0X04) --FOOBAR",
"dog apple @cat banana bar",
"dog apple cat \"banana \'bar",
"102 TABLE CLOTH",
"(1001-'1') union select 1,2,3,4 from credit_cards",
NULL
};
const int imax = 1000000;
int i, j;
size_t slen;
sfilter sf;
clock_t t0,t1;
double total;
int tps;
t0 = clock();
for (i = imax, j=0; i != 0; --i, ++j) {
if (s[j] == NULL) {
j = 0;
}
slen = strlen(s[j]);
libinjection_sqli_init(&sf, s[j], slen, FLAG_QUOTE_NONE | FLAG_SQL_ANSI);
libinjection_is_sqli(&sf);
}
t1 = clock();
total = (double) (t1 - t0) / (double) CLOCKS_PER_SEC;
tps = (int)((double) imax / total);
return tps;
}
int main()
{
const int mintps = 450000;
int tps = testIsSQL();
printf("\nTPS : %d\n\n", tps);
if (tps < mintps) {
printf("FAIL: %d < %d\n", tps, mintps);
/* FAIL */
return 1;
} else {
printf("OK: %d > %d\n", tps, mintps);
/* OK */
return 0;
}
}

View File

@@ -0,0 +1,84 @@
/*
* A not very good test for performance. This is mostly useful in
* testing performance -regressions-
*
*/
#include <time.h>
#include <string.h>
#include <stdio.h>
#include "libinjection.h"
int testIsSQL(void);
int testIsSQL(void)
{
const char* const s[] = {
"<script>alert(1);</script>",
"><script>alert(1);</script>"
"x ><script>alert(1);</script>",
"' ><script>alert(1);</script>",
"\"><script>alert(1);</script>",
"red;</style><script>alert(1);</script>",
"red;}</style><script>alert(1);</script>",
"red;\"/><script>alert(1);</script>",
"');}</style><script>alert(1);</script>",
"onerror=alert(1)>",
"x onerror=alert(1);>",
"x' onerror=alert(1);>",
"x\" onerror=alert(1);>",
"<a href=\"javascript:alert(1)\">",
"<a href='javascript:alert(1)'>",
"<a href=javascript:alert(1)>",
"<a href = javascript:alert(1); >",
"<a href=\" javascript:alert(1);\" >",
"<a href=\"JAVASCRIPT:alert(1);\" >",
"123 LIKE -1234.5678E+2;",
"APPLE 19.123 'FOO' \"BAR\"",
"/* BAR */ UNION ALL SELECT (2,3,4)",
"1 || COS(+0X04) --FOOBAR",
"dog apple @cat banana bar",
"dog apple cat \"banana \'bar",
"102 TABLE CLOTH",
"(1001-'1') union select 1,2,3,4 from credit_cards",
NULL
};
const int imax = 1000000;
int i, j;
size_t slen;
clock_t t0,t1;
double total;
int tps;
t0 = clock();
for (i = imax, j=0; i != 0; --i, ++j) {
if (s[j] == NULL) {
j = 0;
}
slen = strlen(s[j]);
libinjection_xss(s[j], slen);
}
t1 = clock();
total = (double) (t1 - t0) / (double) CLOCKS_PER_SEC;
tps = (int)((double) imax / total);
return tps;
}
int main()
{
const int mintps = 500000;
int tps = testIsSQL();
printf("\nTPS : %d\n\n", tps);
if (tps < 500000) {
printf("FAIL: %d < %d\n", tps, mintps);
/* FAIL */
return 1;
} else {
printf("OK: %d > %d\n", tps, mintps);
/* OK */
return 0;
}
}

View File

@@ -0,0 +1,312 @@
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glob.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
#include "libinjection_html5.h"
#include "libinjection_xss.h"
static char g_test[8096];
static char g_input[8096];
static char g_expected[8096];
size_t modp_rtrim(char* str, size_t len);
size_t print_string(char* buf, size_t len, stoken_t* t);
size_t print_var(char* buf, size_t len, stoken_t* t);
size_t print_token(char* buf, size_t len, stoken_t *t);
int read_file(const char* fname, int flags, int testtype);
const char* h5_type_to_string(enum html5_type x);
size_t print_html5_token(char* buf, size_t len, h5_state_t* hs);
size_t modp_rtrim(char* str, size_t len)
{
while (len) {
char c = str[len -1];
if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
str[len -1] = '\0';
len -= 1;
} else {
break;
}
}
return len;
}
size_t print_string(char* buf, size_t len, stoken_t* t)
{
int slen = 0;
/* print opening quote */
if (t->str_open != '\0') {
slen = sprintf(buf + len, "%c", t->str_open);
assert(slen >= 0);
len += (size_t) slen;
}
/* print content */
slen = sprintf(buf + len, "%s", t->val);
assert(slen >= 0);
len += (size_t) slen;
/* print closing quote */
if (t->str_close != '\0') {
slen = sprintf(buf + len, "%c", t->str_close);
assert(slen >= 0);
len += (size_t) slen;
}
return len;
}
size_t print_var(char* buf, size_t len, stoken_t* t)
{
int slen = 0;
if (t->count >= 1) {
slen = sprintf(buf + len, "%c", '@');
assert(slen >= 0);
len += (size_t) slen;
}
if (t->count == 2) {
slen = sprintf(buf + len, "%c", '@');
assert(slen >= 0);
len += (size_t) slen;
}
return print_string(buf, len, t);
}
const char* h5_type_to_string(enum html5_type x)
{
switch (x) {
case DATA_TEXT: return "DATA_TEXT";
case TAG_NAME_OPEN: return "TAG_NAME_OPEN";
case TAG_NAME_CLOSE: return "TAG_NAME_CLOSE";
case TAG_NAME_SELFCLOSE: return "TAG_NAME_SELFCLOSE";
case TAG_DATA: return "TAG_DATA";
case TAG_CLOSE: return "TAG_CLOSE";
case ATTR_NAME: return "ATTR_NAME";
case ATTR_VALUE: return "ATTR_VALUE";
case TAG_COMMENT: return "TAG_COMMENT";
case DOCTYPE: return "DOCTYPE";
default:
assert(0);
}
}
size_t print_html5_token(char* buf, size_t len, h5_state_t* hs)
{
int slen;
char* tmp = (char*) malloc(hs->token_len + 1);
memcpy(tmp, hs->token_start, hs->token_len);
/* TODO.. encode to be printable */
tmp[hs->token_len] = '\0';
slen = sprintf(buf + len, "%s,%d,%s\n",
h5_type_to_string(hs->token_type),
(int) hs->token_len,
tmp);
len += (size_t) slen;
free(tmp);
return len;
}
size_t print_token(char* buf, size_t len, stoken_t *t)
{
int slen;
slen = sprintf(buf + len, "%c ", t->type);
assert(slen >= 0);
len += (size_t) slen;
switch (t->type) {
case 's':
len = print_string(buf, len, t);
break;
case 'v':
len = print_var(buf, len, t);
break;
default:
slen = sprintf(buf + len, "%s", t->val);
assert(slen >= 0);
len += (size_t) slen;
}
slen = sprintf(buf + len, "%c", '\n');
assert(slen >= 0);
len += (size_t) slen;
return len;
}
int read_file(const char* fname, int flags, int testtype)
{
int count = 0;
FILE *fp = NULL;
char linebuf[8192];
char g_actual[8192];
char* bufptr = NULL;
size_t slen;
char* copy;
sfilter sf;
int ok = 1;
int num_tokens;
int issqli;
int i;
g_test[0] = '\0';
g_input[0] = '\0';
g_expected[0] = '\0';
fp = fopen(fname, "r");
while(fgets(linebuf, sizeof(linebuf), fp) != NULL) {
if (count == 0 && strcmp(linebuf, "--TEST--\n") == 0) {
bufptr = g_test;
count = 1;
} else if (count == 1 && strcmp(linebuf, "--INPUT--\n") == 0) {
bufptr = g_input;
count = 2;
} else if (count == 2 && strcmp(linebuf, "--EXPECTED--\n") == 0) {
bufptr = g_expected;
count = 3;
} else {
assert(bufptr != NULL);
strcat(bufptr, linebuf);
}
}
fclose(fp);
if (count != 3) {
return 1;
}
g_expected[modp_rtrim(g_expected, strlen(g_expected))] = '\0';
g_input[modp_rtrim(g_input, strlen(g_input))] = '\0';
slen = strlen(g_input);
copy = (char* ) malloc(slen);
memcpy(copy, g_input, slen);
g_actual[0] = '\0';
if (testtype == 0) {
/*
* print sqli tokenization only
*/
libinjection_sqli_init(&sf, copy, slen, flags);
libinjection_sqli_callback(&sf, NULL, NULL);
slen =0;
while (libinjection_sqli_tokenize(&sf) == 1) {
slen = print_token(g_actual, slen, sf.current);
}
} else if (testtype == 1) {
/*
* testing tokenization + folding
*/
libinjection_sqli_init(&sf, copy, slen, flags);
libinjection_sqli_callback(&sf, NULL, NULL);
slen =0;
num_tokens = libinjection_sqli_fold(&sf);
for (i = 0; i < num_tokens; ++i) {
slen = print_token(g_actual, slen, libinjection_sqli_get_token(&sf, i));
}
} else if (testtype == 2) {
/**
* test sqli detection
*/
char buf[100];
issqli = libinjection_sqli(copy, slen, buf);
if (issqli) {
sprintf(g_actual, "%s", buf);
}
} else if (testtype == 3) {
/*
* test html5 tokenization only
*/
h5_state_t hs;
libinjection_h5_init(&hs, copy, slen, DATA_STATE);
slen = 0;
while (libinjection_h5_next(&hs)) {
slen = print_html5_token(g_actual, slen, &hs);
}
} else if (testtype == 4) {
/*
* test XSS detection
*/
sprintf(g_actual, "%d", libinjection_xss(copy, slen));
} else {
fprintf(stderr, "Got stange testtype value of %d\n", testtype);
assert(0);
}
g_actual[modp_rtrim(g_actual, strlen(g_actual))] = '\0';
if (strcmp(g_expected, g_actual) != 0) {
printf("INPUT: \n%s\n==\n", g_input);
printf("EXPECTED: \n%s\n==\n", g_expected);
printf("GOT: \n%s\n==\n", g_actual);
ok = 0;
}
free(copy);
return ok;
}
int main(int argc, char** argv)
{
int offset = 1;
int i;
int ok;
int count = 0;
int count_fail = 0;
int flags = 0;
int testtype = 0;
int quiet = 0;
const char* fname;
while (1) {
if (strcmp(argv[offset], "-q") == 0 || strcmp(argv[offset], "--quiet") == 0) {
quiet = 1;
offset += 1;
} else {
break;
}
}
printf("%s\n", libinjection_version());
for (i = offset; i < argc; ++i) {
fname = argv[i];
count += 1;
if (strstr(fname, "test-tokens-")) {
flags = FLAG_QUOTE_NONE | FLAG_SQL_ANSI;
testtype = 0;
} else if (strstr(fname, "test-folding-")) {
flags = FLAG_QUOTE_NONE | FLAG_SQL_ANSI;
testtype = 1;
} else if (strstr(fname, "test-sqli-")) {
flags = FLAG_NONE;
testtype = 2;
} else if (strstr(fname, "test-html5-")) {
flags = FLAG_NONE;
testtype = 3;
} else if (strstr(fname, "test-xss-")) {
flags = FLAG_NONE;
testtype = 4;
} else {
fprintf(stderr, "Unknown test type: %s, failing\n", fname);
count_fail += 1;
continue;
}
ok = read_file(fname, flags, testtype);
if (ok) {
if (! quiet) {
fprintf(stderr, "%s: ok\n", fname);
}
} else {
count_fail += 1;
if (! quiet) {
fprintf(stderr, "%s: fail\n", fname);
}
}
}
return count > 0 && count_fail > 0;
}

617
naxsi-0.55.3/naxsi.h Normal file
View File

@@ -0,0 +1,617 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __FOO_H__
#define __FOO_H__
#define NAXSI_VERSION "0.55.3"
#include <nginx.h>
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_event.h>
#include <ngx_md5.h>
#include <ngx_http_core_module.h>
#include <pcre.h>
#include <ctype.h>
#include "ext/libinjection/libinjection_sqli.h"
#include "ext/libinjection/libinjection_xss.h"
extern ngx_module_t ngx_http_naxsi_module;
/*
** as the #ifdef #endif for debug are getting really messy ...
** Bellow are all the possibles debug defines. To enable associated feature
** debug, just set it to 1. Do not comment the actual define except if you
** know all the associated debug calls are deleted.
** The idea is that the compiler will optimize out the do { if (0) ... } while (0);
*/
#define _naxsi_rawbody 0
#define _debug_basestr_ruleset 0
#define _debug_custom_score 0
#define _debug_body_parse 0
#define _debug_cfg_parse_one_rule 0
#define _debug_zone 0
#define _debug_extensive_log 0
#define _debug_loc_conf 0
#define _debug_main_conf 0
#define _debug_mechanics 0
#define _debug_json 0
#define _debug_modifier 0
#define _debug_payload_handler 0
#define _debug_post_heavy 0
#define _debug_rawbody 0
#define _debug_readconf 0
#define _debug_rx 0
#define _debug_score 0
#define _debug_spliturl_ruleset 0
#define _debug_whitelist_compat 0
#define _debug_whitelist 0
#define _debug_whitelist_heavy 0
#define _debug_whitelist_light 0
#define wl_debug_rx 0
#ifndef __NAXSI_DEBUG
#define __NAXSI_DEBUG
#define NX_DEBUG(FEATURE, DEF, LOG, ST, ...) do { if (FEATURE) ngx_log_debug(DEF, LOG, ST, __VA_ARGS__); } while (0)
#endif
#ifndef __NAXSI_LOG_DEBUG
#define __NAXSI_LOG_DEBUG
#define NX_LOG_DEBUG(FEATURE, DEF, LOG, ST, ...) do { if (FEATURE) ngx_conf_log_error(DEF, LOG, ST, __VA_ARGS__); } while (0)
#endif
/*
** Here is globally how the structures are organized :
**
** [[ngx_http_dummy_main_conf_t]] is the main structure for the module.
** it contains the core rules and a set of ngx_http_dummy_loc_conf_t,
** for each NGINX location.
** ---
** [[ngx_http_dummy_loc_conf_t]] is the main structure for any NGINX
** locations, that is - a web site. It contains both pointers to the core
** rules, as well as whitelists, scores, denied_url and all flags. all
** the data of a nginx location is held into the loc_conf_t struct.
** The sets of rules are actually containted into [[ngx_http_rule_t]] structs.
** ---
** [[ngx_http_rule_t]] structs are used to hold any info about a rule, as well
** as whitelists. (whitelists is just a 'kind' of rule).
**
*/
/*
** basic rule can have 4 (so far) kind of matching mechanisms
** RX
** STR
** LIBINJ_XSS
** LIBINJ_SQL
*/
enum DETECT_MECHANISM {
NONE = -1,
RX,
STR,
LIBINJ_XSS,
LIBINJ_SQL
};
enum MATCH_TYPE {
URI_ONLY=0,
NAME_ONLY,
MIXED
};
enum DUMMY_MATCH_ZONE {
HEADERS=0,
URL,
ARGS,
BODY,
RAW_BODY,
FILE_EXT,
UNKNOWN
};
/*
** struct used to store a specific match zone
** in conf : MATCH_ZONE:[GET_VAR|HEADER|POST_VAR]:VAR_NAME:
*/
typedef struct
{
/* match in [name] var of body */
ngx_flag_t body_var:1;
/* match in [name] var of headers */
ngx_flag_t headers_var:1;
/* match in [name] var of args */
ngx_flag_t args_var:1;
/* match on URL [name] */
ngx_flag_t specific_url:1;
ngx_str_t target;
/* to be used for regexed match zones */
ngx_regex_compile_t *target_rx;
ngx_uint_t hash;
} ngx_http_custom_rule_location_t;
/*
** WhiteList Rules Definition :
** A whitelist contains :
** - an URI
**
** - one or several sets containing :
** - an variable name ('foo') associated with a zone ($GET_VAR:foo)
** - one or several rules id to whitelist
*/
typedef struct
{
/* match in full body (POST DATA) */
ngx_flag_t body:1;
/* match in [name] var of body */
ngx_flag_t body_var:1;
/* match in all headers */
ngx_flag_t headers:1;
/* match in [name] var of headers */
ngx_flag_t headers_var:1;
/* match in URI */
ngx_flag_t url:1;
/* match in args (bla.php?<ARGS>) */
ngx_flag_t args:1;
/* match in [name] var of args */
ngx_flag_t args_var:1;
/* match on a global flag : weird_request, big_body etc. */
ngx_flag_t flags:1;
/* match on file upload extension */
ngx_flag_t file_ext:1;
/* set if defined "custom" match zone (GET_VAR/POST_VAR/...) */
ngx_array_t *ids;
ngx_str_t *name;
} ngx_http_whitelist_location_t;
/*
** this struct is used to aggregate all whitelist
** that point to the same URI or the same VARNAME
** all the "subrules" will then be stored in the "whitelist_locations"
*/
typedef struct
{
/*ngx_http_whitelist_location_t **/
ngx_array_t *whitelist_locations;
/* zone to wich the WL applies */
enum DUMMY_MATCH_ZONE zone;
/* if the "name" is only an url, specify it */
int uri_only:1;
/* does the rule targets the name
instead of the content ?*/
int target_name;
ngx_str_t *name;
ngx_int_t hash;
ngx_array_t *ids;
} ngx_http_whitelist_rule_t;
/* basic rule */
typedef struct
{
ngx_str_t *str; // string
ngx_regex_compile_t *rx; // or regex
/*
** basic rule can have 4 (so far) kind of matching mechanisms :
** RX, STR, LIBINJ_XSS, LIBINJ_SQL
*/
enum DETECT_MECHANISM match_type;
/* is the match zone a regex or a string (hashtable) */
ngx_int_t rx_mz;
/* ~~~~~ match zones ~~~~~~ */
ngx_int_t zone;
/* match in full body (POST DATA) */
ngx_flag_t body:1;
ngx_flag_t raw_body:1;
ngx_flag_t body_var:1;
/* match in all headers */
ngx_flag_t headers:1;
ngx_flag_t headers_var:1;
/* match in URI */
ngx_flag_t url:1;
/* match in args (bla.php?<ARGS>) */
ngx_flag_t args:1;
ngx_flag_t args_var:1;
/* match on flags (weird_uri, big_body etc. */
ngx_flag_t flags:1;
/* match on file upload extension */
ngx_flag_t file_ext:1;
/* set if defined "custom" match zone (GET_VAR/POST_VAR/...) */
ngx_flag_t custom_location:1;
ngx_int_t custom_location_only;
/* does the rule targets variable name instead ? */
ngx_int_t target_name;
/* custom location match zones list (GET_VAR/POST_VAR ...) */
ngx_array_t *custom_locations;
/* ~~~~~~~ specific flags ~~~~~~~~~ */
ngx_flag_t negative:1;
} ngx_http_basic_rule_t;
/* define for RULE TYPE in rule_t */
#define BR 1
/* flags used for 'custom match rules', like $XSS > 7 */
#define SUP 1
#define SUP_OR_EQUAL 2
#define INF 3
#define INF_OR_EQUAL 4
/*
** This struct is used to store custom scores at runtime.
** ie : $XSS = 7
** tag is the $XSS and sc_score is 7
*/
typedef struct
{
ngx_str_t *sc_tag;
ngx_int_t sc_score;
ngx_flag_t block:1;
ngx_flag_t allow:1;
ngx_flag_t drop:1;
ngx_flag_t log:1;
} ngx_http_special_score_t;
/*
** This one is very related to the previous one,
** it's used to store a score rule comparison.
** ie : $XSS > 7
*/
typedef struct
{
ngx_str_t sc_tag;
ngx_int_t sc_score;
ngx_int_t cmp;
ngx_flag_t block:1;
ngx_flag_t allow:1;
ngx_flag_t drop:1;
ngx_flag_t log:1;
} ngx_http_check_rule_t;
/* TOP level rule structure */
typedef struct
{
/* type of the rule */
ngx_int_t type;
/* simply put a flag if it's a wlr,
wl_id array will be used to store the whitelisted IDs */
ngx_flag_t whitelist:1;
ngx_array_t *wlid_array;
/* "common" data for all rules */
ngx_int_t rule_id;
ngx_str_t *log_msg; // a specific log message
ngx_int_t score; //also handles DENY and ALLOW
/* List of scores increased on rule match. */
ngx_array_t *sscores;
ngx_flag_t sc_block:1; //
ngx_flag_t sc_allow:1; //
// end of specific score tag stuff
ngx_flag_t block:1;
ngx_flag_t allow:1;
ngx_flag_t drop:1;
ngx_flag_t log:1;
/* pointers on specific rule stuff */
ngx_http_basic_rule_t *br;
} ngx_http_rule_t;
typedef struct
{
ngx_array_t *get_rules; /*ngx_http_rule_t*/
ngx_array_t *body_rules;
ngx_array_t *header_rules;
ngx_array_t *generic_rules;
ngx_array_t *raw_body_rules;
ngx_array_t *locations; /*ngx_http_dummy_loc_conf_t*/
ngx_log_t *log;
} ngx_http_dummy_main_conf_t;
/* TOP level configuration structure */
typedef struct
{
/*
** basicrule / mainrules, sorted by target zone
*/
ngx_array_t *get_rules;
ngx_array_t *body_rules;
ngx_array_t *raw_body_rules;
ngx_array_t *header_rules;
ngx_array_t *generic_rules;
ngx_array_t *check_rules;
/* raw array of whitelisted rules */
ngx_array_t *whitelist_rules;
/* raw array of transformed whitelists */
ngx_array_t *tmp_wlr;
/* raw array of regex-mz whitelists */
ngx_array_t *rxmz_wlr;
/* hash table of whitelisted URL rules */
ngx_hash_t *wlr_url_hash;
/* hash table of whitelisted ARGS rules */
ngx_hash_t *wlr_args_hash;
/* hash table of whitelisted BODY rules */
ngx_hash_t *wlr_body_hash;
/* hash table of whitelisted HEADERS rules */
ngx_hash_t *wlr_headers_hash;
/* rules that are globally disabled in one location */
ngx_array_t *disabled_rules;
/* counters for both processed requests and
blocked requests, used in naxsi_fmt */
size_t request_processed;
size_t request_blocked;
ngx_int_t error;
ngx_array_t *persistant_data;
ngx_flag_t extensive:1;
ngx_flag_t learning:1;
ngx_flag_t enabled:1;
ngx_flag_t force_disabled:1;
ngx_flag_t pushed:1;
ngx_flag_t libinjection_sql_enabled:1;
ngx_flag_t libinjection_xss_enabled:1;
ngx_str_t *denied_url;
/* precomputed hash for dynamic variable lookup,
variable themselves are boolean */
ngx_uint_t flag_enable_h;
ngx_uint_t flag_learning_h;
ngx_uint_t flag_post_action_h;
ngx_uint_t flag_extensive_log_h;
/* precomputed hash for
libinjection dynamic flags */
ngx_uint_t flag_libinjection_xss_h;
ngx_uint_t flag_libinjection_sql_h;
} ngx_http_dummy_loc_conf_t;
/*
** used to store sets of matched rules during runtime
*/
typedef struct
{
/* matched in [name] var of body */
ngx_flag_t body_var:1;
/* matched in [name] var of headers */
ngx_flag_t headers_var:1;
/* matched in [name] var of args */
ngx_flag_t args_var:1;
/* matched on URL */
ngx_flag_t url:1;
/* matched in filename [name] of args*/
ngx_flag_t file_ext:1;
/* matched within the 'NAME' */
ngx_flag_t target_name:1;
ngx_str_t *name;
ngx_http_rule_t *rule;
} ngx_http_matched_rule_t;
/*
** Context structure
*/
typedef struct
{
ngx_array_t *special_scores;
ngx_int_t score;
/* blocking flags */
ngx_flag_t log:1;
ngx_flag_t block:1;
ngx_flag_t allow:1;
ngx_flag_t drop:1;
/* state */
ngx_flag_t wait_for_body:1;
ngx_flag_t ready:1;
ngx_flag_t over:1;
/* matched rules */
ngx_array_t *matched;
/* runtime flags (modifiers) */
ngx_flag_t learning:1;
ngx_flag_t enabled:1;
ngx_flag_t post_action:1;
ngx_flag_t extensive_log:1;
/* did libinjection sql/xss matched ? */
ngx_flag_t libinjection_sql:1;
ngx_flag_t libinjection_xss:1;
} ngx_http_request_ctx_t;
/*
** this structure is used only for json parsing.
*/
typedef struct ngx_http_nx_json_s {
ngx_str_t json;
u_char *src;
ngx_int_t off, len;
u_char c;
int depth;
ngx_http_request_t *r;
ngx_http_request_ctx_t *ctx;
ngx_str_t ckey;
ngx_http_dummy_main_conf_t *main_cf;
ngx_http_dummy_loc_conf_t *loc_cf;
} ngx_json_t;
#define TOP_DENIED_URL_T "DeniedUrl"
#define TOP_LEARNING_FLAG_T "LearningMode"
#define TOP_ENABLED_FLAG_T "SecRulesEnabled"
#define TOP_DISABLED_FLAG_T "SecRulesDisabled"
#define TOP_CHECK_RULE_T "CheckRule"
#define TOP_BASIC_RULE_T "BasicRule"
#define TOP_MAIN_BASIC_RULE_T "MainRule"
#define TOP_LIBINJECTION_SQL_T "LibInjectionSql"
#define TOP_LIBINJECTION_XSS_T "LibInjectionXss"
/* nginx-style names */
#define TOP_DENIED_URL_N "denied_url"
#define TOP_LEARNING_FLAG_N "learning_mode"
#define TOP_ENABLED_FLAG_N "rules_enabled"
#define TOP_DISABLED_FLAG_N "rules_disabled"
#define TOP_CHECK_RULE_N "check_rule"
#define TOP_BASIC_RULE_N "basic_rule"
#define TOP_MAIN_BASIC_RULE_N "main_rule"
#define TOP_LIBINJECTION_SQL_N "libinjection_sql"
#define TOP_LIBINJECTION_XSS_N "libinjection_xss"
/*possible 'tokens' in rule */
#define ID_T "id:"
#define SCORE_T "s:"
#define MSG_T "msg:"
#define RX_T "rx:"
#define STR_T "str:"
#define MATCH_ZONE_T "mz:"
#define WHITELIST_T "wl:"
#define LIBINJ_XSS_T "d:libinj_xss"
#define LIBINJ_SQL_T "d:libinj_sql"
#define NEGATIVE_T "negative"
/*
** name of hardcoded variables to
** change behavior of naxsi at runtime
*/
#define RT_EXTENSIVE_LOG "naxsi_extensive_log"
#define RT_ENABLE "naxsi_flag_enable"
#define RT_LEARNING "naxsi_flag_learning"
#define RT_POST_ACTION "naxsi_flag_post_action"
#define RT_LIBINJECTION_SQL "naxsi_flag_libinjection_sql"
#define RT_LIBINJECTION_XSS "naxsi_flag_libinjection_xss"
/*
** To avoid getting DoS'ed, define max depth
** for JSON parser, as it is recursive
*/
#define JSON_MAX_DEPTH 10
void *ngx_http_dummy_cfg_parse_one_rule(ngx_conf_t *cf,
ngx_str_t *value,
ngx_http_rule_t *rule,
ngx_int_t nb_elem);
char *strfaststr(unsigned char *haystack, unsigned int hl,
unsigned char *needle, unsigned int nl);
char *strnchr(const char *s, int c, int len);
ngx_int_t ngx_http_dummy_create_hashtables_n(ngx_http_dummy_loc_conf_t *dlc,
ngx_conf_t *cf);
void ngx_http_dummy_data_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r);
ngx_int_t ngx_http_output_forbidden_page(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r);
int nx_check_ids(ngx_int_t match_id, ngx_array_t *wl_ids);
int naxsi_unescape(ngx_str_t *str);
void ngx_http_dummy_json_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r,
u_char *src,
u_int len);
void ngx_http_libinjection(ngx_pool_t *pool,
ngx_str_t *name,
ngx_str_t *value,
ngx_http_request_ctx_t *ctx,
ngx_http_request_t *req,
enum DUMMY_MATCH_ZONE zone);
/*
** JSON parsing prototypes.
*/
ngx_int_t ngx_http_nx_json_forward(ngx_json_t *js) ;
ngx_int_t ngx_http_nx_json_seek(ngx_json_t *js, unsigned char seek);
ngx_int_t ngx_http_nx_json_quoted(ngx_json_t *js, ngx_str_t *ve);
ngx_int_t ngx_http_nx_json_array(ngx_json_t *js);
ngx_int_t ngx_http_nx_json_val(ngx_json_t *js);
ngx_int_t ngx_http_nx_json_obj(ngx_json_t *js);
/*
** naxsi_runtime
**
*/
void ngx_http_dummy_update_current_ctx_status(ngx_http_request_ctx_t *ctx,
ngx_http_dummy_loc_conf_t *cf,
ngx_http_request_t *r);
int ngx_http_process_basic_rule_buffer(ngx_str_t *str, ngx_http_rule_t *rl,
ngx_int_t *match);
void ngx_http_dummy_payload_handler(ngx_http_request_t *r);
int ngx_http_basestr_ruleset_n(ngx_pool_t *pool,
ngx_str_t *name,
ngx_str_t *value,
ngx_array_t *rules,
ngx_http_request_t *req,
ngx_http_request_ctx_t *ctx,
enum DUMMY_MATCH_ZONE zone);
void ngx_http_dummy_body_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r,
ngx_http_dummy_loc_conf_t *cf,
ngx_http_dummy_main_conf_t *main_cf);
void naxsi_log_offending(ngx_str_t *name, ngx_str_t *val, ngx_http_request_t *req,
ngx_http_rule_t *rule, enum DUMMY_MATCH_ZONE zone, ngx_int_t target_name);
int ngx_http_apply_rulematch_v_n(ngx_http_rule_t *r, ngx_http_request_ctx_t *ctx,
ngx_http_request_t *req, ngx_str_t *name,
ngx_str_t *value, enum DUMMY_MATCH_ZONE zone,
ngx_int_t nb_match, ngx_int_t target_name);
/*
** externs for internal rules that requires it.
*/
extern ngx_http_rule_t *nx_int__libinject_sql;
extern ngx_http_rule_t *nx_int__libinject_xss;
/*libinjection_xss wrapper not exported by libinject_xss.h.*/
int libinjection_xss(const char* s, size_t len);
#endif

593
naxsi-0.55.3/naxsi_config.c Normal file
View File

@@ -0,0 +1,593 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "naxsi.h"
/*
** TOP LEVEL configuration parsing code
*/
/*
** code to parse FLAGS and OPTIONS on each line.
*/
void *dummy_id(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_score(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_msg(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_rx(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_zone(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_str(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_negative(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_libinj_xss(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_libinj_sql(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_whitelist(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
/*
** Structures related to the configuration parser
*/
typedef struct {
char *prefix;
void *(*pars)(ngx_conf_t *, ngx_str_t *, ngx_http_rule_t *);
} ngx_http_dummy_parser_t;
static ngx_http_dummy_parser_t rule_parser[] = {
{ID_T, dummy_id},
{SCORE_T, dummy_score},
{MSG_T, dummy_msg},
{RX_T, dummy_rx},
{STR_T, dummy_str},
{LIBINJ_XSS_T, dummy_libinj_xss},
{LIBINJ_SQL_T, dummy_libinj_sql},
{MATCH_ZONE_T, dummy_zone},
{NEGATIVE_T, dummy_negative},
{WHITELIST_T, dummy_whitelist},
{NULL, NULL}
};
void *
dummy_negative(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
rule->br->negative = 1;
return (NGX_CONF_OK);
}
void *
dummy_libinj_xss(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
rule->br->match_type = LIBINJ_XSS;
return (NGX_CONF_OK);
}
void *
dummy_libinj_sql(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
rule->br->match_type = LIBINJ_SQL;
return (NGX_CONF_OK);
}
void *
dummy_score(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
int score, len;
char *tmp_ptr, *tmp_end;
ngx_http_special_score_t *sc;
rule->score = 0;
rule->block = 0;
rule->allow = 0;
rule->drop = 0;
tmp_ptr = (char *) (tmp->data + strlen(SCORE_T));
NX_LOG_DEBUG(_debug_score, NGX_LOG_EMERG, r, 0,
"XX-(debug) dummy score (%V)",
tmp);
/*allocate scores array*/
if (!rule->sscores) {
rule->sscores = ngx_array_create(r->pool, 1, sizeof(ngx_http_special_score_t));
}
while (*tmp_ptr) {
if (tmp_ptr[0] == '$') {
NX_LOG_DEBUG(_debug_score, NGX_LOG_EMERG, r, 0,
"XX-(debug) special scoring rule (%s)",
tmp_ptr);
tmp_end = strchr(tmp_ptr, ':');
if (!tmp_end)
return (NGX_CONF_ERROR);
len = tmp_end - tmp_ptr;
if (len <= 0)
return (NGX_CONF_ERROR);
sc = ngx_array_push(rule->sscores);
if (!sc)
return (NGX_CONF_ERROR);
sc->sc_tag = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
if (!sc->sc_tag)
return (NGX_CONF_ERROR);
sc->sc_tag->data = ngx_pcalloc(r->pool, len+1);
if (!sc->sc_tag->data)
return (NGX_CONF_ERROR);
//memset(rule->sc_tag->data, 0, len+1);
memcpy(sc->sc_tag->data, tmp_ptr, len);
sc->sc_tag->len = len;
sc->sc_score = atoi(tmp_end+1);
NX_LOG_DEBUG(_debug_score, NGX_LOG_EMERG, r, 0,
"XX-(debug) special scoring (%V) => (%d)",
sc->sc_tag, sc->sc_score);
/* move to end of score. */
while ( /*don't overflow*/((unsigned int)((unsigned char *)tmp_ptr - tmp->data)) < tmp->len &&
/*and seek for next score */ *tmp_ptr != ',')
++tmp_ptr;
}
else if (tmp_ptr[0] == ',')
++tmp_ptr;
else if (!strcasecmp(tmp_ptr, "BLOCK")) {
rule->block = 1;
tmp_ptr += 5;
}
else if (!strcasecmp(tmp_ptr, "DROP")) {
rule->drop = 1;
tmp_ptr += 4;
}
else if (!strcasecmp(tmp_ptr, "ALLOW")) {
rule->allow = 1;
tmp_ptr += 5;
}
else if (!strcasecmp(tmp_ptr, "LOG")) {
rule->log = 1;
tmp_ptr += 3;
}
//or maybe you just want to assign a score
else if ( (tmp_ptr[0] >= '0' && tmp_ptr[0] <= '9') || tmp_ptr[0] == '-') {
score = atoi((const char *)tmp->data+2);
rule->score = score;
break;
}
else
return (NGX_CONF_ERROR);
}
#if defined(_debug_score) && _debug_score != 0
unsigned int z;
ngx_http_special_score_t *scr;
scr = rule->sscores->elts;
if (rule->sscores) {
for (z = 0; z < rule->sscores->nelts; z++) {
ngx_conf_log_error(NGX_LOG_EMERG, r, 0,
"XX-score n°%d special scoring (%V) => (%d)",
z, scr[z].sc_tag, scr[z].sc_score);
}
}
else
ngx_conf_log_error(NGX_LOG_EMERG, r, 0,
"XX-no custom scores for this rule.");
#endif
return (NGX_CONF_OK);
}
void *
dummy_zone(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
int tmp_len, has_zone=0;
ngx_http_custom_rule_location_t *custom_rule;
char *tmp_ptr, *tmp_end;
if (!rule->br)
return (NGX_CONF_ERROR);
tmp_ptr = (char *) tmp->data+strlen(MATCH_ZONE_T);
while (*tmp_ptr) {
if (tmp_ptr[0] == '|')
tmp_ptr++;
/* match global zones */
if (!strncmp(tmp_ptr, "RAW_BODY", strlen("RAW_BODY"))) {
rule->br->raw_body = 1;
tmp_ptr += strlen("RAW_BODY");
has_zone = 1;
continue;
}
else
if (!strncmp(tmp_ptr, "BODY", strlen("BODY"))) {
rule->br->body = 1;
tmp_ptr += strlen("BODY");
has_zone = 1;
continue;
}
else
if (!strncmp(tmp_ptr, "HEADERS", strlen("HEADERS"))) {
rule->br->headers = 1;
tmp_ptr += strlen("HEADERS");
has_zone = 1;
continue;
}
else
if (!strncmp(tmp_ptr, "URL", strlen("URL"))) {
rule->br->url = 1;
tmp_ptr += strlen("URL");
has_zone = 1;
continue;
}
else
if (!strncmp(tmp_ptr, "ARGS", strlen("ARGS"))) {
rule->br->args = 1;
tmp_ptr += strlen("ARGS");
has_zone = 1;
continue;
}
else
/* match against variable name*/
if (!strncmp(tmp_ptr, "NAME", strlen("NAME"))) {
rule->br->target_name = 1;
tmp_ptr += strlen("NAME");
has_zone = 1;
continue;
}
else
/* for file_ext, just push'em in the body rules.
when multipart parsing comes in, it'll tag the zone as
FILE_EXT as the rule will be pushed in body rules it'll be
checked !*/
if (!strncmp(tmp_ptr, "FILE_EXT", strlen("FILE_EXT"))) {
rule->br->file_ext = 1;
rule->br->body = 1;
tmp_ptr += strlen("FILE_EXT");
has_zone = 1;
continue;
}
else
/* custom match zones */
#define MZ_GET_VAR_T "$ARGS_VAR:"
#define MZ_HEADER_VAR_T "$HEADERS_VAR:"
#define MZ_POST_VAR_T "$BODY_VAR:"
#define MZ_SPECIFIC_URL_T "$URL:"
//probably a custom zone
if (tmp_ptr[0] == '$') {
// tag as a custom_location rule.
rule->br->custom_location = 1;
if (!rule->br->custom_locations) {
rule->br->custom_locations = ngx_array_create(r->pool, 1,
sizeof(ngx_http_custom_rule_location_t));
if (!rule->br->custom_locations)
return (NGX_CONF_ERROR);
}
custom_rule = ngx_array_push(rule->br->custom_locations);
if (!custom_rule)
return (NGX_CONF_ERROR);
memset(custom_rule, 0, sizeof(ngx_http_custom_rule_location_t));
if (!strncmp(tmp_ptr, MZ_GET_VAR_T, strlen(MZ_GET_VAR_T))) {
has_zone = 1;
custom_rule->args_var = 1;
rule->br->args_var = 1;
tmp_ptr += strlen(MZ_GET_VAR_T);
}
else if (!strncmp(tmp_ptr, MZ_POST_VAR_T,
strlen(MZ_POST_VAR_T))) {
has_zone = 1;
custom_rule->body_var = 1;
rule->br->body_var = 1;
tmp_ptr += strlen(MZ_POST_VAR_T);
}
else if (!strncmp(tmp_ptr, MZ_HEADER_VAR_T,
strlen(MZ_HEADER_VAR_T))) {
has_zone = 1;
custom_rule->headers_var = 1;
rule->br->headers_var = 1;
tmp_ptr += strlen(MZ_HEADER_VAR_T);
}
else if (!strncmp(tmp_ptr, MZ_SPECIFIC_URL_T,
strlen(MZ_SPECIFIC_URL_T))) {
custom_rule->specific_url = 1;
tmp_ptr += strlen(MZ_SPECIFIC_URL_T);
}
else
/* add support for regex-style match zones.
** this whole function should be rewritten as it's getting
** messy as hell
*/
#define MZ_GET_VAR_X "$ARGS_VAR_X:"
#define MZ_HEADER_VAR_X "$HEADERS_VAR_X:"
#define MZ_POST_VAR_X "$BODY_VAR_X:"
#define MZ_SPECIFIC_URL_X "$URL_X:"
/*
** if the rule is a negative rule (has an ID, not a WL field)
** we need to pre-compile the regex for runtime.
** Don't do it for whitelists, as its done in a separate manner.
*/
if (!strncmp(tmp_ptr, MZ_GET_VAR_X, strlen(MZ_GET_VAR_X))) {
has_zone = 1;
custom_rule->args_var = 1;
rule->br->args_var = 1;
rule->br->rx_mz = 1;
tmp_ptr += strlen(MZ_GET_VAR_X);
}
else if (!strncmp(tmp_ptr, MZ_POST_VAR_X,
strlen(MZ_POST_VAR_X))) {
has_zone = 1;
rule->br->rx_mz = 1;
custom_rule->body_var = 1;
rule->br->body_var = 1;
tmp_ptr += strlen(MZ_POST_VAR_X);
}
else if (!strncmp(tmp_ptr, MZ_HEADER_VAR_X,
strlen(MZ_HEADER_VAR_X))) {
has_zone = 1;
custom_rule->headers_var = 1;
rule->br->headers_var = 1;
rule->br->rx_mz = 1;
tmp_ptr += strlen(MZ_HEADER_VAR_X);
}
else if (!strncmp(tmp_ptr, MZ_SPECIFIC_URL_X,
strlen(MZ_SPECIFIC_URL_X))) {
custom_rule->specific_url = 1;
rule->br->rx_mz = 1;
tmp_ptr += strlen(MZ_SPECIFIC_URL_X);
}
else
return (NGX_CONF_ERROR);
/* else
return (NGX_CONF_ERROR);*/
tmp_end = strchr((const char *) tmp_ptr, '|');
if (!tmp_end)
tmp_end = tmp_ptr + strlen(tmp_ptr);
tmp_len = tmp_end - tmp_ptr;
if (tmp_len <= 0)
return (NGX_CONF_ERROR);
custom_rule->target.data = ngx_pcalloc(r->pool, tmp_len+1);
if (!custom_rule->target.data)
return (NGX_CONF_ERROR);
custom_rule->target.len = tmp_len;
memcpy(custom_rule->target.data, tmp_ptr, tmp_len);
/*
** pre-compile regex !
*/
if (rule->br->rx_mz == 1) {
custom_rule->target_rx = ngx_pcalloc(r->pool, sizeof(ngx_regex_compile_t));
if (!custom_rule->target_rx)
return (NGX_CONF_ERROR);
custom_rule->target_rx->options = PCRE_CASELESS|PCRE_MULTILINE;
custom_rule->target_rx->pattern = custom_rule->target;
custom_rule->target_rx->pool = r->pool;
custom_rule->target_rx->err.len = 0;
custom_rule->target_rx->err.data = NULL;
if (ngx_regex_compile(custom_rule->target_rx) != NGX_OK) {
NX_LOG_DEBUG(_debug_rx, NGX_LOG_EMERG, r, 0, "XX-FAILED RX:%V",
custom_rule->target);
return (NGX_CONF_ERROR);
}
}
custom_rule->hash = ngx_hash_key_lc(custom_rule->target.data,
custom_rule->target.len);
NX_LOG_DEBUG(_debug_zone, NGX_LOG_EMERG, r, 0, "XX- ZONE:[%V]",
&(custom_rule->target));
tmp_ptr += tmp_len;
continue;
}
else
return (NGX_CONF_ERROR);
}
/*
** ensure the match-zone actually returns a zone :)
*/
if (has_zone == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, r, 0,
"matchzone doesn't target an actual zone.");
return (NGX_CONF_ERROR);
}
return (NGX_CONF_OK);
}
void *
dummy_id(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
rule->rule_id = atoi((const char *) tmp->data+strlen(ID_T));
return (NGX_CONF_OK);
}
void *
dummy_str(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
ngx_str_t *str;
uint i;
if (!rule->br)
return (NGX_CONF_ERROR);
rule->br->match_type = STR;
str = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
if (!str)
return (NGX_CONF_ERROR);
str->data = tmp->data + strlen(STR_T);
str->len = tmp->len - strlen(STR_T);
for (i = 0; i < str->len; i++)
str->data[i] = tolower(str->data[i]);
rule->br->str = str;
return (NGX_CONF_OK);
}
void *
dummy_msg(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
ngx_str_t *str;
if (!rule->br)
return (NGX_CONF_ERROR);
str = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
if (!str)
return (NGX_CONF_ERROR);
str->data = tmp->data + strlen(STR_T);
str->len = tmp->len - strlen(STR_T);
rule->log_msg = str;
return (NGX_CONF_OK);
}
void *
dummy_whitelist(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
ngx_array_t *wl_ar;
unsigned int i, ct;
ngx_int_t *id;
ngx_str_t str;
str.data = tmp->data + strlen(WHITELIST_T);
str.len = tmp->len - strlen(WHITELIST_T);
for (ct = 1, i = 0; i < str.len; i++)
if (str.data[i] == ',')
ct++;
wl_ar = ngx_array_create(r->pool, ct, sizeof(ngx_int_t));
if (!wl_ar)
return (NGX_CONF_ERROR);
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, r, 0, "XX- allocated %d elems for WL", ct);
for (i = 0; i < str.len; i++) {
if (i == 0 || str.data[i-1] == ',') {
id = (ngx_int_t *) ngx_array_push(wl_ar);
if (!id)
return (NGX_CONF_ERROR);
*id = (ngx_int_t) atoi((const char *)str.data+i);
}
}
rule->wlid_array = wl_ar;
return (NGX_CONF_OK);
}
void *
dummy_rx(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
ngx_regex_compile_t *rgc;
ngx_str_t ha;
if (!rule->br)
return (NGX_CONF_ERROR);
rule->br->match_type = RX;
//just prepare a string to hold the directive without 'rx:'
ha.data = tmp->data+strlen(RX_T);
ha.len = tmp->len-strlen(RX_T);
rgc = ngx_pcalloc(r->pool, sizeof(ngx_regex_compile_t));
if (!rgc)
return (NGX_CONF_ERROR);
rgc->options = PCRE_CASELESS|PCRE_MULTILINE;
rgc->pattern = ha;
rgc->pool = r->pool;
rgc->err.len = 0;
rgc->err.data = NULL;
if (ngx_regex_compile(rgc) != NGX_OK) {
NX_LOG_DEBUG(_debug_rx, NGX_LOG_EMERG, r, 0, "XX-FAILED RX:%V",
tmp);
return (NGX_CONF_ERROR);
}
rule->br->rx = rgc;
NX_LOG_DEBUG(_debug_rx, NGX_LOG_EMERG, r, 0, "XX- RX:[%V]",
&(rule->br->rx->pattern));
return (NGX_CONF_OK);
}
/* Parse one rule line */
/*
** in : nb elem, value array, rule to fill
** does : creates a rule struct from configuration line
** For each element name matching a tag
** (cf. rule_parser), then call the associated func.
*/
void *
ngx_http_dummy_cfg_parse_one_rule(ngx_conf_t *cf,
ngx_str_t *value,
ngx_http_rule_t *current_rule,
ngx_int_t nb_elem)
{
int i, z;
void *ret;
int valid;
if (!value || !value[0].data)
return NGX_CONF_ERROR;
/*
** parse basic rule
*/
if (!ngx_strcmp(value[0].data, TOP_CHECK_RULE_T) ||
!ngx_strcmp(value[0].data, TOP_CHECK_RULE_N) ||
!ngx_strcmp(value[0].data, TOP_BASIC_RULE_T) ||
!ngx_strcmp(value[0].data, TOP_BASIC_RULE_N) ||
!ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_T) ||
!ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_N)) {
NX_LOG_DEBUG(_debug_cfg_parse_one_rule, NGX_LOG_EMERG, cf, 0, "naxsi-basic rule %V", &(value[1]));
current_rule->type = BR;
current_rule->br = ngx_pcalloc(cf->pool, sizeof(ngx_http_basic_rule_t));
if (!current_rule->br)
return (NGX_CONF_ERROR);
}
else {
NX_LOG_DEBUG(_debug_cfg_parse_one_rule, NGX_LOG_EMERG, cf, 0,
"Unknown start keyword in rule %V", &(value[1]));
return (NGX_CONF_ERROR);
}
// check each word of config line against each rule
for(i = 1; i < nb_elem && value[i].len > 0; i++) {
valid = 0;
for (z = 0; rule_parser[z].pars; z++) {
if (!ngx_strncmp(value[i].data,
rule_parser[z].prefix,
strlen(rule_parser[z].prefix))) {
ret = rule_parser[z].pars(cf, &(value[i]),
current_rule);
if (ret != NGX_CONF_OK) {
NX_LOG_DEBUG(_debug_cfg_parse_one_rule, NGX_LOG_EMERG, cf, 0,
"XX-FAILED PARSING '%s'",
value[i].data);
return (ret);
}
valid = 1;
}
}
if (!valid)
return (NGX_CONF_ERROR);
}
/* validate the structure, and fill empty fields.*/
if (!current_rule->log_msg)
{
current_rule->log_msg = ngx_pcalloc(cf->pool, sizeof(ngx_str_t));
current_rule->log_msg->data = NULL;
current_rule->log_msg->len = 0;
}
return (NGX_CONF_OK);
}

View File

@@ -0,0 +1,83 @@
##################################
## INTERNAL RULES IDS:1-999 ##
##################################
#@MainRule "msg:weird request, unable to parse" id:1;
#@MainRule "msg:request too big, stored on disk and not parsed" id:2;
#@MainRule "msg:invalid hex encoding, null bytes" id:10;
#@MainRule "msg:unknown content-type" id:11;
#@MainRule "msg:invalid formatted url" id:12;
#@MainRule "msg:invalid POST format" id:13;
#@MainRule "msg:invalid POST boundary" id:14;
#@MainRule "msg:invalid JSON" id:15;
#@MainRule "msg:empty POST" id:16;
#@MainRule "msg:libinjection_sql" id:17;
#@MainRule "msg:libinjection_xss" id:18;
##################################
## SQL Injections IDs:1000-1099 ##
##################################
MainRule "rx:select|union|update|delete|insert|table|from|ascii|hex|unhex|drop" "msg:sql keywords" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1000;
MainRule "str:\"" "msg:double quote" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8,$XSS:8" id:1001;
MainRule "str:0x" "msg:0x, possible hex encoding" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:2" id:1002;
## Hardcore rules
MainRule "str:/*" "msg:mysql comment (/*)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1003;
MainRule "str:*/" "msg:mysql comment (*/)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1004;
MainRule "str:|" "msg:mysql keyword (|)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1005;
MainRule "str:&&" "msg:mysql keyword (&&)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1006;
## end of hardcore rules
MainRule "str:--" "msg:mysql comment (--)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1007;
MainRule "str:;" "msg:semicolon" "mz:BODY|URL|ARGS" "s:$SQL:4,$XSS:8" id:1008;
MainRule "str:=" "msg:equal sign in var, probable sql/xss" "mz:ARGS|BODY" "s:$SQL:2" id:1009;
MainRule "str:(" "msg:open parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1010;
MainRule "str:)" "msg:close parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1011;
MainRule "str:'" "msg:simple quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1013;
MainRule "str:," "msg:comma" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1015;
MainRule "str:#" "msg:mysql comment (#)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1016;
MainRule "str:@@" "msg:double arobase (@@)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1017;
###############################
## OBVIOUS RFI IDs:1100-1199 ##
###############################
MainRule "str:http://" "msg:http:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1100;
MainRule "str:https://" "msg:https:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1101;
MainRule "str:ftp://" "msg:ftp:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1102;
MainRule "str:php://" "msg:php:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1103;
MainRule "str:sftp://" "msg:sftp:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1104;
MainRule "str:zlib://" "msg:zlib:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1105;
MainRule "str:data://" "msg:data:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1106;
MainRule "str:glob://" "msg:glob:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1107;
MainRule "str:phar://" "msg:phar:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1108;
MainRule "str:file://" "msg:file:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1109;
MainRule "str:gopher://" "msg:gopher:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1110;
#######################################
## Directory traversal IDs:1200-1299 ##
#######################################
MainRule "str:.." "msg:double dot" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1200;
MainRule "str:/etc/passwd" "msg:obvious probe" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1202;
MainRule "str:c:\\" "msg:obvious windows path" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1203;
MainRule "str:cmd.exe" "msg:obvious probe" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1204;
MainRule "str:\\" "msg:backslash" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1205;
#MainRule "str:/" "msg:slash in args" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:2" id:1206;
########################################
## Cross Site Scripting IDs:1300-1399 ##
########################################
MainRule "str:<" "msg:html open tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1302;
MainRule "str:>" "msg:html close tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1303;
MainRule "str:[" "msg:open square backet ([), possible js" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1310;
MainRule "str:]" "msg:close square bracket (]), possible js" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1311;
MainRule "str:~" "msg:tilde (~) character" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1312;
MainRule "str:`" "msg:grave accent (`)" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1314;
MainRule "rx:%[2|3]." "msg:double encoding" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1315;
####################################
## Evading tricks IDs: 1400-1500 ##
####################################
MainRule "str:&#" "msg:utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400;
MainRule "str:%U" "msg:M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401;
#############################
## File uploads: 1500-1600 ##
#############################
MainRule "rx:\.ph|\.asp|\.ht" "msg:asp/php file upload" "mz:FILE_EXT" "s:$UPLOAD:8" id:1500;

347
naxsi-0.55.3/naxsi_json.c Normal file
View File

@@ -0,0 +1,347 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "naxsi.h"
//#define _debug_json 1
ngx_http_rule_t nx_int__invalid_json = {/*type*/ 0, /*whitelist flag*/ 0,
/*wl_id ptr*/ NULL, /*rule_id*/ 15,
/*log_msg*/ NULL, /*score*/ 0,
/*sscores*/ NULL,
/*sc_block*/ 1, /*sc_allow*/ 0,
/*block*/ 1, /*allow*/ 0, /*drop*/ 0, /*log*/ 0,
/*br ptrs*/ NULL};
ngx_int_t
ngx_http_nx_json_forward(ngx_json_t *js)
{
while ((*(js->src+js->off) == ' ' ||
*(js->src+js->off) == '\t' ||
*(js->src+js->off) == '\n' ||
*(js->src+js->off) == '\r') && js->off < js->len) {
js->off++;
}
js->c = *(js->src + js->off);
return (NGX_OK);
}
/*
** used to fast forward in json POSTS,
** we skip whitespaces/tab/CR/LF
*/
ngx_int_t
ngx_http_nx_json_seek(ngx_json_t *js, unsigned char seek)
{
ngx_http_nx_json_forward(js);
if (js->c != seek)
return (NGX_ERROR);
return (NGX_OK);
}
/*
** extract a quoted strings,
** JSON spec only supports double-quoted strings,
** so do we.
*/
ngx_int_t
ngx_http_nx_json_quoted(ngx_json_t *js, ngx_str_t *ve)
{
u_char *vn_start, *vn_end;
vn_start = vn_end = NULL;
if (*(js->src+js->off) != '"')
return (NGX_ERROR);
js->off++;
vn_start = js->src+js->off;
/* extract varname inbetween "..."*/
while (js->off < js->len) {
/* skip next character if backslashed */
if (*(js->src+js->off) == '\\') {
js->off += 2;
if (js->off >= js->len) break;
}
if (*(js->src+js->off) == '"') {
vn_end = js->src+js->off;
js->off++;
break;
}
js->off++;
}
if (!vn_start || !vn_end)
return (NGX_ERROR);
if (!*vn_start || !*vn_end)
return (NGX_ERROR);
ve->data = vn_start;
ve->len = vn_end - vn_start;
return (NGX_OK);
}
/*
** an array is values separated by ','
*/
ngx_int_t
ngx_http_nx_json_array(ngx_json_t *js) {
ngx_int_t rc;
js->c = *(js->src + js->off);
if (js->c != '[' || js->depth > JSON_MAX_DEPTH)
return (NGX_ERROR);
js->off++;
do {
rc = ngx_http_nx_json_val(js);
/* if we cannot extract the value,
we may have reached array end. */
if (rc != NGX_OK)
break;
ngx_http_nx_json_forward(js);
if (js->c == ',') {
js->off++;
ngx_http_nx_json_forward(js);
} else break;
} while (rc == NGX_OK);
if (js->c != ']')
return (NGX_ERROR);
return (NGX_OK);
}
ngx_int_t
ngx_http_nx_json_val(ngx_json_t *js) {
ngx_str_t val;
ngx_int_t ret;
ngx_str_t empty = ngx_string("");
val.data = NULL;
val.len = 0;
ngx_http_nx_json_forward(js);
if (js->c == '"') {
ret = ngx_http_nx_json_quoted(js, &val);
if (ret == NGX_OK)
{
/* parse extracted values. */
if (js->loc_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->loc_cf->body_rules, js->r, js->ctx,
BODY);
if (js->main_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->main_cf->body_rules, js->r, js->ctx,
BODY);
NX_DEBUG(_debug_json, NGX_LOG_DEBUG_HTTP, js->r->connection->log, 0, "JSON '%V' : '%V'",
&(js->ckey), &(val));
}
return (ret);
}
if ((js->c >= '0' && js->c <= '9') || js->c == '-') {
val.data = js->src+js->off;
while ( ((*(js->src+js->off) >= '0' && *(js->src+js->off) <= '9') ||
*(js->src+js->off) == '.' || *(js->src+js->off) == '-') && js->off < js->len) {
val.len++;
js->off++;
}
/* parse extracted values. */
if (js->loc_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->loc_cf->body_rules, js->r, js->ctx,
BODY);
if (js->main_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->main_cf->body_rules, js->r, js->ctx,
BODY);
NX_DEBUG(_debug_json, NGX_LOG_DEBUG_HTTP, js->r->connection->log, 0, "JSON '%V' : '%V'",
&(js->ckey), &(val));
return (NGX_OK);
}
if (!strncasecmp((const char *) (js->src + js->off), (const char *) "true", 4) ||
!strncasecmp((const char *) (js->src + js->off), (const char *) "false", 5) ||
!strncasecmp((const char *) (js->src + js->off), (const char *) "null", 4)) {
js->c = *(js->src + js->off);
/* we don't check static values, do we ?! */
val.data = js->src+js->off;
if (js->c == 'F' || js->c == 'f') {
js->off += 5;
val.len = 5;
}
else {
js->off += 4;
val.len = 4;
}
/* parse extracted values. */
if (js->loc_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->loc_cf->body_rules, js->r, js->ctx,
BODY);
if (js->main_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->main_cf->body_rules, js->r, js->ctx,
BODY);
NX_DEBUG(_debug_json, NGX_LOG_DEBUG_HTTP, js->r->connection->log, 0, "JSON '%V' : '%V'",
&(js->ckey), &(val));
return (NGX_OK);
}
if (js->c == '[') {
ret = ngx_http_nx_json_array(js);
if (js->c != ']')
return (NGX_ERROR);
js->off++;
return (ret);
}
if (js->c == '{') {
/*
** if sub-struct, parse key without value :
** "foobar" : { "bar" : [1,2,3]} => "foobar" parsed alone.
** this is to avoid "foobar" left unparsed, as we won't have
** key/value here with "foobar" as a key.
*/
if (js->loc_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &empty,
js->loc_cf->body_rules, js->r, js->ctx,
BODY);
if (js->main_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &empty,
js->main_cf->body_rules, js->r, js->ctx,
BODY);
ret = ngx_http_nx_json_obj(js);
ngx_http_nx_json_forward(js);
if (js->c != '}')
return (NGX_ERROR);
js->off++;
return (ret);
}
return (NGX_ERROR);
}
ngx_int_t
ngx_http_nx_json_obj(ngx_json_t *js)
{
js->c = *(js->src + js->off);
if (js->c != '{' || js->depth > JSON_MAX_DEPTH)
return (NGX_ERROR);
js->off++;
do {
ngx_http_nx_json_forward(js);
/* check subs (arrays, objects) */
switch (js->c) {
case '[': /* array */
js->depth++;
ngx_http_nx_json_array(js);
if (ngx_http_nx_json_seek(js, ']'))
return (NGX_ERROR);
js->off++;
js->depth--;
break;
case '{': /* sub-object */
js->depth++;
ngx_http_nx_json_obj(js);
if (js->c != '}')
return (NGX_ERROR);
js->off++;
js->depth--;
break;
case '"': /* key : value, extract and parse. */
if (ngx_http_nx_json_quoted(js, &(js->ckey)) != NGX_OK)
return (NGX_ERROR);
if (ngx_http_nx_json_seek(js, ':'))
return (NGX_ERROR);
js->off++;
ngx_http_nx_json_forward(js);
if (ngx_http_nx_json_val(js) != NGX_OK)
return (NGX_ERROR);
}
ngx_http_nx_json_forward(js);
/* another element ? */
if (js->c == ',') {
js->off++;
ngx_http_nx_json_forward(js);
continue;
} else if (js->c == '}') {
js->depth--;
/* or maybe we just finished parsing this object */
return (NGX_OK);
} else {
/* nothing we expected, die. */
return (NGX_ERROR);
}
} while (js->off < js->len);
return (NGX_ERROR);
}
/*
** Parse a JSON request
*/
void
ngx_http_dummy_json_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r,
u_char *src,
u_int len)
{
ngx_json_t *js;
js = ngx_pcalloc(r->pool, sizeof(ngx_json_t));
if (!js) return ;
js->json.data = js->src = src;
js->json.len = js->len = len;
js->r = r;
js->ctx = ctx;
js->loc_cf = ngx_http_get_module_loc_conf(r, ngx_http_naxsi_module);
js->main_cf = ngx_http_get_module_main_conf(r, ngx_http_naxsi_module);
if (ngx_http_nx_json_seek(js, '{')) {
ngx_http_apply_rulematch_v_n(&nx_int__invalid_json, ctx, r, NULL, NULL, BODY, 1, 0);
return ;
}
if (ngx_http_nx_json_obj(js) != NGX_OK) {
ngx_http_apply_rulematch_v_n(&nx_int__invalid_json, ctx, r, NULL, NULL, BODY, 1, 0);
NX_DEBUG(_debug_json, NGX_LOG_DEBUG_HTTP, js->r->connection->log, 0, "nx_json_obj returned error, apply invalid_json.");
}
/* we are now on closing bracket, check for garbage. */
js->off++;
ngx_http_nx_json_forward(js);
if (js->off != js->len)
ngx_http_apply_rulematch_v_n(&nx_int__invalid_json, ctx, r, NULL, NULL, BODY, 1, 0);
return ;
}

70
naxsi-0.55.3/naxsi_raw.c Normal file
View File

@@ -0,0 +1,70 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "naxsi.h"
void
ngx_http_dummy_rawbody_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r,
u_char *src,
u_int len)
{
ngx_http_dummy_loc_conf_t *cf;
ngx_str_t body;
ngx_http_dummy_main_conf_t *main_cf;
ngx_str_t empty = ngx_string("");
NX_DEBUG(_debug_rawbody, NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "XX-RAWBODY CALLED len:%d",len);
if (len <= 0 || !src)
return;
cf = ngx_http_get_module_loc_conf(r, ngx_http_naxsi_module);
main_cf = ngx_http_get_module_main_conf(r, ngx_http_naxsi_module);
body.data = src;
body.len = len;
naxsi_unescape(&body);
/* here we got val name + val content !*/
if (cf->raw_body_rules) {
NX_DEBUG(_debug_rawbody, NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "XX-(local) RAW BODY RULES");
ngx_http_basestr_ruleset_n(r->pool, &empty, &body,
cf->raw_body_rules, r, ctx, BODY);
}
if (main_cf->raw_body_rules) {
NX_DEBUG(_debug_rawbody, NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "XX-(global) RAW BODY RULES");
ngx_http_basestr_ruleset_n(r->pool, &empty, &body,
main_cf->raw_body_rules, r, ctx, BODY);
}
}

2273
naxsi-0.55.3/naxsi_runtime.c Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
CORE_VERS := $(shell grep NAXSI_VERSION naxsi.h | cut -d '"' -f 2)
MOD_PATH := $(shell pwd)
TMP_DIR := /tmp/nginx/
# Keys for coverity
CAN :=
CAK :=
#Set to 1 if you want coverage report
COV ?= 1
#Allows to force for specific UT only
#TEST := "29*.t"
NGINX_VERS := "1.9.11"
NGINX_OPTIONS="--error-log-path=/tmp/naxsi_ut/error.log"
NGINX_OPTIONS+="--conf-path=/tmp/naxsi_ut/nginx.conf"
NGINX_OPTIONS+="--http-client-body-temp-path=/tmp/naxsi_ut/body/"
NGINX_OPTIONS+="--http-fastcgi-temp-path=/tmp/naxsi_ut/fastcgi/"
NGINX_OPTIONS+="--http-log-path=/tmp/naxsi_ut/access.log"
NGINX_OPTIONS+="--http-proxy-temp-path=/tmp/naxsi_ut/proxy/"
NGINX_OPTIONS+="--lock-path=/tmpnginx.lock"
NGINX_OPTIONS+="--pid-path=/tmp/naxsi_ut/nginx.pid"
NGINX_OPTIONS+="--modules-path=/tmp/naxsi_ut/modules/"
NGINX_OPTIONS+="--with-http_ssl_module"
NGINX_OPTIONS+="--without-mail_pop3_module"
NGINX_OPTIONS+="--without-mail_smtp_module"
NGINX_OPTIONS+="--without-mail_imap_module"
NGINX_OPTIONS+="--without-http_uwsgi_module"
NGINX_OPTIONS+="--without-http_scgi_module"
NGINX_OPTIONS+="--add-dynamic-module=$(MOD_PATH)"
NGINX_OPTIONS+="--with-ipv6"
NGINX_OPTIONS+="--prefix=/tmp"
NGINX_OPTIONS+="--with-debug"
CFLAGS:="-Wall -Wextra"
all: nginx_download configure build install deploy
re: clean all test
clean:
rm -f "nginx-"$(NGINX_VERS)".tar.gz"
rm -f "nginx-"$(NGINX_VERS)".tar.gz.asc"
rm -rf /tmp/naxsi_ut/
rm -rf $(TMP_DIR)/
nginx_download:
wget --no-clobber "http://nginx.org/download/nginx-"$(NGINX_VERS)".tar.gz" || exit 1
wget --no-clobber "http://nginx.org/download/nginx-"$(NGINX_VERS)".tar.gz.asc" || exit 1
gpg --keyserver pgp.key-server.io --recv-keys 0x251a28de2685aed4 0x520A9993A1C052F8
gpg --verify "nginx-"$(NGINX_VERS)".tar.gz.asc" "nginx-"$(NGINX_VERS)".tar.gz" || exit 1
mkdir -p $(TMP_DIR)/
tar -C $(TMP_DIR)/ -xzf nginx-$(NGINX_VERS).tar.gz --strip-components=1
configure:
ifeq ($(COV),1)
cd $(TMP_DIR)/ && ./configure --with-cc-opt="--coverage -g3 -gstabs" --with-ld-opt="-lgcov" $(NGINX_OPTIONS)
else
cd $(TMP_DIR)/ && ./configure --with-cc-opt="-g3 -ggdb" $(NGINX_OPTIONS)
endif
build:
cd $(TMP_DIR)/ && make
if [ -d "/tmp/naxsi_ut" ] ; then cp $(TMP_DIR)/objs/ngx_http_naxsi_module.so /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so ; fi
install:
cd $(TMP_DIR)/ && make install
deploy:
@cp ./nginx.conf.example /tmp/naxsi_ut/nginx.conf
@cp ../naxsi_config/naxsi_core.rules /tmp/naxsi_ut/naxsi_core.rules
# RUN UNIT TESTS
test:
ifeq ($(COV),1)
lcov --directory $(TMP_DIR) --zerocounters
endif
if [ ! $(TEST) ] ; then TEST="*.t" ; fi
export PATH="$(TMP_DIR)/objs/:"$(PATH) ; \
export PERL5LIB="~/perl5/lib/perl5/:/home/travis/perl5/lib/perl5/" ; \
cd .. ; prove -r "t/$(TEST)"
ifeq ($(COV),1)
lcov --directory $(TMP_DIR)/objs/addon/naxsi_src/ --capture --output-file naxsi.info --base-directory $(TMP_DIR)
genhtml -s -o /tmp/naxsicov.html naxsi.info
endif
#Build for coverity and submit build !
coverity:
@CAK=$(shell cat ../../coverity.key | cut -d ':' -f2) ; \
CAN=$(shell cat ../../coverity.key | cut -d ':' -f1) ; \
echo "Coverity token/login : $$CAK and $$CAN"; \
wget -nc https://scan.coverity.com/download/linux-64 --post-data "token=$$CAK&project=nbs-system%2Fnaxsi" -O /tmp/coverity.tgz ; \
mkdir /tmp/cov && cd /tmp/cov && cat ../coverity.tgz | tar --strip-components=1 -xvzf - ; \
cd $(TMP_DIR) ; \
./configure $(NGINX_OPTIONS) && \
/tmp/cov/bin/cov-build --dir cov-int make && \
tar cvzf coverity-res-naxsi.tgz cov-int/ ; \
curl --form token="$$CAK" \
--form email="$$CAN" \
--form file=@$(TMP_DIR)/coverity-res-naxsi.tgz \
--form version="$(CORE_VERS)" \
--form description="Automatically submitted" \
https://scan.coverity.com/builds?project=nbs-system%2Fnaxsi

View File

@@ -0,0 +1,12 @@
ngx_addon_name=ngx_http_naxsi_module
if test -n "$ngx_module_link"; then
ngx_module_type=HTTP
ngx_module_name=ngx_http_naxsi_module
ngx_module_srcs="$ngx_addon_dir/naxsi_runtime.c $ngx_addon_dir/naxsi_config.c $ngx_addon_dir/naxsi_utils.c $ngx_addon_dir/naxsi_skeleton.c $ngx_addon_dir/naxsi_json.c $ngx_addon_dir/naxsi_raw.c $ngx_addon_dir/ext/libinjection/libinjection_sqli.c $ngx_addon_dir/ext/libinjection/libinjection_xss.c $ngx_addon_dir/ext/libinjection/libinjection_html5.c"
. auto/module
else
HTTP_MODULES="$HTTP_MODULES ngx_http_naxsi_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/naxsi_runtime.c $ngx_addon_dir/naxsi_config.c $ngx_addon_dir/naxsi_utils.c $ngx_addon_dir/naxsi_skeleton.c $ngx_addon_dir/naxsi_json.c $ngx_addon_dir/naxsi_raw.c $ngx_addon_dir/ext/libinjection/libinjection_sqli.c $ngx_addon_dir/ext/libinjection/libinjection_xss.c $ngx_addon_dir/ext/libinjection/libinjection_html5.c"
NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/naxsi.h"
fi

View File

@@ -0,0 +1,30 @@
#include <stdio.h>
#include <string.h>
#include "libinjection.h"
int main(int argc, const char* argv[])
{
char fingerprint[8];
const char* input;
size_t slen;
int issqli;
if (argc < 2) {
fprintf(stderr, "Usage: %s inputstring\n", argv[0]);
return -1;
}
input = argv[1];
slen = strlen(input);
issqli = libinjection_sqli(input, slen, fingerprint);
if (issqli) {
printf("sqli with fingerprint of '%s'\n", fingerprint);
} else {
printf("not sqli\n");
}
return issqli;
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright 2012, 2013 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* This is for testing against files in ../data/ *.txt
* Reads from stdin or a list of files, and emits if a line
* is a SQLi attack or not, and does basic statistics
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
int main(int argc, const char* argv[])
{
size_t slen;
int ok;
int single = 0;
int offset = 1;
sfilter sf;
if (argc < 2) {
fprintf(stderr, "need more args\n");
return 1;
}
while (1) {
if (strcmp(argv[offset], "-0") == 0) {
single = 1;
offset += 1;
} else {
break;
}
}
slen = strlen(argv[offset]);
if (slen == 0) {
return 1;
}
/*
* "plain" context.. test string "as-is"
*/
libinjection_sqli_init(&sf, argv[offset], slen, 0);
if (single) {
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_NONE | FLAG_SQL_ANSI);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "%s\n", sf.fingerprint);
return 0;
}
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_NONE | FLAG_SQL_ANSI);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "plain-asni\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_NONE | FLAG_SQL_MYSQL);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "plain-mysql\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_SINGLE | FLAG_SQL_ANSI);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "single-ansi\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_SINGLE | FLAG_SQL_MYSQL);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "single-mysql\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_DOUBLE | FLAG_SQL_MYSQL);
ok = libinjection_sqli_check_fingerprint(&sf);
fprintf(stdout, "double-mysql\t%s\t%s\n", sf.fingerprint, ok ? "true": "false");
return 0;
}

View File

@@ -0,0 +1,168 @@
/**
* Copyright 2012, 2013, 2014 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* This is for testing against files in ../data/ *.txt
* Reads from stdin or a list of files, and emits if a line
* is a SQLi attack or not, and does basic statistics
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "libinjection_html5.h"
#include "libinjection_xss.h"
#include "libinjection.h"
int urlcharmap(char ch);
size_t modp_url_decode(char* dest, const char* s, size_t len);
const char* h5_type_to_string(enum html5_type x);
void print_html5_token(h5_state_t* hs);
int urlcharmap(char ch) {
switch (ch) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
default:
return 256;
}
}
size_t modp_url_decode(char* dest, const char* s, size_t len)
{
const char* deststart = dest;
size_t i = 0;
int d = 0;
while (i < len) {
switch (s[i]) {
case '+':
*dest++ = ' ';
i += 1;
break;
case '%':
if (i+2 < len) {
d = (urlcharmap(s[i+1]) << 4) | urlcharmap(s[i+2]);
if ( d < 256) {
*dest = (char) d;
dest++;
i += 3; /* loop will increment one time */
} else {
*dest++ = '%';
i += 1;
}
} else {
*dest++ = '%';
i += 1;
}
break;
default:
*dest++ = s[i];
i += 1;
}
}
*dest = '\0';
return (size_t)(dest - deststart); /* compute "strlen" of dest */
}
const char* h5_type_to_string(enum html5_type x)
{
switch (x) {
case DATA_TEXT: return "DATA_TEXT";
case TAG_NAME_OPEN: return "TAG_NAME_OPEN";
case TAG_NAME_CLOSE: return "TAG_NAME_CLOSE";
case TAG_NAME_SELFCLOSE: return "TAG_NAME_SELFCLOSE";
case TAG_DATA: return "TAG_DATA";
case TAG_CLOSE: return "TAG_CLOSE";
case ATTR_NAME: return "ATTR_NAME";
case ATTR_VALUE: return "ATTR_VALUE";
case TAG_COMMENT: return "TAG_COMMENT";
case DOCTYPE: return "DOCTYPE";
default:
assert(0);
}
}
void print_html5_token(h5_state_t* hs)
{
char* tmp = (char*) malloc(hs->token_len + 1);
memcpy(tmp, hs->token_start, hs->token_len);
/* TODO.. encode to be printable */
tmp[hs->token_len] = '\0';
printf("%s,%d,%s\n",
h5_type_to_string(hs->token_type),
(int) hs->token_len,
tmp);
free(tmp);
}
int main(int argc, const char* argv[])
{
size_t slen;
h5_state_t hs;
char* copy;
int offset = 1;
int flag = 0;
int urldecode = 0;
if (argc < 2) {
fprintf(stderr, "need more args\n");
return 1;
}
while (offset < argc) {
if (strcmp(argv[offset], "-u") == 0) {
offset += 1;
urldecode = 1;
} else if (strcmp(argv[offset], "-f") == 0) {
offset += 1;
flag = atoi(argv[offset]);
offset += 1;
} else {
break;
}
}
/* ATTENTION: argv is a C-string, null terminated. We copy this
* to it's own location, WITHOUT null byte. This way, valgrind
* can see if we run past the buffer.
*/
slen = strlen(argv[offset]);
copy = (char* ) malloc(slen);
memcpy(copy, argv[offset], slen);
if (urldecode) {
slen = modp_url_decode(copy, copy, slen);
}
libinjection_h5_init(&hs, copy, slen, (enum html5_flags) flag);
while (libinjection_h5_next(&hs)) {
print_html5_token(&hs);
}
if (libinjection_is_xss(copy, slen, flag)) {
printf("is injection!\n");
}
free(copy);
return 0;
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright 2012, 2013 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* https://libinjection.client9.com/
*
*/
#ifndef _LIBINJECTION_H
#define _LIBINJECTION_H
#ifdef __cplusplus
# define LIBINJECTION_BEGIN_DECLS extern "C" {
# define LIBINJECTION_END_DECLS }
#else
# define LIBINJECTION_BEGIN_DECLS
# define LIBINJECTION_END_DECLS
#endif
LIBINJECTION_BEGIN_DECLS
/*
* Pull in size_t
*/
#include <string.h>
/*
* Version info.
*
* This is moved into a function to allow SWIG and other auto-generated
* binding to not be modified during minor release changes. We change
* change the version number in the c source file, and not regenerated
* the binding
*
* See python's normalized version
* http://www.python.org/dev/peps/pep-0386/#normalizedversion
*/
const char* libinjection_version(void);
/**
* Simple API for SQLi detection - returns a SQLi fingerprint or NULL
* is benign input
*
* \param[in] s input string, may contain nulls, does not need to be null-terminated
* \param[in] slen input string length
* \param[out] fingerprint buffer of 8+ characters. c-string,
* \return 1 if SQLi, 0 if benign. fingerprint will be set or set to empty string.
*/
int libinjection_sqli(const char* s, size_t slen, char fingerprint[]);
/** ALPHA version of xss detector.
*
* NOT DONE.
*
* \param[in] s input string, may contain nulls, does not need to be null-terminated
* \param[in] slen input string length
* \return 1 if XSS found, 0 if benign
*
*/
int libinjection_xss(const char* s, size_t slen);
LIBINJECTION_END_DECLS
#endif /* _LIBINJECTION_H */

View File

@@ -0,0 +1,847 @@
#include "libinjection_html5.h"
#include <string.h>
#include <assert.h>
#ifdef DEBUG
#include <stdio.h>
#define TRACE() printf("%s:%d\n", __FUNCTION__, __LINE__)
#else
#define TRACE()
#endif
#define CHAR_EOF -1
#define CHAR_NULL 0
#define CHAR_BANG 33
#define CHAR_DOUBLE 34
#define CHAR_PERCENT 37
#define CHAR_SINGLE 39
#define CHAR_DASH 45
#define CHAR_SLASH 47
#define CHAR_LT 60
#define CHAR_EQUALS 61
#define CHAR_GT 62
#define CHAR_QUESTION 63
#define CHAR_RIGHTB 93
#define CHAR_TICK 96
/* prototypes */
static int h5_skip_white(h5_state_t* hs);
static int h5_is_white(char c);
static int h5_state_eof(h5_state_t* hs);
static int h5_state_data(h5_state_t* hs);
static int h5_state_tag_open(h5_state_t* hs);
static int h5_state_tag_name(h5_state_t* hs);
static int h5_state_tag_name_close(h5_state_t* hs);
static int h5_state_end_tag_open(h5_state_t* hs);
static int h5_state_self_closing_start_tag(h5_state_t* hs);
static int h5_state_attribute_name(h5_state_t* hs);
static int h5_state_after_attribute_name(h5_state_t* hs);
static int h5_state_before_attribute_name(h5_state_t* hs);
static int h5_state_before_attribute_value(h5_state_t* hs);
static int h5_state_attribute_value_double_quote(h5_state_t* hs);
static int h5_state_attribute_value_single_quote(h5_state_t* hs);
static int h5_state_attribute_value_back_quote(h5_state_t* hs);
static int h5_state_attribute_value_no_quote(h5_state_t* hs);
static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs);
static int h5_state_comment(h5_state_t* hs);
static int h5_state_cdata(h5_state_t* hs);
/* 12.2.4.44 */
static int h5_state_bogus_comment(h5_state_t* hs);
static int h5_state_bogus_comment2(h5_state_t* hs);
/* 12.2.4.45 */
static int h5_state_markup_declaration_open(h5_state_t* hs);
/* 8.2.4.52 */
static int h5_state_doctype(h5_state_t* hs);
/**
* public function
*/
void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags flags)
{
memset(hs, 0, sizeof(h5_state_t));
hs->s = s;
hs->len = len;
switch (flags) {
case DATA_STATE:
hs->state = h5_state_data;
break;
case VALUE_NO_QUOTE:
hs->state = h5_state_before_attribute_name;
break;
case VALUE_SINGLE_QUOTE:
hs->state = h5_state_attribute_value_single_quote;
break;
case VALUE_DOUBLE_QUOTE:
hs->state = h5_state_attribute_value_double_quote;
break;
case VALUE_BACK_QUOTE:
hs->state = h5_state_attribute_value_back_quote;
break;
}
}
/**
* public function
*/
int libinjection_h5_next(h5_state_t* hs)
{
assert(hs->state != NULL);
return (*hs->state)(hs);
}
/**
* Everything below here is private
*
*/
static int h5_is_white(char ch)
{
/*
* \t = htab = 0x09
* \n = newline = 0x0A
* \v = vtab = 0x0B
* \f = form feed = 0x0C
* \r = cr = 0x0D
*/
return strchr(" \t\n\v\f\r", ch) != NULL;
}
static int h5_skip_white(h5_state_t* hs)
{
char ch;
while (hs->pos < hs->len) {
ch = hs->s[hs->pos];
switch (ch) {
case 0x00: /* IE only */
case 0x20:
case 0x09:
case 0x0A:
case 0x0B: /* IE only */
case 0x0C:
case 0x0D: /* IE only */
hs->pos += 1;
break;
default:
return ch;
}
}
return CHAR_EOF;
}
static int h5_state_eof(h5_state_t* hs)
{
/* eliminate unused function argument warning */
(void)hs;
return 0;
}
static int h5_state_data(h5_state_t* hs)
{
const char* idx;
TRACE();
assert(hs->len >= hs->pos);
idx = (const char*) memchr(hs->s + hs->pos, CHAR_LT, hs->len - hs->pos);
if (idx == NULL) {
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = DATA_TEXT;
hs->state = h5_state_eof;
if (hs->token_len == 0) {
return 0;
}
} else {
hs->token_start = hs->s + hs->pos;
hs->token_type = DATA_TEXT;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 1;
hs->state = h5_state_tag_open;
if (hs->token_len == 0) {
return h5_state_tag_open(hs);
}
}
return 1;
}
/**
* 12 2.4.8
*/
static int h5_state_tag_open(h5_state_t* hs)
{
char ch;
TRACE();
ch = hs->s[hs->pos];
if (ch == CHAR_BANG) {
hs->pos += 1;
return h5_state_markup_declaration_open(hs);
} else if (ch == CHAR_SLASH) {
hs->pos += 1;
hs->is_close = 1;
return h5_state_end_tag_open(hs);
} else if (ch == CHAR_QUESTION) {
hs->pos += 1;
return h5_state_bogus_comment(hs);
} else if (ch == CHAR_PERCENT) {
/* this is not in spec.. alternative comment format used
by IE <= 9 and Safari < 4.0.3 */
hs->pos += 1;
return h5_state_bogus_comment2(hs);
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
return h5_state_tag_name(hs);
} else if (ch == CHAR_NULL) {
/* IE-ism NULL characters are ignored */
return h5_state_tag_name(hs);
} else {
/* user input mistake in configuring state */
if (hs->pos == 0) {
return h5_state_data(hs);
}
hs->token_start = hs->s + hs->pos - 1;
hs->token_len = 1;
hs->token_type = DATA_TEXT;
hs->state = h5_state_data;
return 1;
}
}
/**
* 12.2.4.9
*/
static int h5_state_end_tag_open(h5_state_t* hs)
{
char ch;
TRACE();
if (hs->pos >= hs->len) {
return 0;
}
ch = hs->s[hs->pos];
if (ch == CHAR_GT) {
return h5_state_data(hs);
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
return h5_state_tag_name(hs);
}
hs->is_close = 0;
return h5_state_bogus_comment(hs);
}
/*
*
*/
static int h5_state_tag_name_close(h5_state_t* hs)
{
TRACE();
hs->is_close = 0;
hs->token_start = hs->s + hs->pos;
hs->token_len = 1;
hs->token_type = TAG_NAME_CLOSE;
hs->pos += 1;
if (hs->pos < hs->len) {
hs->state = h5_state_data;
} else {
hs->state = h5_state_eof;
}
return 1;
}
/**
* 12.2.4.10
*/
static int h5_state_tag_name(h5_state_t* hs)
{
char ch;
size_t pos;
TRACE();
pos = hs->pos;
while (pos < hs->len) {
ch = hs->s[pos];
if (ch == 0) {
/* special non-standard case */
/* allow nulls in tag name */
/* some old browsers apparently allow and ignore them */
pos += 1;
} else if (h5_is_white(ch)) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = TAG_NAME_OPEN;
hs->pos = pos + 1;
hs->state = h5_state_before_attribute_name;
return 1;
} else if (ch == CHAR_SLASH) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = TAG_NAME_OPEN;
hs->pos = pos + 1;
hs->state = h5_state_self_closing_start_tag;
return 1;
} else if (ch == CHAR_GT) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
if (hs->is_close) {
hs->pos = pos + 1;
hs->is_close = 0;
hs->token_type = TAG_CLOSE;
hs->state = h5_state_data;
} else {
hs->pos = pos;
hs->token_type = TAG_NAME_OPEN;
hs->state = h5_state_tag_name_close;
}
return 1;
} else {
pos += 1;
}
}
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_NAME_OPEN;
hs->state = h5_state_eof;
return 1;
}
/**
* 12.2.4.34
*/
static int h5_state_before_attribute_name(h5_state_t* hs)
{
int ch;
TRACE();
ch = h5_skip_white(hs);
switch (ch) {
case CHAR_EOF: {
return 0;
}
case CHAR_SLASH: {
hs->pos += 1;
return h5_state_self_closing_start_tag(hs);
}
case CHAR_GT: {
hs->state = h5_state_data;
hs->token_start = hs->s + hs->pos;
hs->token_len = 1;
hs->token_type = TAG_NAME_CLOSE;
hs->pos += 1;
return 1;
}
default: {
return h5_state_attribute_name(hs);
}
}
}
static int h5_state_attribute_name(h5_state_t* hs)
{
char ch;
size_t pos;
TRACE();
pos = hs->pos + 1;
while (pos < hs->len) {
ch = hs->s[pos];
if (h5_is_white(ch)) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_after_attribute_name;
hs->pos = pos + 1;
return 1;
} else if (ch == CHAR_SLASH) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_self_closing_start_tag;
hs->pos = pos + 1;
return 1;
} else if (ch == CHAR_EQUALS) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_before_attribute_value;
hs->pos = pos + 1;
return 1;
} else if (ch == CHAR_GT) {
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_tag_name_close;
hs->pos = pos;
return 1;
} else {
pos += 1;
}
}
/* EOF */
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = ATTR_NAME;
hs->state = h5_state_eof;
hs->pos = hs->len;
return 1;
}
/**
* 12.2.4.36
*/
static int h5_state_after_attribute_name(h5_state_t* hs)
{
int c;
TRACE();
c = h5_skip_white(hs);
switch (c) {
case CHAR_EOF: {
return 0;
}
case CHAR_SLASH: {
hs->pos += 1;
return h5_state_self_closing_start_tag(hs);
}
case CHAR_EQUALS: {
hs->pos += 1;
return h5_state_before_attribute_value(hs);
}
case CHAR_GT: {
return h5_state_tag_name_close(hs);
}
default: {
return h5_state_attribute_name(hs);
}
}
}
/**
* 12.2.4.37
*/
static int h5_state_before_attribute_value(h5_state_t* hs)
{
int c;
TRACE();
c = h5_skip_white(hs);
if (c == CHAR_EOF) {
hs->state = h5_state_eof;
return 0;
}
if (c == CHAR_DOUBLE) {
return h5_state_attribute_value_double_quote(hs);
} else if (c == CHAR_SINGLE) {
return h5_state_attribute_value_single_quote(hs);
} else if (c == CHAR_TICK) {
/* NON STANDARD IE */
return h5_state_attribute_value_back_quote(hs);
} else {
return h5_state_attribute_value_no_quote(hs);
}
}
static int h5_state_attribute_value_quote(h5_state_t* hs, char qchar)
{
const char* idx;
TRACE();
/* skip initial quote in normal case.
* dont do this is pos == 0 since it means we have started
* in a non-data state. given an input of '><foo
* we want to make 0-length attribute name
*/
if (hs->pos > 0) {
hs->pos += 1;
}
idx = (const char*) memchr(hs->s + hs->pos, qchar, hs->len - hs->pos);
if (idx == NULL) {
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = ATTR_VALUE;
hs->state = h5_state_eof;
} else {
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->token_type = ATTR_VALUE;
hs->state = h5_state_after_attribute_value_quoted_state;
hs->pos += hs->token_len + 1;
}
return 1;
}
static
int h5_state_attribute_value_double_quote(h5_state_t* hs)
{
TRACE();
return h5_state_attribute_value_quote(hs, CHAR_DOUBLE);
}
static
int h5_state_attribute_value_single_quote(h5_state_t* hs)
{
TRACE();
return h5_state_attribute_value_quote(hs, CHAR_SINGLE);
}
static
int h5_state_attribute_value_back_quote(h5_state_t* hs)
{
TRACE();
return h5_state_attribute_value_quote(hs, CHAR_TICK);
}
static int h5_state_attribute_value_no_quote(h5_state_t* hs)
{
char ch;
size_t pos;
TRACE();
pos = hs->pos;
while (pos < hs->len) {
ch = hs->s[pos];
if (h5_is_white(ch)) {
hs->token_type = ATTR_VALUE;
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->pos = pos + 1;
hs->state = h5_state_before_attribute_name;
return 1;
} else if (ch == CHAR_GT) {
hs->token_type = ATTR_VALUE;
hs->token_start = hs->s + hs->pos;
hs->token_len = pos - hs->pos;
hs->pos = pos;
hs->state = h5_state_tag_name_close;
return 1;
}
pos += 1;
}
TRACE();
/* EOF */
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = ATTR_VALUE;
return 1;
}
/**
* 12.2.4.41
*/
static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs)
{
char ch;
TRACE();
if (hs->pos >= hs->len) {
return 0;
}
ch = hs->s[hs->pos];
if (h5_is_white(ch)) {
hs->pos += 1;
return h5_state_before_attribute_name(hs);
} else if (ch == CHAR_SLASH) {
hs->pos += 1;
return h5_state_self_closing_start_tag(hs);
} else if (ch == CHAR_GT) {
hs->token_start = hs->s + hs->pos;
hs->token_len = 1;
hs->token_type = TAG_NAME_CLOSE;
hs->pos += 1;
hs->state = h5_state_data;
return 1;
} else {
return h5_state_before_attribute_name(hs);
}
}
/**
* 12.2.4.43
*/
static int h5_state_self_closing_start_tag(h5_state_t* hs)
{
char ch;
TRACE();
if (hs->pos >= hs->len) {
return 0;
}
ch = hs->s[hs->pos];
if (ch == CHAR_GT) {
assert(hs->pos > 0);
hs->token_start = hs->s + hs->pos -1;
hs->token_len = 2;
hs->token_type = TAG_NAME_SELFCLOSE;
hs->state = h5_state_data;
hs->pos += 1;
return 1;
} else {
return h5_state_before_attribute_name(hs);
}
}
/**
* 12.2.4.44
*/
static int h5_state_bogus_comment(h5_state_t* hs)
{
const char* idx;
TRACE();
idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos);
if (idx == NULL) {
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->pos = hs->len;
hs->state = h5_state_eof;
} else {
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 1;
hs->state = h5_state_data;
}
hs->token_type = TAG_COMMENT;
return 1;
}
/**
* 12.2.4.44 ALT
*/
static int h5_state_bogus_comment2(h5_state_t* hs)
{
const char* idx;
size_t pos;
TRACE();
pos = hs->pos;
while (1) {
idx = (const char*) memchr(hs->s + pos, CHAR_PERCENT, hs->len - pos);
if (idx == NULL || (idx + 1 >= hs->s + hs->len)) {
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->pos = hs->len;
hs->token_type = TAG_COMMENT;
hs->state = h5_state_eof;
return 1;
}
if (*(idx +1) != CHAR_GT) {
pos = (size_t)(idx - hs->s) + 1;
continue;
}
/* ends in %> */
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 2;
hs->state = h5_state_data;
hs->token_type = TAG_COMMENT;
return 1;
}
}
/**
* 8.2.4.45
*/
static int h5_state_markup_declaration_open(h5_state_t* hs)
{
size_t remaining;
TRACE();
remaining = hs->len - hs->pos;
if (remaining >= 7 &&
/* case insensitive */
(hs->s[hs->pos + 0] == 'D' || hs->s[hs->pos + 0] == 'd') &&
(hs->s[hs->pos + 1] == 'O' || hs->s[hs->pos + 1] == 'o') &&
(hs->s[hs->pos + 2] == 'C' || hs->s[hs->pos + 2] == 'c') &&
(hs->s[hs->pos + 3] == 'T' || hs->s[hs->pos + 3] == 't') &&
(hs->s[hs->pos + 4] == 'Y' || hs->s[hs->pos + 4] == 'y') &&
(hs->s[hs->pos + 5] == 'P' || hs->s[hs->pos + 5] == 'p') &&
(hs->s[hs->pos + 6] == 'E' || hs->s[hs->pos + 6] == 'e')
) {
return h5_state_doctype(hs);
} else if (remaining >= 7 &&
/* upper case required */
hs->s[hs->pos + 0] == '[' &&
hs->s[hs->pos + 1] == 'C' &&
hs->s[hs->pos + 2] == 'D' &&
hs->s[hs->pos + 3] == 'A' &&
hs->s[hs->pos + 4] == 'T' &&
hs->s[hs->pos + 5] == 'A' &&
hs->s[hs->pos + 6] == '['
) {
hs->pos += 7;
return h5_state_cdata(hs);
} else if (remaining >= 2 &&
hs->s[hs->pos + 0] == '-' &&
hs->s[hs->pos + 1] == '-') {
hs->pos += 2;
return h5_state_comment(hs);
}
return h5_state_bogus_comment(hs);
}
/**
* 12.2.4.48
* 12.2.4.49
* 12.2.4.50
* 12.2.4.51
* state machine spec is confusing since it can only look
* at one character at a time but simply it's comments end by:
* 1) EOF
* 2) ending in -->
* 3) ending in -!>
*/
static int h5_state_comment(h5_state_t* hs)
{
char ch;
const char* idx;
size_t pos;
size_t offset;
const char* end = hs->s + hs->len;
TRACE();
pos = hs->pos;
while (1) {
idx = (const char*) memchr(hs->s + pos, CHAR_DASH, hs->len - pos);
/* did not find anything or has less than 3 chars left */
if (idx == NULL || idx > hs->s + hs->len - 3) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_COMMENT;
return 1;
}
offset = 1;
/* skip all nulls */
while (idx + offset < end && *(idx + offset) == 0) {
offset += 1;
}
if (idx + offset == end) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_COMMENT;
return 1;
}
ch = *(idx + offset);
if (ch != CHAR_DASH && ch != CHAR_BANG) {
pos = (size_t)(idx - hs->s) + 1;
continue;
}
/* need to test */
#if 0
/* skip all nulls */
while (idx + offset < end && *(idx + offset) == 0) {
offset += 1;
}
if (idx + offset == end) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_COMMENT;
return 1;
}
#endif
offset += 1;
if (idx + offset == end) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = TAG_COMMENT;
return 1;
}
ch = *(idx + offset);
if (ch != CHAR_GT) {
pos = (size_t)(idx - hs->s) + 1;
continue;
}
offset += 1;
/* ends in --> or -!> */
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx + offset - hs->s);
hs->state = h5_state_data;
hs->token_type = TAG_COMMENT;
return 1;
}
}
static int h5_state_cdata(h5_state_t* hs)
{
const char* idx;
size_t pos;
TRACE();
pos = hs->pos;
while (1) {
idx = (const char*) memchr(hs->s + pos, CHAR_RIGHTB, hs->len - pos);
/* did not find anything or has less than 3 chars left */
if (idx == NULL || idx > hs->s + hs->len - 3) {
hs->state = h5_state_eof;
hs->token_start = hs->s + hs->pos;
hs->token_len = hs->len - hs->pos;
hs->token_type = DATA_TEXT;
return 1;
} else if ( *(idx+1) == CHAR_RIGHTB && *(idx+2) == CHAR_GT) {
hs->state = h5_state_data;
hs->token_start = hs->s + hs->pos;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 3;
hs->token_type = DATA_TEXT;
return 1;
} else {
pos = (size_t)(idx - hs->s) + 1;
}
}
}
/**
* 8.2.4.52
* http://www.w3.org/html/wg/drafts/html/master/syntax.html#doctype-state
*/
static int h5_state_doctype(h5_state_t* hs)
{
const char* idx;
TRACE();
hs->token_start = hs->s + hs->pos;
hs->token_type = DOCTYPE;
idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos);
if (idx == NULL) {
hs->state = h5_state_eof;
hs->token_len = hs->len - hs->pos;
} else {
hs->state = h5_state_data;
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
hs->pos = (size_t)(idx - hs->s) + 1;
}
return 1;
}

View File

@@ -0,0 +1,54 @@
#ifndef LIBINJECTION_HTML5
#define LIBINJECTION_HTML5
#ifdef __cplusplus
extern "C" {
#endif
/* pull in size_t */
#include <stddef.h>
enum html5_type {
DATA_TEXT
, TAG_NAME_OPEN
, TAG_NAME_CLOSE
, TAG_NAME_SELFCLOSE
, TAG_DATA
, TAG_CLOSE
, ATTR_NAME
, ATTR_VALUE
, TAG_COMMENT
, DOCTYPE
};
enum html5_flags {
DATA_STATE
, VALUE_NO_QUOTE
, VALUE_SINGLE_QUOTE
, VALUE_DOUBLE_QUOTE
, VALUE_BACK_QUOTE
};
struct h5_state;
typedef int (*ptr_html5_state)(struct h5_state*);
typedef struct h5_state {
const char* s;
size_t len;
size_t pos;
int is_close;
ptr_html5_state state;
const char* token_start;
size_t token_len;
enum html5_type token_type;
} h5_state_t;
void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags);
int libinjection_h5_next(h5_state_t* hs);
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
/**
* Copyright 2012, 2013 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* https://libinjection.client9.com/
*
*/
#ifndef _LIBINJECTION_SQLI_H
#define _LIBINJECTION_SQLI_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Pull in size_t
*/
#include <string.h>
enum sqli_flags {
FLAG_NONE = 0
, FLAG_QUOTE_NONE = 1 /* 1 << 0 */
, FLAG_QUOTE_SINGLE = 2 /* 1 << 1 */
, FLAG_QUOTE_DOUBLE = 4 /* 1 << 2 */
, FLAG_SQL_ANSI = 8 /* 1 << 3 */
, FLAG_SQL_MYSQL = 16 /* 1 << 4 */
};
enum lookup_type {
LOOKUP_WORD = 1
, LOOKUP_TYPE = 2
, LOOKUP_OPERATOR = 3
, LOOKUP_FINGERPRINT = 4
};
struct libinjection_sqli_token {
#ifdef SWIG
%immutable;
#endif
char type;
char str_open;
char str_close;
/*
* position and length of token
* in original string
*/
size_t pos;
size_t len;
/* count:
* in type 'v', used for number of opening '@'
* but maybe unsed in other contexts
*/
int count;
char val[32];
};
typedef struct libinjection_sqli_token stoken_t;
/**
* Pointer to function, takes cstr input,
* returns '\0' for no match, else a char
*/
struct libinjection_sqli_state;
typedef char (*ptr_lookup_fn)(struct libinjection_sqli_state*, int lookuptype, const char* word, size_t len);
struct libinjection_sqli_state {
#ifdef SWIG
%immutable;
#endif
/*
* input, does not need to be null terminated.
* it is also not modified.
*/
const char *s;
/*
* input length
*/
size_t slen;
/*
* How to lookup a word or fingerprint
*/
ptr_lookup_fn lookup;
void* userdata;
/*
*
*/
int flags;
/*
* pos is index in string we are at when tokenizing
*/
size_t pos;
#ifndef SWIG
/* for SWIG.. don't use this.. use functional API instead */
/* MAX TOKENS + 1 since we use one extra token
* to determine the type of the previous token
*/
struct libinjection_sqli_token tokenvec[8];
#endif
/*
* Pointer to token position in tokenvec, above
*/
struct libinjection_sqli_token *current;
/*
* fingerprint pattern c-string
* +1 for ending null
* Mimimum of 8 bytes to add gcc's -fstack-protector to work
*/
char fingerprint[8];
/*
* Line number of code that said decided if the input was SQLi or
* not. Most of the time it's line that said "it's not a matching
* fingerprint" but there is other logic that sometimes approves
* an input. This is only useful for debugging.
*
*/
int reason;
/* Number of ddw (dash-dash-white) comments
* These comments are in the form of
* '--[whitespace]' or '--[EOF]'
*
* All databases treat this as a comment.
*/
int stats_comment_ddw;
/* Number of ddx (dash-dash-[notwhite]) comments
*
* ANSI SQL treats these are comments, MySQL treats this as
* two unary operators '-' '-'
*
* If you are parsing result returns FALSE and
* stats_comment_dd > 0, you should reparse with
* COMMENT_MYSQL
*
*/
int stats_comment_ddx;
/*
* c-style comments found /x .. x/
*/
int stats_comment_c;
/* '#' operators or mysql EOL comments found
*
*/
int stats_comment_hash;
/*
* number of tokens folded away
*/
int stats_folds;
/*
* total tokens processed
*/
int stats_tokens;
};
typedef struct libinjection_sqli_state sfilter;
struct libinjection_sqli_token* libinjection_sqli_get_token(
struct libinjection_sqli_state* sqlistate, int i);
/*
* Version info.
*
* This is moved into a function to allow SWIG and other auto-generated
* binding to not be modified during minor release changes. We change
* change the version number in the c source file, and not regenerated
* the binding
*
* See python's normalized version
* http://www.python.org/dev/peps/pep-0386/#normalizedversion
*/
const char* libinjection_version(void);
/**
*
*/
void libinjection_sqli_init(struct libinjection_sqli_state* sql_state,
const char* s, size_t slen,
int flags);
/**
* Main API: tests for SQLi in three possible contexts, no quotes,
* single quote and double quote
*
* \param sql_state core data structure
*
* \return 1 (true) if SQLi, 0 (false) if benign
*/
int libinjection_is_sqli(struct libinjection_sqli_state* sql_state);
/* FOR H@CKERS ONLY
*
*/
void libinjection_sqli_callback(struct libinjection_sqli_state* sql_state,
ptr_lookup_fn fn,
void* userdata);
/*
* Resets state, but keeps initial string and callbacks
*/
void libinjection_sqli_reset(struct libinjection_sqli_state* sql_state,
int flags);
/**
*
*/
/**
* This detects SQLi in a single context, mostly useful for custom
* logic and debugging.
*
* \param sql_state Main data structure
* \param flags flags to adjust parsing
*
* \returns a pointer to sfilter.fingerprint as convenience
* do not free!
*
*/
const char* libinjection_sqli_fingerprint(struct libinjection_sqli_state* sql_state,
int flags);
/**
* The default "word" to token-type or fingerprint function. This
* uses a ASCII case-insensitive binary tree.
*/
char libinjection_sqli_lookup_word(struct libinjection_sqli_state* sql_state,
int lookup_type,
const char* s,
size_t slen);
/* Streaming tokenization interface.
*
* sql_state->current is updated with the current token.
*
* \returns 1, has a token, keep going, or 0 no tokens
*
*/
int libinjection_sqli_tokenize(struct libinjection_sqli_state * sql_state);
/**
* parses and folds input, up to 5 tokens
*
*/
int libinjection_sqli_fold(struct libinjection_sqli_state * sql_state);
/** The built-in default function to match fingerprints
* and do false negative/positive analysis. This calls the following
* two functions. With this, you over-ride one part or the other.
*
* return libinjection_sqli_blacklist(sql_state) &&
* libinject_sqli_not_whitelist(sql_state);
*
* \param sql_state should be filled out after libinjection_sqli_fingerprint is called
*/
int libinjection_sqli_check_fingerprint(struct libinjection_sqli_state * sql_state);
/* Given a pattern determine if it's a SQLi pattern.
*
* \return TRUE if sqli, false otherwise
*/
int libinjection_sqli_blacklist(struct libinjection_sqli_state* sql_state);
/* Given a positive match for a pattern (i.e. pattern is SQLi), this function
* does additional analysis to reduce false positives.
*
* \return TRUE if sqli, false otherwise
*/
int libinjection_sqli_not_whitelist(struct libinjection_sqli_state * sql_state);
#ifdef __cplusplus
}
#endif
#endif /* _LIBINJECTION_SQLI_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,531 @@
#include "libinjection.h"
#include "libinjection_xss.h"
#include "libinjection_html5.h"
#include <assert.h>
#include <stdio.h>
typedef enum attribute {
TYPE_NONE
, TYPE_BLACK /* ban always */
, TYPE_ATTR_URL /* attribute value takes a URL-like object */
, TYPE_STYLE
, TYPE_ATTR_INDIRECT /* attribute *name* is given in *value* */
} attribute_t;
static attribute_t is_black_attr(const char* s, size_t len);
static int is_black_tag(const char* s, size_t len);
static int is_black_url(const char* s, size_t len);
static int cstrcasecmp_with_null(const char *a, const char *b, size_t n);
static int html_decode_char_at(const char* src, size_t len, size_t* consumed);
static int htmlencode_startswith(const char* prefix, const char *src, size_t n);
typedef struct stringtype {
const char* name;
attribute_t atype;
} stringtype_t;
static const int gsHexDecodeMap[256] = {
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 256, 256,
256, 256, 256, 256, 256, 10, 11, 12, 13, 14, 15, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 10, 11, 12, 13, 14, 15, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256
};
static int html_decode_char_at(const char* src, size_t len, size_t* consumed)
{
int val = 0;
size_t i;
int ch;
if (len == 0 || src == NULL) {
*consumed = 0;
return -1;
}
*consumed = 1;
if (*src != '&' || len < 2) {
return (unsigned char)(*src);
}
if (*(src+1) != '#') {
/* normally this would be for named entities
* but for this case we don't actually care
*/
return '&';
}
if (*(src+2) == 'x' || *(src+2) == 'X') {
ch = (unsigned char) (*(src+3));
ch = gsHexDecodeMap[ch];
if (ch == 256) {
/* degenerate case '&#[?]' */
return '&';
}
val = ch;
i = 4;
while (i < len) {
ch = (unsigned char) src[i];
if (ch == ';') {
*consumed = i + 1;
return val;
}
ch = gsHexDecodeMap[ch];
if (ch == 256) {
*consumed = i;
return val;
}
val = (val * 16) + ch;
if (val > 0x1000FF) {
return '&';
}
++i;
}
*consumed = i;
return val;
} else {
i = 2;
ch = (unsigned char) src[i];
if (ch < '0' || ch > '9') {
return '&';
}
val = ch - '0';
i += 1;
while (i < len) {
ch = (unsigned char) src[i];
if (ch == ';') {
*consumed = i + 1;
return val;
}
if (ch < '0' || ch > '9') {
*consumed = i;
return val;
}
val = (val * 10) + (ch - '0');
if (val > 0x1000FF) {
return '&';
}
++i;
}
*consumed = i;
return val;
}
}
/*
* view-source:
* data:
* javascript:
*/
static stringtype_t BLACKATTR[] = {
{ "ACTION", TYPE_ATTR_URL } /* form */
, { "ATTRIBUTENAME", TYPE_ATTR_INDIRECT } /* SVG allow indirection of attribute names */
, { "BY", TYPE_ATTR_URL } /* SVG */
, { "BACKGROUND", TYPE_ATTR_URL } /* IE6, O11 */
, { "DATAFORMATAS", TYPE_BLACK } /* IE */
, { "DATASRC", TYPE_BLACK } /* IE */
, { "DYNSRC", TYPE_ATTR_URL } /* Obsolete img attribute */
, { "FILTER", TYPE_STYLE } /* Opera, SVG inline style */
, { "FORMACTION", TYPE_ATTR_URL } /* HTML5 */
, { "FOLDER", TYPE_ATTR_URL } /* Only on A tags, IE-only */
, { "FROM", TYPE_ATTR_URL } /* SVG */
, { "HANDLER", TYPE_ATTR_URL } /* SVG Tiny, Opera */
, { "HREF", TYPE_ATTR_URL }
, { "LOWSRC", TYPE_ATTR_URL } /* Obsolete img attribute */
, { "POSTER", TYPE_ATTR_URL } /* Opera 10,11 */
, { "SRC", TYPE_ATTR_URL }
, { "STYLE", TYPE_STYLE }
, { "TO", TYPE_ATTR_URL } /* SVG */
, { "VALUES", TYPE_ATTR_URL } /* SVG */
, { "XLINK:HREF", TYPE_ATTR_URL }
, { NULL, TYPE_NONE }
};
/* xmlns */
/* xml-stylesheet > <eval>, <if expr=> */
/*
static const char* BLACKATTR[] = {
"ATTRIBUTENAME",
"BACKGROUND",
"DATAFORMATAS",
"HREF",
"SCROLL",
"SRC",
"STYLE",
"SRCDOC",
NULL
};
*/
static const char* BLACKTAG[] = {
"APPLET"
/* , "AUDIO" */
, "BASE"
, "COMMENT" /* IE http://html5sec.org/#38 */
, "EMBED"
/* , "FORM" */
, "FRAME"
, "FRAMESET"
, "HANDLER" /* Opera SVG, effectively a script tag */
, "IFRAME"
, "IMPORT"
, "ISINDEX"
, "LINK"
, "LISTENER"
/* , "MARQUEE" */
, "META"
, "NOSCRIPT"
, "OBJECT"
, "SCRIPT"
, "STYLE"
/* , "VIDEO" */
, "VMLFRAME"
, "XML"
, "XSS"
, NULL
};
static int cstrcasecmp_with_null(const char *a, const char *b, size_t n)
{
char ca;
char cb;
/* printf("Comparing to %s %.*s\n", a, (int)n, b); */
while (n-- > 0) {
cb = *b++;
if (cb == '\0') continue;
ca = *a++;
if (cb >= 'a' && cb <= 'z') {
cb -= 0x20;
}
/* printf("Comparing %c vs %c with %d left\n", ca, cb, (int)n); */
if (ca != cb) {
return 1;
}
}
if (*a == 0) {
/* printf(" MATCH \n"); */
return 0;
} else {
return 1;
}
}
/*
* Does an HTML encoded binary string (const char*, lenght) start with
* a all uppercase c-string (null terminated), case insenstive!
*
* also ignore any embedded nulls in the HTML string!
*
* return 1 if match / starts with
* return 0 if not
*/
static int htmlencode_startswith(const char *a, const char *b, size_t n)
{
size_t consumed;
int cb;
int first = 1;
/* printf("Comparing %s with %.*s\n", a,(int)n,b); */
while (n > 0) {
if (*a == 0) {
/* printf("Match EOL!\n"); */
return 1;
}
cb = html_decode_char_at(b, n, &consumed);
b += consumed;
n -= consumed;
if (first && cb <= 32) {
/* ignore all leading whitespace and control characters */
continue;
}
first = 0;
if (cb == 0) {
/* always ignore null characters in user input */
continue;
}
if (cb == 10) {
/* always ignore vtab characters in user input */
/* who allows this?? */
continue;
}
if (cb >= 'a' && cb <= 'z') {
/* upcase */
cb -= 0x20;
}
if (*a != (char) cb) {
/* printf(" %c != %c\n", *a, cb); */
/* mismatch */
return 0;
}
a++;
}
return (*a == 0) ? 1 : 0;
}
static int is_black_tag(const char* s, size_t len)
{
const char** black;
if (len < 3) {
return 0;
}
black = BLACKTAG;
while (*black != NULL) {
if (cstrcasecmp_with_null(*black, s, len) == 0) {
/* printf("Got black tag %s\n", *black); */
return 1;
}
black += 1;
}
/* anything SVG related */
if ((s[0] == 's' || s[0] == 'S') &&
(s[1] == 'v' || s[1] == 'V') &&
(s[2] == 'g' || s[2] == 'G')) {
/* printf("Got SVG tag \n"); */
return 1;
}
/* Anything XSL(t) related */
if ((s[0] == 'x' || s[0] == 'X') &&
(s[1] == 's' || s[1] == 'S') &&
(s[2] == 'l' || s[2] == 'L')) {
/* printf("Got XSL tag\n"); */
return 1;
}
return 0;
}
static attribute_t is_black_attr(const char* s, size_t len)
{
stringtype_t* black;
if (len < 2) {
return TYPE_NONE;
}
/* javascript on.* */
if ((s[0] == 'o' || s[0] == 'O') && (s[1] == 'n' || s[1] == 'N')) {
/* printf("Got javascript on- attribute name\n"); */
return TYPE_BLACK;
}
if (len >= 5) {
/* XMLNS can be used to create arbitrary tags */
if (cstrcasecmp_with_null("XMLNS", s, 5) == 0 || cstrcasecmp_with_null("XLINK", s, 5) == 0) {
/* printf("Got XMLNS and XLINK tags\n"); */
return TYPE_BLACK;
}
}
black = BLACKATTR;
while (black->name != NULL) {
if (cstrcasecmp_with_null(black->name, s, len) == 0) {
/* printf("Got banned attribute name %s\n", black->name); */
return black->atype;
}
black += 1;
}
return TYPE_NONE;
}
static int is_black_url(const char* s, size_t len)
{
static const char* data_url = "DATA";
static const char* viewsource_url = "VIEW-SOURCE";
/* obsolete but interesting signal */
static const char* vbscript_url = "VBSCRIPT";
/* covers JAVA, JAVASCRIPT, + colon */
static const char* javascript_url = "JAVA";
/* skip whitespace */
while (len > 0 && (*s <= 32 || *s >= 127)) {
/*
* HEY: this is a signed character.
* We are intentionally skipping high-bit characters too
* since they are not ascii, and Opera sometimes uses UTF8 whitespace.
*
* Also in EUC-JP some of the high bytes are just ignored.
*/
++s;
--len;
}
if (htmlencode_startswith(data_url, s, len)) {
return 1;
}
if (htmlencode_startswith(viewsource_url, s, len)) {
return 1;
}
if (htmlencode_startswith(javascript_url, s, len)) {
return 1;
}
if (htmlencode_startswith(vbscript_url, s, len)) {
return 1;
}
return 0;
}
int libinjection_is_xss(const char* s, size_t len, int flags)
{
h5_state_t h5;
attribute_t attr = TYPE_NONE;
libinjection_h5_init(&h5, s, len, (enum html5_flags) flags);
while (libinjection_h5_next(&h5)) {
if (h5.token_type != ATTR_VALUE) {
attr = TYPE_NONE;
}
if (h5.token_type == DOCTYPE) {
return 1;
} else if (h5.token_type == TAG_NAME_OPEN) {
if (is_black_tag(h5.token_start, h5.token_len)) {
return 1;
}
} else if (h5.token_type == ATTR_NAME) {
attr = is_black_attr(h5.token_start, h5.token_len);
} else if (h5.token_type == ATTR_VALUE) {
/*
* IE6,7,8 parsing works a bit differently so
* a whole <script> or other black tag might be hiding
* inside an attribute value under HTML5 parsing
* See http://html5sec.org/#102
* to avoid doing a full reparse of the value, just
* look for "<". This probably need adjusting to
* handle escaped characters
*/
/*
if (memchr(h5.token_start, '<', h5.token_len) != NULL) {
return 1;
}
*/
switch (attr) {
case TYPE_NONE:
break;
case TYPE_BLACK:
return 1;
case TYPE_ATTR_URL:
if (is_black_url(h5.token_start, h5.token_len)) {
return 1;
}
break;
case TYPE_STYLE:
return 1;
case TYPE_ATTR_INDIRECT:
/* an attribute name is specified in a _value_ */
if (is_black_attr(h5.token_start, h5.token_len)) {
return 1;
}
break;
/*
default:
assert(0);
*/
}
attr = TYPE_NONE;
} else if (h5.token_type == TAG_COMMENT) {
/* IE uses a "`" as a tag ending char */
if (memchr(h5.token_start, '`', h5.token_len) != NULL) {
return 1;
}
/* IE conditional comment */
if (h5.token_len > 3) {
if (h5.token_start[0] == '[' &&
(h5.token_start[1] == 'i' || h5.token_start[1] == 'I') &&
(h5.token_start[2] == 'f' || h5.token_start[2] == 'F')) {
return 1;
}
if ((h5.token_start[0] == 'x' || h5.token_start[1] == 'X') &&
(h5.token_start[1] == 'm' || h5.token_start[1] == 'M') &&
(h5.token_start[2] == 'l' || h5.token_start[2] == 'L')) {
return 1;
}
}
if (h5.token_len > 5) {
/* IE <?import pseudo-tag */
if (cstrcasecmp_with_null("IMPORT", h5.token_start, 6) == 0) {
return 1;
}
/* XML Entity definition */
if (cstrcasecmp_with_null("ENTITY", h5.token_start, 6) == 0) {
return 1;
}
}
}
}
return 0;
}
/*
* wrapper
*/
int libinjection_xss(const char* s, size_t len)
{
if (libinjection_is_xss(s, len, DATA_STATE)) {
return 1;
}
if (libinjection_is_xss(s, len, VALUE_NO_QUOTE)) {
return 1;
}
if (libinjection_is_xss(s, len, VALUE_SINGLE_QUOTE)) {
return 1;
}
if (libinjection_is_xss(s, len, VALUE_DOUBLE_QUOTE)) {
return 1;
}
if (libinjection_is_xss(s, len, VALUE_BACK_QUOTE)) {
return 1;
}
return 0;
}

View File

@@ -0,0 +1,21 @@
#ifndef LIBINJECTION_XSS
#define LIBINJECTION_XSS
#ifdef __cplusplus
extern "C" {
#endif
/**
* HEY THIS ISN'T DONE
*/
/* pull in size_t */
#include <string.h>
int libinjection_is_xss(const char* s, size_t len, int flags);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,314 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
#include "libinjection_xss.h"
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
static int g_test_ok = 0;
static int g_test_fail = 0;
typedef enum {
MODE_SQLI,
MODE_XSS
} detect_mode_t;
static void usage(const char* argv[]);
size_t modp_rtrim(char* str, size_t len);
void modp_toprint(char* str, size_t len);
void test_positive(FILE * fd, const char *fname, detect_mode_t mode,
int flag_invert, int flag_true, int flag_quiet);
int urlcharmap(char ch);
size_t modp_url_decode(char* dest, const char* s, size_t len);
int urlcharmap(char ch) {
switch (ch) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
default:
return 256;
}
}
size_t modp_url_decode(char* dest, const char* s, size_t len)
{
const char* deststart = dest;
size_t i = 0;
int d = 0;
while (i < len) {
switch (s[i]) {
case '+':
*dest++ = ' ';
i += 1;
break;
case '%':
if (i+2 < len) {
d = (urlcharmap(s[i+1]) << 4) | urlcharmap(s[i+2]);
if ( d < 256) {
*dest = (char) d;
dest++;
i += 3; /* loop will increment one time */
} else {
*dest++ = '%';
i += 1;
}
} else {
*dest++ = '%';
i += 1;
}
break;
default:
*dest++ = s[i];
i += 1;
}
}
*dest = '\0';
return (size_t)(dest - deststart); /* compute "strlen" of dest */
}
void modp_toprint(char* str, size_t len)
{
size_t i;
for (i = 0; i < len; ++i) {
if (str[i] < 32 || str[i] > 126) {
str[i] = '?';
}
}
}
size_t modp_rtrim(char* str, size_t len)
{
while (len) {
char c = str[len -1];
if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
str[len -1] = '\0';
len -= 1;
} else {
break;
}
}
return len;
}
void test_positive(FILE * fd, const char *fname, detect_mode_t mode,
int flag_invert, int flag_true, int flag_quiet)
{
char linebuf[8192];
int issqli;
int linenum = 0;
size_t len;
sfilter sf;
while (fgets(linebuf, sizeof(linebuf), fd)) {
linenum += 1;
len = modp_rtrim(linebuf, strlen(linebuf));
if (len == 0) {
continue;
}
if (linebuf[0] == '#') {
continue;
}
len = modp_url_decode(linebuf, linebuf, len);
issqli = 0;
switch (mode) {
case MODE_SQLI: {
libinjection_sqli_init(&sf, linebuf, len, 0);
issqli = libinjection_is_sqli(&sf);
break;
}
case MODE_XSS: {
issqli = libinjection_xss(linebuf, len);
break;
}
default:
assert(0);
}
if (issqli) {
g_test_ok += 1;
} else {
g_test_fail += 1;
}
if (!flag_quiet) {
if ((issqli && flag_true && ! flag_invert) ||
(!issqli && flag_true && flag_invert) ||
!flag_true) {
modp_toprint(linebuf, len);
switch (mode) {
case MODE_SQLI: {
/*
* if we didn't find a SQLi and fingerprint from
* sqlstats is is 'sns' or 'snsns' then redo using
* plain context
*/
if (!issqli && (strcmp(sf.fingerprint, "sns") == 0 ||
strcmp(sf.fingerprint, "snsns") == 0)) {
libinjection_sqli_fingerprint(&sf, 0);
}
fprintf(stdout, "%s\t%d\t%s\t%s\t%s\n",
fname, linenum,
(issqli ? "True" : "False"), sf.fingerprint, linebuf);
break;
}
case MODE_XSS: {
fprintf(stdout, "%s\t%d\t%s\t%s\n",
fname, linenum,
(issqli ? "True" : "False"), linebuf);
break;
}
default:
assert(0);
}
}
}
}
}
static void usage(const char* argv[])
{
fprintf(stdout, "usage: %s [flags] [files...]\n", argv[0]);
fprintf(stdout, "%s\n", "");
fprintf(stdout, "%s\n", "-q --quiet : quiet mode");
fprintf(stdout, "%s\n", "-m --max-fails : number of failed cases need to fail entire test");
fprintf(stdout, "%s\n", "-s INTEGER : repeat each test N time "
"(for performance testing)");
fprintf(stdout, "%s\n", "-t : only print positive matches");
fprintf(stdout, "%s\n", "-x --mode-xss : test input for XSS");
fprintf(stdout, "%s\n", "-i --invert : invert test logic "
"(input is tested for being safe)");
fprintf(stdout, "%s\n", "");
fprintf(stdout, "%s\n", "-? -h -help --help : this page");
fprintf(stdout, "%s\n", "");
}
int main(int argc, const char *argv[])
{
/*
* invert output, by
*/
int flag_invert = FALSE;
/*
* don't print anything.. useful for
* performance monitors, gprof.
*/
int flag_quiet = FALSE;
/*
* only print postive results
* with invert, only print negative results
*/
int flag_true = FALSE;
detect_mode_t mode = MODE_SQLI;
int flag_slow = 1;
int count = 0;
int max = -1;
int i, j;
int offset = 1;
while (offset < argc) {
if (strcmp(argv[offset], "-?") == 0 ||
strcmp(argv[offset], "-h") == 0 ||
strcmp(argv[offset], "-help") == 0 ||
strcmp(argv[offset], "--help") == 0) {
usage(argv);
exit(0);
}
if (strcmp(argv[offset], "-i") == 0) {
offset += 1;
flag_invert = TRUE;
} else if (strcmp(argv[offset], "-q") == 0 ||
strcmp(argv[offset], "--quiet") == 0) {
offset += 1;
flag_quiet = TRUE;
} else if (strcmp(argv[offset], "-t") == 0) {
offset += 1;
flag_true = TRUE;
} else if (strcmp(argv[offset], "-s") == 0) {
offset += 1;
flag_slow = 100;
} else if (strcmp(argv[offset], "-m") == 0 ||
strcmp(argv[offset], "--max-fails") == 0) {
offset += 1;
max = atoi(argv[offset]);
offset += 1;
} else if (strcmp(argv[offset], "-x") == 0 ||
strcmp(argv[offset], "--mode-xss") == 0) {
mode = MODE_XSS;
offset += 1;
} else {
break;
}
}
if (offset == argc) {
test_positive(stdin, "stdin", mode, flag_invert, flag_true, flag_quiet);
} else {
for (j = 0; j < flag_slow; ++j) {
for (i = offset; i < argc; ++i) {
FILE* fd = fopen(argv[i], "r");
if (fd) {
test_positive(fd, argv[i], mode, flag_invert, flag_true, flag_quiet);
fclose(fd);
}
}
}
}
if (!flag_quiet) {
fprintf(stdout, "%s", "\n");
fprintf(stdout, "SQLI : %d\n", g_test_ok);
fprintf(stdout, "SAFE : %d\n", g_test_fail);
fprintf(stdout, "TOTAL : %d\n", g_test_ok + g_test_fail);
}
if (max == -1) {
return 0;
}
count = g_test_ok;
if (flag_invert) {
count = g_test_fail;
}
if (count > max) {
printf("\nThreshold is %d, got %d, failing.\n", max, count);
return 1;
} else {
printf("\nThreshold is %d, got %d, passing.\n", max, count);
return 0;
}
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright 2012, 2013 Nick Galbreath
* nickg@client9.com
* BSD License -- see COPYING.txt for details
*
* This is for testing against files in ../data/ *.txt
* Reads from stdin or a list of files, and emits if a line
* is a SQLi attack or not, and does basic statistics
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
void print_string(stoken_t* t);
void print_var(stoken_t* t);
void print_token(stoken_t *t);
void print_string(stoken_t* t)
{
/* print opening quote */
if (t->str_open != '\0') {
printf("%c", t->str_open);
}
/* print content */
printf("%s", t->val);
/* print closing quote */
if (t->str_close != '\0') {
printf("%c", t->str_close);
}
}
void print_var(stoken_t* t)
{
if (t->count >= 1) {
printf("%c", '@');
}
if (t->count == 2) {
printf("%c", '@');
}
print_string(t);
}
void print_token(stoken_t *t) {
printf("%c ", t->type);
switch (t->type) {
case 's':
print_string(t);
break;
case 'v':
print_var(t);
break;
default:
printf("%s", t->val);
}
printf("%s", "\n");
}
int main(int argc, const char* argv[])
{
size_t slen;
char* copy;
int flags = 0;
int fold = 0;
int detect = 0;
int i;
int count;
int offset = 1;
int issqli;
sfilter sf;
if (argc < 2) {
fprintf(stderr, "need more args\n");
return 1;
}
while (1) {
if (strcmp(argv[offset], "-m") == 0) {
flags |= FLAG_SQL_MYSQL;
offset += 1;
}
else if (strcmp(argv[offset], "-f") == 0 || strcmp(argv[offset], "--fold") == 0) {
fold = 1;
offset += 1;
} else if (strcmp(argv[offset], "-d") == 0 || strcmp(argv[offset], "--detect") == 0) {
detect = 1;
offset += 1;
} else if (strcmp(argv[offset], "-ca") == 0) {
flags |= FLAG_SQL_ANSI;
offset += 1;
} else if (strcmp(argv[offset], "-cm") == 0) {
flags |= FLAG_SQL_MYSQL;
offset += 1;
} else if (strcmp(argv[offset], "-q0") == 0) {
flags |= FLAG_QUOTE_NONE;
offset += 1;
} else if (strcmp(argv[offset], "-q1") == 0) {
flags |= FLAG_QUOTE_SINGLE;
offset += 1;
} else if (strcmp(argv[offset], "-q2") == 0) {
flags |= FLAG_QUOTE_DOUBLE;
offset += 1;
} else {
break;
}
}
/* ATTENTION: argv is a C-string, null terminated. We copy this
* to it's own location, WITHOUT null byte. This way, valgrind
* can see if we run past the buffer.
*/
slen = strlen(argv[offset]);
copy = (char* ) malloc(slen);
memcpy(copy, argv[offset], slen);
libinjection_sqli_init(&sf, copy, slen, flags);
if (detect == 1) {
issqli = libinjection_is_sqli(&sf);
if (issqli) {
printf("%s\n", sf.fingerprint);
}
} else if (fold == 1) {
count = libinjection_sqli_fold(&sf);
for (i = 0; i < count; ++i) {
print_token(&(sf.tokenvec[i]));
}
} else {
while (libinjection_sqli_tokenize(&sf)) {
print_token(sf.current);
}
}
free(copy);
return 0;
}

View File

@@ -0,0 +1,68 @@
/*
* A not very good test for performance. This is mostly useful in
* testing performance -regressions-
*
*/
#include <time.h>
#include <string.h>
#include <stdio.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
int testIsSQL(void);
int testIsSQL(void)
{
const char* const s[] = {
"123 LIKE -1234.5678E+2;",
"APPLE 19.123 'FOO' \"BAR\"",
"/* BAR */ UNION ALL SELECT (2,3,4)",
"1 || COS(+0X04) --FOOBAR",
"dog apple @cat banana bar",
"dog apple cat \"banana \'bar",
"102 TABLE CLOTH",
"(1001-'1') union select 1,2,3,4 from credit_cards",
NULL
};
const int imax = 1000000;
int i, j;
size_t slen;
sfilter sf;
clock_t t0,t1;
double total;
int tps;
t0 = clock();
for (i = imax, j=0; i != 0; --i, ++j) {
if (s[j] == NULL) {
j = 0;
}
slen = strlen(s[j]);
libinjection_sqli_init(&sf, s[j], slen, FLAG_QUOTE_NONE | FLAG_SQL_ANSI);
libinjection_is_sqli(&sf);
}
t1 = clock();
total = (double) (t1 - t0) / (double) CLOCKS_PER_SEC;
tps = (int)((double) imax / total);
return tps;
}
int main()
{
const int mintps = 450000;
int tps = testIsSQL();
printf("\nTPS : %d\n\n", tps);
if (tps < mintps) {
printf("FAIL: %d < %d\n", tps, mintps);
/* FAIL */
return 1;
} else {
printf("OK: %d > %d\n", tps, mintps);
/* OK */
return 0;
}
}

View File

@@ -0,0 +1,84 @@
/*
* A not very good test for performance. This is mostly useful in
* testing performance -regressions-
*
*/
#include <time.h>
#include <string.h>
#include <stdio.h>
#include "libinjection.h"
int testIsSQL(void);
int testIsSQL(void)
{
const char* const s[] = {
"<script>alert(1);</script>",
"><script>alert(1);</script>"
"x ><script>alert(1);</script>",
"' ><script>alert(1);</script>",
"\"><script>alert(1);</script>",
"red;</style><script>alert(1);</script>",
"red;}</style><script>alert(1);</script>",
"red;\"/><script>alert(1);</script>",
"');}</style><script>alert(1);</script>",
"onerror=alert(1)>",
"x onerror=alert(1);>",
"x' onerror=alert(1);>",
"x\" onerror=alert(1);>",
"<a href=\"javascript:alert(1)\">",
"<a href='javascript:alert(1)'>",
"<a href=javascript:alert(1)>",
"<a href = javascript:alert(1); >",
"<a href=\" javascript:alert(1);\" >",
"<a href=\"JAVASCRIPT:alert(1);\" >",
"123 LIKE -1234.5678E+2;",
"APPLE 19.123 'FOO' \"BAR\"",
"/* BAR */ UNION ALL SELECT (2,3,4)",
"1 || COS(+0X04) --FOOBAR",
"dog apple @cat banana bar",
"dog apple cat \"banana \'bar",
"102 TABLE CLOTH",
"(1001-'1') union select 1,2,3,4 from credit_cards",
NULL
};
const int imax = 1000000;
int i, j;
size_t slen;
clock_t t0,t1;
double total;
int tps;
t0 = clock();
for (i = imax, j=0; i != 0; --i, ++j) {
if (s[j] == NULL) {
j = 0;
}
slen = strlen(s[j]);
libinjection_xss(s[j], slen);
}
t1 = clock();
total = (double) (t1 - t0) / (double) CLOCKS_PER_SEC;
tps = (int)((double) imax / total);
return tps;
}
int main()
{
const int mintps = 500000;
int tps = testIsSQL();
printf("\nTPS : %d\n\n", tps);
if (tps < 500000) {
printf("FAIL: %d < %d\n", tps, mintps);
/* FAIL */
return 1;
} else {
printf("OK: %d > %d\n", tps, mintps);
/* OK */
return 0;
}
}

View File

@@ -0,0 +1,312 @@
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glob.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
#include "libinjection_html5.h"
#include "libinjection_xss.h"
static char g_test[8096];
static char g_input[8096];
static char g_expected[8096];
size_t modp_rtrim(char* str, size_t len);
size_t print_string(char* buf, size_t len, stoken_t* t);
size_t print_var(char* buf, size_t len, stoken_t* t);
size_t print_token(char* buf, size_t len, stoken_t *t);
int read_file(const char* fname, int flags, int testtype);
const char* h5_type_to_string(enum html5_type x);
size_t print_html5_token(char* buf, size_t len, h5_state_t* hs);
size_t modp_rtrim(char* str, size_t len)
{
while (len) {
char c = str[len -1];
if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
str[len -1] = '\0';
len -= 1;
} else {
break;
}
}
return len;
}
size_t print_string(char* buf, size_t len, stoken_t* t)
{
int slen = 0;
/* print opening quote */
if (t->str_open != '\0') {
slen = sprintf(buf + len, "%c", t->str_open);
assert(slen >= 0);
len += (size_t) slen;
}
/* print content */
slen = sprintf(buf + len, "%s", t->val);
assert(slen >= 0);
len += (size_t) slen;
/* print closing quote */
if (t->str_close != '\0') {
slen = sprintf(buf + len, "%c", t->str_close);
assert(slen >= 0);
len += (size_t) slen;
}
return len;
}
size_t print_var(char* buf, size_t len, stoken_t* t)
{
int slen = 0;
if (t->count >= 1) {
slen = sprintf(buf + len, "%c", '@');
assert(slen >= 0);
len += (size_t) slen;
}
if (t->count == 2) {
slen = sprintf(buf + len, "%c", '@');
assert(slen >= 0);
len += (size_t) slen;
}
return print_string(buf, len, t);
}
const char* h5_type_to_string(enum html5_type x)
{
switch (x) {
case DATA_TEXT: return "DATA_TEXT";
case TAG_NAME_OPEN: return "TAG_NAME_OPEN";
case TAG_NAME_CLOSE: return "TAG_NAME_CLOSE";
case TAG_NAME_SELFCLOSE: return "TAG_NAME_SELFCLOSE";
case TAG_DATA: return "TAG_DATA";
case TAG_CLOSE: return "TAG_CLOSE";
case ATTR_NAME: return "ATTR_NAME";
case ATTR_VALUE: return "ATTR_VALUE";
case TAG_COMMENT: return "TAG_COMMENT";
case DOCTYPE: return "DOCTYPE";
default:
assert(0);
}
}
size_t print_html5_token(char* buf, size_t len, h5_state_t* hs)
{
int slen;
char* tmp = (char*) malloc(hs->token_len + 1);
memcpy(tmp, hs->token_start, hs->token_len);
/* TODO.. encode to be printable */
tmp[hs->token_len] = '\0';
slen = sprintf(buf + len, "%s,%d,%s\n",
h5_type_to_string(hs->token_type),
(int) hs->token_len,
tmp);
len += (size_t) slen;
free(tmp);
return len;
}
size_t print_token(char* buf, size_t len, stoken_t *t)
{
int slen;
slen = sprintf(buf + len, "%c ", t->type);
assert(slen >= 0);
len += (size_t) slen;
switch (t->type) {
case 's':
len = print_string(buf, len, t);
break;
case 'v':
len = print_var(buf, len, t);
break;
default:
slen = sprintf(buf + len, "%s", t->val);
assert(slen >= 0);
len += (size_t) slen;
}
slen = sprintf(buf + len, "%c", '\n');
assert(slen >= 0);
len += (size_t) slen;
return len;
}
int read_file(const char* fname, int flags, int testtype)
{
int count = 0;
FILE *fp = NULL;
char linebuf[8192];
char g_actual[8192];
char* bufptr = NULL;
size_t slen;
char* copy;
sfilter sf;
int ok = 1;
int num_tokens;
int issqli;
int i;
g_test[0] = '\0';
g_input[0] = '\0';
g_expected[0] = '\0';
fp = fopen(fname, "r");
while(fgets(linebuf, sizeof(linebuf), fp) != NULL) {
if (count == 0 && strcmp(linebuf, "--TEST--\n") == 0) {
bufptr = g_test;
count = 1;
} else if (count == 1 && strcmp(linebuf, "--INPUT--\n") == 0) {
bufptr = g_input;
count = 2;
} else if (count == 2 && strcmp(linebuf, "--EXPECTED--\n") == 0) {
bufptr = g_expected;
count = 3;
} else {
assert(bufptr != NULL);
strcat(bufptr, linebuf);
}
}
fclose(fp);
if (count != 3) {
return 1;
}
g_expected[modp_rtrim(g_expected, strlen(g_expected))] = '\0';
g_input[modp_rtrim(g_input, strlen(g_input))] = '\0';
slen = strlen(g_input);
copy = (char* ) malloc(slen);
memcpy(copy, g_input, slen);
g_actual[0] = '\0';
if (testtype == 0) {
/*
* print sqli tokenization only
*/
libinjection_sqli_init(&sf, copy, slen, flags);
libinjection_sqli_callback(&sf, NULL, NULL);
slen =0;
while (libinjection_sqli_tokenize(&sf) == 1) {
slen = print_token(g_actual, slen, sf.current);
}
} else if (testtype == 1) {
/*
* testing tokenization + folding
*/
libinjection_sqli_init(&sf, copy, slen, flags);
libinjection_sqli_callback(&sf, NULL, NULL);
slen =0;
num_tokens = libinjection_sqli_fold(&sf);
for (i = 0; i < num_tokens; ++i) {
slen = print_token(g_actual, slen, libinjection_sqli_get_token(&sf, i));
}
} else if (testtype == 2) {
/**
* test sqli detection
*/
char buf[100];
issqli = libinjection_sqli(copy, slen, buf);
if (issqli) {
sprintf(g_actual, "%s", buf);
}
} else if (testtype == 3) {
/*
* test html5 tokenization only
*/
h5_state_t hs;
libinjection_h5_init(&hs, copy, slen, DATA_STATE);
slen = 0;
while (libinjection_h5_next(&hs)) {
slen = print_html5_token(g_actual, slen, &hs);
}
} else if (testtype == 4) {
/*
* test XSS detection
*/
sprintf(g_actual, "%d", libinjection_xss(copy, slen));
} else {
fprintf(stderr, "Got stange testtype value of %d\n", testtype);
assert(0);
}
g_actual[modp_rtrim(g_actual, strlen(g_actual))] = '\0';
if (strcmp(g_expected, g_actual) != 0) {
printf("INPUT: \n%s\n==\n", g_input);
printf("EXPECTED: \n%s\n==\n", g_expected);
printf("GOT: \n%s\n==\n", g_actual);
ok = 0;
}
free(copy);
return ok;
}
int main(int argc, char** argv)
{
int offset = 1;
int i;
int ok;
int count = 0;
int count_fail = 0;
int flags = 0;
int testtype = 0;
int quiet = 0;
const char* fname;
while (1) {
if (strcmp(argv[offset], "-q") == 0 || strcmp(argv[offset], "--quiet") == 0) {
quiet = 1;
offset += 1;
} else {
break;
}
}
printf("%s\n", libinjection_version());
for (i = offset; i < argc; ++i) {
fname = argv[i];
count += 1;
if (strstr(fname, "test-tokens-")) {
flags = FLAG_QUOTE_NONE | FLAG_SQL_ANSI;
testtype = 0;
} else if (strstr(fname, "test-folding-")) {
flags = FLAG_QUOTE_NONE | FLAG_SQL_ANSI;
testtype = 1;
} else if (strstr(fname, "test-sqli-")) {
flags = FLAG_NONE;
testtype = 2;
} else if (strstr(fname, "test-html5-")) {
flags = FLAG_NONE;
testtype = 3;
} else if (strstr(fname, "test-xss-")) {
flags = FLAG_NONE;
testtype = 4;
} else {
fprintf(stderr, "Unknown test type: %s, failing\n", fname);
count_fail += 1;
continue;
}
ok = read_file(fname, flags, testtype);
if (ok) {
if (! quiet) {
fprintf(stderr, "%s: ok\n", fname);
}
} else {
count_fail += 1;
if (! quiet) {
fprintf(stderr, "%s: fail\n", fname);
}
}
}
return count > 0 && count_fail > 0;
}

View File

@@ -0,0 +1,617 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __FOO_H__
#define __FOO_H__
#define NAXSI_VERSION "0.55.3"
#include <nginx.h>
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_event.h>
#include <ngx_md5.h>
#include <ngx_http_core_module.h>
#include <pcre.h>
#include <ctype.h>
#include "ext/libinjection/libinjection_sqli.h"
#include "ext/libinjection/libinjection_xss.h"
extern ngx_module_t ngx_http_naxsi_module;
/*
** as the #ifdef #endif for debug are getting really messy ...
** Bellow are all the possibles debug defines. To enable associated feature
** debug, just set it to 1. Do not comment the actual define except if you
** know all the associated debug calls are deleted.
** The idea is that the compiler will optimize out the do { if (0) ... } while (0);
*/
#define _naxsi_rawbody 0
#define _debug_basestr_ruleset 0
#define _debug_custom_score 0
#define _debug_body_parse 0
#define _debug_cfg_parse_one_rule 0
#define _debug_zone 0
#define _debug_extensive_log 0
#define _debug_loc_conf 0
#define _debug_main_conf 0
#define _debug_mechanics 0
#define _debug_json 0
#define _debug_modifier 0
#define _debug_payload_handler 0
#define _debug_post_heavy 0
#define _debug_rawbody 0
#define _debug_readconf 0
#define _debug_rx 0
#define _debug_score 0
#define _debug_spliturl_ruleset 0
#define _debug_whitelist_compat 0
#define _debug_whitelist 0
#define _debug_whitelist_heavy 0
#define _debug_whitelist_light 0
#define wl_debug_rx 0
#ifndef __NAXSI_DEBUG
#define __NAXSI_DEBUG
#define NX_DEBUG(FEATURE, DEF, LOG, ST, ...) do { if (FEATURE) ngx_log_debug(DEF, LOG, ST, __VA_ARGS__); } while (0)
#endif
#ifndef __NAXSI_LOG_DEBUG
#define __NAXSI_LOG_DEBUG
#define NX_LOG_DEBUG(FEATURE, DEF, LOG, ST, ...) do { if (FEATURE) ngx_conf_log_error(DEF, LOG, ST, __VA_ARGS__); } while (0)
#endif
/*
** Here is globally how the structures are organized :
**
** [[ngx_http_dummy_main_conf_t]] is the main structure for the module.
** it contains the core rules and a set of ngx_http_dummy_loc_conf_t,
** for each NGINX location.
** ---
** [[ngx_http_dummy_loc_conf_t]] is the main structure for any NGINX
** locations, that is - a web site. It contains both pointers to the core
** rules, as well as whitelists, scores, denied_url and all flags. all
** the data of a nginx location is held into the loc_conf_t struct.
** The sets of rules are actually containted into [[ngx_http_rule_t]] structs.
** ---
** [[ngx_http_rule_t]] structs are used to hold any info about a rule, as well
** as whitelists. (whitelists is just a 'kind' of rule).
**
*/
/*
** basic rule can have 4 (so far) kind of matching mechanisms
** RX
** STR
** LIBINJ_XSS
** LIBINJ_SQL
*/
enum DETECT_MECHANISM {
NONE = -1,
RX,
STR,
LIBINJ_XSS,
LIBINJ_SQL
};
enum MATCH_TYPE {
URI_ONLY=0,
NAME_ONLY,
MIXED
};
enum DUMMY_MATCH_ZONE {
HEADERS=0,
URL,
ARGS,
BODY,
RAW_BODY,
FILE_EXT,
UNKNOWN
};
/*
** struct used to store a specific match zone
** in conf : MATCH_ZONE:[GET_VAR|HEADER|POST_VAR]:VAR_NAME:
*/
typedef struct
{
/* match in [name] var of body */
ngx_flag_t body_var:1;
/* match in [name] var of headers */
ngx_flag_t headers_var:1;
/* match in [name] var of args */
ngx_flag_t args_var:1;
/* match on URL [name] */
ngx_flag_t specific_url:1;
ngx_str_t target;
/* to be used for regexed match zones */
ngx_regex_compile_t *target_rx;
ngx_uint_t hash;
} ngx_http_custom_rule_location_t;
/*
** WhiteList Rules Definition :
** A whitelist contains :
** - an URI
**
** - one or several sets containing :
** - an variable name ('foo') associated with a zone ($GET_VAR:foo)
** - one or several rules id to whitelist
*/
typedef struct
{
/* match in full body (POST DATA) */
ngx_flag_t body:1;
/* match in [name] var of body */
ngx_flag_t body_var:1;
/* match in all headers */
ngx_flag_t headers:1;
/* match in [name] var of headers */
ngx_flag_t headers_var:1;
/* match in URI */
ngx_flag_t url:1;
/* match in args (bla.php?<ARGS>) */
ngx_flag_t args:1;
/* match in [name] var of args */
ngx_flag_t args_var:1;
/* match on a global flag : weird_request, big_body etc. */
ngx_flag_t flags:1;
/* match on file upload extension */
ngx_flag_t file_ext:1;
/* set if defined "custom" match zone (GET_VAR/POST_VAR/...) */
ngx_array_t *ids;
ngx_str_t *name;
} ngx_http_whitelist_location_t;
/*
** this struct is used to aggregate all whitelist
** that point to the same URI or the same VARNAME
** all the "subrules" will then be stored in the "whitelist_locations"
*/
typedef struct
{
/*ngx_http_whitelist_location_t **/
ngx_array_t *whitelist_locations;
/* zone to wich the WL applies */
enum DUMMY_MATCH_ZONE zone;
/* if the "name" is only an url, specify it */
int uri_only:1;
/* does the rule targets the name
instead of the content ?*/
int target_name;
ngx_str_t *name;
ngx_int_t hash;
ngx_array_t *ids;
} ngx_http_whitelist_rule_t;
/* basic rule */
typedef struct
{
ngx_str_t *str; // string
ngx_regex_compile_t *rx; // or regex
/*
** basic rule can have 4 (so far) kind of matching mechanisms :
** RX, STR, LIBINJ_XSS, LIBINJ_SQL
*/
enum DETECT_MECHANISM match_type;
/* is the match zone a regex or a string (hashtable) */
ngx_int_t rx_mz;
/* ~~~~~ match zones ~~~~~~ */
ngx_int_t zone;
/* match in full body (POST DATA) */
ngx_flag_t body:1;
ngx_flag_t raw_body:1;
ngx_flag_t body_var:1;
/* match in all headers */
ngx_flag_t headers:1;
ngx_flag_t headers_var:1;
/* match in URI */
ngx_flag_t url:1;
/* match in args (bla.php?<ARGS>) */
ngx_flag_t args:1;
ngx_flag_t args_var:1;
/* match on flags (weird_uri, big_body etc. */
ngx_flag_t flags:1;
/* match on file upload extension */
ngx_flag_t file_ext:1;
/* set if defined "custom" match zone (GET_VAR/POST_VAR/...) */
ngx_flag_t custom_location:1;
ngx_int_t custom_location_only;
/* does the rule targets variable name instead ? */
ngx_int_t target_name;
/* custom location match zones list (GET_VAR/POST_VAR ...) */
ngx_array_t *custom_locations;
/* ~~~~~~~ specific flags ~~~~~~~~~ */
ngx_flag_t negative:1;
} ngx_http_basic_rule_t;
/* define for RULE TYPE in rule_t */
#define BR 1
/* flags used for 'custom match rules', like $XSS > 7 */
#define SUP 1
#define SUP_OR_EQUAL 2
#define INF 3
#define INF_OR_EQUAL 4
/*
** This struct is used to store custom scores at runtime.
** ie : $XSS = 7
** tag is the $XSS and sc_score is 7
*/
typedef struct
{
ngx_str_t *sc_tag;
ngx_int_t sc_score;
ngx_flag_t block:1;
ngx_flag_t allow:1;
ngx_flag_t drop:1;
ngx_flag_t log:1;
} ngx_http_special_score_t;
/*
** This one is very related to the previous one,
** it's used to store a score rule comparison.
** ie : $XSS > 7
*/
typedef struct
{
ngx_str_t sc_tag;
ngx_int_t sc_score;
ngx_int_t cmp;
ngx_flag_t block:1;
ngx_flag_t allow:1;
ngx_flag_t drop:1;
ngx_flag_t log:1;
} ngx_http_check_rule_t;
/* TOP level rule structure */
typedef struct
{
/* type of the rule */
ngx_int_t type;
/* simply put a flag if it's a wlr,
wl_id array will be used to store the whitelisted IDs */
ngx_flag_t whitelist:1;
ngx_array_t *wlid_array;
/* "common" data for all rules */
ngx_int_t rule_id;
ngx_str_t *log_msg; // a specific log message
ngx_int_t score; //also handles DENY and ALLOW
/* List of scores increased on rule match. */
ngx_array_t *sscores;
ngx_flag_t sc_block:1; //
ngx_flag_t sc_allow:1; //
// end of specific score tag stuff
ngx_flag_t block:1;
ngx_flag_t allow:1;
ngx_flag_t drop:1;
ngx_flag_t log:1;
/* pointers on specific rule stuff */
ngx_http_basic_rule_t *br;
} ngx_http_rule_t;
typedef struct
{
ngx_array_t *get_rules; /*ngx_http_rule_t*/
ngx_array_t *body_rules;
ngx_array_t *header_rules;
ngx_array_t *generic_rules;
ngx_array_t *raw_body_rules;
ngx_array_t *locations; /*ngx_http_dummy_loc_conf_t*/
ngx_log_t *log;
} ngx_http_dummy_main_conf_t;
/* TOP level configuration structure */
typedef struct
{
/*
** basicrule / mainrules, sorted by target zone
*/
ngx_array_t *get_rules;
ngx_array_t *body_rules;
ngx_array_t *raw_body_rules;
ngx_array_t *header_rules;
ngx_array_t *generic_rules;
ngx_array_t *check_rules;
/* raw array of whitelisted rules */
ngx_array_t *whitelist_rules;
/* raw array of transformed whitelists */
ngx_array_t *tmp_wlr;
/* raw array of regex-mz whitelists */
ngx_array_t *rxmz_wlr;
/* hash table of whitelisted URL rules */
ngx_hash_t *wlr_url_hash;
/* hash table of whitelisted ARGS rules */
ngx_hash_t *wlr_args_hash;
/* hash table of whitelisted BODY rules */
ngx_hash_t *wlr_body_hash;
/* hash table of whitelisted HEADERS rules */
ngx_hash_t *wlr_headers_hash;
/* rules that are globally disabled in one location */
ngx_array_t *disabled_rules;
/* counters for both processed requests and
blocked requests, used in naxsi_fmt */
size_t request_processed;
size_t request_blocked;
ngx_int_t error;
ngx_array_t *persistant_data;
ngx_flag_t extensive:1;
ngx_flag_t learning:1;
ngx_flag_t enabled:1;
ngx_flag_t force_disabled:1;
ngx_flag_t pushed:1;
ngx_flag_t libinjection_sql_enabled:1;
ngx_flag_t libinjection_xss_enabled:1;
ngx_str_t *denied_url;
/* precomputed hash for dynamic variable lookup,
variable themselves are boolean */
ngx_uint_t flag_enable_h;
ngx_uint_t flag_learning_h;
ngx_uint_t flag_post_action_h;
ngx_uint_t flag_extensive_log_h;
/* precomputed hash for
libinjection dynamic flags */
ngx_uint_t flag_libinjection_xss_h;
ngx_uint_t flag_libinjection_sql_h;
} ngx_http_dummy_loc_conf_t;
/*
** used to store sets of matched rules during runtime
*/
typedef struct
{
/* matched in [name] var of body */
ngx_flag_t body_var:1;
/* matched in [name] var of headers */
ngx_flag_t headers_var:1;
/* matched in [name] var of args */
ngx_flag_t args_var:1;
/* matched on URL */
ngx_flag_t url:1;
/* matched in filename [name] of args*/
ngx_flag_t file_ext:1;
/* matched within the 'NAME' */
ngx_flag_t target_name:1;
ngx_str_t *name;
ngx_http_rule_t *rule;
} ngx_http_matched_rule_t;
/*
** Context structure
*/
typedef struct
{
ngx_array_t *special_scores;
ngx_int_t score;
/* blocking flags */
ngx_flag_t log:1;
ngx_flag_t block:1;
ngx_flag_t allow:1;
ngx_flag_t drop:1;
/* state */
ngx_flag_t wait_for_body:1;
ngx_flag_t ready:1;
ngx_flag_t over:1;
/* matched rules */
ngx_array_t *matched;
/* runtime flags (modifiers) */
ngx_flag_t learning:1;
ngx_flag_t enabled:1;
ngx_flag_t post_action:1;
ngx_flag_t extensive_log:1;
/* did libinjection sql/xss matched ? */
ngx_flag_t libinjection_sql:1;
ngx_flag_t libinjection_xss:1;
} ngx_http_request_ctx_t;
/*
** this structure is used only for json parsing.
*/
typedef struct ngx_http_nx_json_s {
ngx_str_t json;
u_char *src;
ngx_int_t off, len;
u_char c;
int depth;
ngx_http_request_t *r;
ngx_http_request_ctx_t *ctx;
ngx_str_t ckey;
ngx_http_dummy_main_conf_t *main_cf;
ngx_http_dummy_loc_conf_t *loc_cf;
} ngx_json_t;
#define TOP_DENIED_URL_T "DeniedUrl"
#define TOP_LEARNING_FLAG_T "LearningMode"
#define TOP_ENABLED_FLAG_T "SecRulesEnabled"
#define TOP_DISABLED_FLAG_T "SecRulesDisabled"
#define TOP_CHECK_RULE_T "CheckRule"
#define TOP_BASIC_RULE_T "BasicRule"
#define TOP_MAIN_BASIC_RULE_T "MainRule"
#define TOP_LIBINJECTION_SQL_T "LibInjectionSql"
#define TOP_LIBINJECTION_XSS_T "LibInjectionXss"
/* nginx-style names */
#define TOP_DENIED_URL_N "denied_url"
#define TOP_LEARNING_FLAG_N "learning_mode"
#define TOP_ENABLED_FLAG_N "rules_enabled"
#define TOP_DISABLED_FLAG_N "rules_disabled"
#define TOP_CHECK_RULE_N "check_rule"
#define TOP_BASIC_RULE_N "basic_rule"
#define TOP_MAIN_BASIC_RULE_N "main_rule"
#define TOP_LIBINJECTION_SQL_N "libinjection_sql"
#define TOP_LIBINJECTION_XSS_N "libinjection_xss"
/*possible 'tokens' in rule */
#define ID_T "id:"
#define SCORE_T "s:"
#define MSG_T "msg:"
#define RX_T "rx:"
#define STR_T "str:"
#define MATCH_ZONE_T "mz:"
#define WHITELIST_T "wl:"
#define LIBINJ_XSS_T "d:libinj_xss"
#define LIBINJ_SQL_T "d:libinj_sql"
#define NEGATIVE_T "negative"
/*
** name of hardcoded variables to
** change behavior of naxsi at runtime
*/
#define RT_EXTENSIVE_LOG "naxsi_extensive_log"
#define RT_ENABLE "naxsi_flag_enable"
#define RT_LEARNING "naxsi_flag_learning"
#define RT_POST_ACTION "naxsi_flag_post_action"
#define RT_LIBINJECTION_SQL "naxsi_flag_libinjection_sql"
#define RT_LIBINJECTION_XSS "naxsi_flag_libinjection_xss"
/*
** To avoid getting DoS'ed, define max depth
** for JSON parser, as it is recursive
*/
#define JSON_MAX_DEPTH 10
void *ngx_http_dummy_cfg_parse_one_rule(ngx_conf_t *cf,
ngx_str_t *value,
ngx_http_rule_t *rule,
ngx_int_t nb_elem);
char *strfaststr(unsigned char *haystack, unsigned int hl,
unsigned char *needle, unsigned int nl);
char *strnchr(const char *s, int c, int len);
ngx_int_t ngx_http_dummy_create_hashtables_n(ngx_http_dummy_loc_conf_t *dlc,
ngx_conf_t *cf);
void ngx_http_dummy_data_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r);
ngx_int_t ngx_http_output_forbidden_page(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r);
int nx_check_ids(ngx_int_t match_id, ngx_array_t *wl_ids);
int naxsi_unescape(ngx_str_t *str);
void ngx_http_dummy_json_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r,
u_char *src,
u_int len);
void ngx_http_libinjection(ngx_pool_t *pool,
ngx_str_t *name,
ngx_str_t *value,
ngx_http_request_ctx_t *ctx,
ngx_http_request_t *req,
enum DUMMY_MATCH_ZONE zone);
/*
** JSON parsing prototypes.
*/
ngx_int_t ngx_http_nx_json_forward(ngx_json_t *js) ;
ngx_int_t ngx_http_nx_json_seek(ngx_json_t *js, unsigned char seek);
ngx_int_t ngx_http_nx_json_quoted(ngx_json_t *js, ngx_str_t *ve);
ngx_int_t ngx_http_nx_json_array(ngx_json_t *js);
ngx_int_t ngx_http_nx_json_val(ngx_json_t *js);
ngx_int_t ngx_http_nx_json_obj(ngx_json_t *js);
/*
** naxsi_runtime
**
*/
void ngx_http_dummy_update_current_ctx_status(ngx_http_request_ctx_t *ctx,
ngx_http_dummy_loc_conf_t *cf,
ngx_http_request_t *r);
int ngx_http_process_basic_rule_buffer(ngx_str_t *str, ngx_http_rule_t *rl,
ngx_int_t *match);
void ngx_http_dummy_payload_handler(ngx_http_request_t *r);
int ngx_http_basestr_ruleset_n(ngx_pool_t *pool,
ngx_str_t *name,
ngx_str_t *value,
ngx_array_t *rules,
ngx_http_request_t *req,
ngx_http_request_ctx_t *ctx,
enum DUMMY_MATCH_ZONE zone);
void ngx_http_dummy_body_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r,
ngx_http_dummy_loc_conf_t *cf,
ngx_http_dummy_main_conf_t *main_cf);
void naxsi_log_offending(ngx_str_t *name, ngx_str_t *val, ngx_http_request_t *req,
ngx_http_rule_t *rule, enum DUMMY_MATCH_ZONE zone, ngx_int_t target_name);
int ngx_http_apply_rulematch_v_n(ngx_http_rule_t *r, ngx_http_request_ctx_t *ctx,
ngx_http_request_t *req, ngx_str_t *name,
ngx_str_t *value, enum DUMMY_MATCH_ZONE zone,
ngx_int_t nb_match, ngx_int_t target_name);
/*
** externs for internal rules that requires it.
*/
extern ngx_http_rule_t *nx_int__libinject_sql;
extern ngx_http_rule_t *nx_int__libinject_xss;
/*libinjection_xss wrapper not exported by libinject_xss.h.*/
int libinjection_xss(const char* s, size_t len);
#endif

View File

@@ -0,0 +1,593 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "naxsi.h"
/*
** TOP LEVEL configuration parsing code
*/
/*
** code to parse FLAGS and OPTIONS on each line.
*/
void *dummy_id(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_score(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_msg(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_rx(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_zone(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_str(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_negative(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_libinj_xss(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_libinj_sql(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
void *dummy_whitelist(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule);
/*
** Structures related to the configuration parser
*/
typedef struct {
char *prefix;
void *(*pars)(ngx_conf_t *, ngx_str_t *, ngx_http_rule_t *);
} ngx_http_dummy_parser_t;
static ngx_http_dummy_parser_t rule_parser[] = {
{ID_T, dummy_id},
{SCORE_T, dummy_score},
{MSG_T, dummy_msg},
{RX_T, dummy_rx},
{STR_T, dummy_str},
{LIBINJ_XSS_T, dummy_libinj_xss},
{LIBINJ_SQL_T, dummy_libinj_sql},
{MATCH_ZONE_T, dummy_zone},
{NEGATIVE_T, dummy_negative},
{WHITELIST_T, dummy_whitelist},
{NULL, NULL}
};
void *
dummy_negative(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
rule->br->negative = 1;
return (NGX_CONF_OK);
}
void *
dummy_libinj_xss(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
rule->br->match_type = LIBINJ_XSS;
return (NGX_CONF_OK);
}
void *
dummy_libinj_sql(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
rule->br->match_type = LIBINJ_SQL;
return (NGX_CONF_OK);
}
void *
dummy_score(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
int score, len;
char *tmp_ptr, *tmp_end;
ngx_http_special_score_t *sc;
rule->score = 0;
rule->block = 0;
rule->allow = 0;
rule->drop = 0;
tmp_ptr = (char *) (tmp->data + strlen(SCORE_T));
NX_LOG_DEBUG(_debug_score, NGX_LOG_EMERG, r, 0,
"XX-(debug) dummy score (%V)",
tmp);
/*allocate scores array*/
if (!rule->sscores) {
rule->sscores = ngx_array_create(r->pool, 1, sizeof(ngx_http_special_score_t));
}
while (*tmp_ptr) {
if (tmp_ptr[0] == '$') {
NX_LOG_DEBUG(_debug_score, NGX_LOG_EMERG, r, 0,
"XX-(debug) special scoring rule (%s)",
tmp_ptr);
tmp_end = strchr(tmp_ptr, ':');
if (!tmp_end)
return (NGX_CONF_ERROR);
len = tmp_end - tmp_ptr;
if (len <= 0)
return (NGX_CONF_ERROR);
sc = ngx_array_push(rule->sscores);
if (!sc)
return (NGX_CONF_ERROR);
sc->sc_tag = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
if (!sc->sc_tag)
return (NGX_CONF_ERROR);
sc->sc_tag->data = ngx_pcalloc(r->pool, len+1);
if (!sc->sc_tag->data)
return (NGX_CONF_ERROR);
//memset(rule->sc_tag->data, 0, len+1);
memcpy(sc->sc_tag->data, tmp_ptr, len);
sc->sc_tag->len = len;
sc->sc_score = atoi(tmp_end+1);
NX_LOG_DEBUG(_debug_score, NGX_LOG_EMERG, r, 0,
"XX-(debug) special scoring (%V) => (%d)",
sc->sc_tag, sc->sc_score);
/* move to end of score. */
while ( /*don't overflow*/((unsigned int)((unsigned char *)tmp_ptr - tmp->data)) < tmp->len &&
/*and seek for next score */ *tmp_ptr != ',')
++tmp_ptr;
}
else if (tmp_ptr[0] == ',')
++tmp_ptr;
else if (!strcasecmp(tmp_ptr, "BLOCK")) {
rule->block = 1;
tmp_ptr += 5;
}
else if (!strcasecmp(tmp_ptr, "DROP")) {
rule->drop = 1;
tmp_ptr += 4;
}
else if (!strcasecmp(tmp_ptr, "ALLOW")) {
rule->allow = 1;
tmp_ptr += 5;
}
else if (!strcasecmp(tmp_ptr, "LOG")) {
rule->log = 1;
tmp_ptr += 3;
}
//or maybe you just want to assign a score
else if ( (tmp_ptr[0] >= '0' && tmp_ptr[0] <= '9') || tmp_ptr[0] == '-') {
score = atoi((const char *)tmp->data+2);
rule->score = score;
break;
}
else
return (NGX_CONF_ERROR);
}
#if defined(_debug_score) && _debug_score != 0
unsigned int z;
ngx_http_special_score_t *scr;
scr = rule->sscores->elts;
if (rule->sscores) {
for (z = 0; z < rule->sscores->nelts; z++) {
ngx_conf_log_error(NGX_LOG_EMERG, r, 0,
"XX-score n°%d special scoring (%V) => (%d)",
z, scr[z].sc_tag, scr[z].sc_score);
}
}
else
ngx_conf_log_error(NGX_LOG_EMERG, r, 0,
"XX-no custom scores for this rule.");
#endif
return (NGX_CONF_OK);
}
void *
dummy_zone(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
int tmp_len, has_zone=0;
ngx_http_custom_rule_location_t *custom_rule;
char *tmp_ptr, *tmp_end;
if (!rule->br)
return (NGX_CONF_ERROR);
tmp_ptr = (char *) tmp->data+strlen(MATCH_ZONE_T);
while (*tmp_ptr) {
if (tmp_ptr[0] == '|')
tmp_ptr++;
/* match global zones */
if (!strncmp(tmp_ptr, "RAW_BODY", strlen("RAW_BODY"))) {
rule->br->raw_body = 1;
tmp_ptr += strlen("RAW_BODY");
has_zone = 1;
continue;
}
else
if (!strncmp(tmp_ptr, "BODY", strlen("BODY"))) {
rule->br->body = 1;
tmp_ptr += strlen("BODY");
has_zone = 1;
continue;
}
else
if (!strncmp(tmp_ptr, "HEADERS", strlen("HEADERS"))) {
rule->br->headers = 1;
tmp_ptr += strlen("HEADERS");
has_zone = 1;
continue;
}
else
if (!strncmp(tmp_ptr, "URL", strlen("URL"))) {
rule->br->url = 1;
tmp_ptr += strlen("URL");
has_zone = 1;
continue;
}
else
if (!strncmp(tmp_ptr, "ARGS", strlen("ARGS"))) {
rule->br->args = 1;
tmp_ptr += strlen("ARGS");
has_zone = 1;
continue;
}
else
/* match against variable name*/
if (!strncmp(tmp_ptr, "NAME", strlen("NAME"))) {
rule->br->target_name = 1;
tmp_ptr += strlen("NAME");
has_zone = 1;
continue;
}
else
/* for file_ext, just push'em in the body rules.
when multipart parsing comes in, it'll tag the zone as
FILE_EXT as the rule will be pushed in body rules it'll be
checked !*/
if (!strncmp(tmp_ptr, "FILE_EXT", strlen("FILE_EXT"))) {
rule->br->file_ext = 1;
rule->br->body = 1;
tmp_ptr += strlen("FILE_EXT");
has_zone = 1;
continue;
}
else
/* custom match zones */
#define MZ_GET_VAR_T "$ARGS_VAR:"
#define MZ_HEADER_VAR_T "$HEADERS_VAR:"
#define MZ_POST_VAR_T "$BODY_VAR:"
#define MZ_SPECIFIC_URL_T "$URL:"
//probably a custom zone
if (tmp_ptr[0] == '$') {
// tag as a custom_location rule.
rule->br->custom_location = 1;
if (!rule->br->custom_locations) {
rule->br->custom_locations = ngx_array_create(r->pool, 1,
sizeof(ngx_http_custom_rule_location_t));
if (!rule->br->custom_locations)
return (NGX_CONF_ERROR);
}
custom_rule = ngx_array_push(rule->br->custom_locations);
if (!custom_rule)
return (NGX_CONF_ERROR);
memset(custom_rule, 0, sizeof(ngx_http_custom_rule_location_t));
if (!strncmp(tmp_ptr, MZ_GET_VAR_T, strlen(MZ_GET_VAR_T))) {
has_zone = 1;
custom_rule->args_var = 1;
rule->br->args_var = 1;
tmp_ptr += strlen(MZ_GET_VAR_T);
}
else if (!strncmp(tmp_ptr, MZ_POST_VAR_T,
strlen(MZ_POST_VAR_T))) {
has_zone = 1;
custom_rule->body_var = 1;
rule->br->body_var = 1;
tmp_ptr += strlen(MZ_POST_VAR_T);
}
else if (!strncmp(tmp_ptr, MZ_HEADER_VAR_T,
strlen(MZ_HEADER_VAR_T))) {
has_zone = 1;
custom_rule->headers_var = 1;
rule->br->headers_var = 1;
tmp_ptr += strlen(MZ_HEADER_VAR_T);
}
else if (!strncmp(tmp_ptr, MZ_SPECIFIC_URL_T,
strlen(MZ_SPECIFIC_URL_T))) {
custom_rule->specific_url = 1;
tmp_ptr += strlen(MZ_SPECIFIC_URL_T);
}
else
/* add support for regex-style match zones.
** this whole function should be rewritten as it's getting
** messy as hell
*/
#define MZ_GET_VAR_X "$ARGS_VAR_X:"
#define MZ_HEADER_VAR_X "$HEADERS_VAR_X:"
#define MZ_POST_VAR_X "$BODY_VAR_X:"
#define MZ_SPECIFIC_URL_X "$URL_X:"
/*
** if the rule is a negative rule (has an ID, not a WL field)
** we need to pre-compile the regex for runtime.
** Don't do it for whitelists, as its done in a separate manner.
*/
if (!strncmp(tmp_ptr, MZ_GET_VAR_X, strlen(MZ_GET_VAR_X))) {
has_zone = 1;
custom_rule->args_var = 1;
rule->br->args_var = 1;
rule->br->rx_mz = 1;
tmp_ptr += strlen(MZ_GET_VAR_X);
}
else if (!strncmp(tmp_ptr, MZ_POST_VAR_X,
strlen(MZ_POST_VAR_X))) {
has_zone = 1;
rule->br->rx_mz = 1;
custom_rule->body_var = 1;
rule->br->body_var = 1;
tmp_ptr += strlen(MZ_POST_VAR_X);
}
else if (!strncmp(tmp_ptr, MZ_HEADER_VAR_X,
strlen(MZ_HEADER_VAR_X))) {
has_zone = 1;
custom_rule->headers_var = 1;
rule->br->headers_var = 1;
rule->br->rx_mz = 1;
tmp_ptr += strlen(MZ_HEADER_VAR_X);
}
else if (!strncmp(tmp_ptr, MZ_SPECIFIC_URL_X,
strlen(MZ_SPECIFIC_URL_X))) {
custom_rule->specific_url = 1;
rule->br->rx_mz = 1;
tmp_ptr += strlen(MZ_SPECIFIC_URL_X);
}
else
return (NGX_CONF_ERROR);
/* else
return (NGX_CONF_ERROR);*/
tmp_end = strchr((const char *) tmp_ptr, '|');
if (!tmp_end)
tmp_end = tmp_ptr + strlen(tmp_ptr);
tmp_len = tmp_end - tmp_ptr;
if (tmp_len <= 0)
return (NGX_CONF_ERROR);
custom_rule->target.data = ngx_pcalloc(r->pool, tmp_len+1);
if (!custom_rule->target.data)
return (NGX_CONF_ERROR);
custom_rule->target.len = tmp_len;
memcpy(custom_rule->target.data, tmp_ptr, tmp_len);
/*
** pre-compile regex !
*/
if (rule->br->rx_mz == 1) {
custom_rule->target_rx = ngx_pcalloc(r->pool, sizeof(ngx_regex_compile_t));
if (!custom_rule->target_rx)
return (NGX_CONF_ERROR);
custom_rule->target_rx->options = PCRE_CASELESS|PCRE_MULTILINE;
custom_rule->target_rx->pattern = custom_rule->target;
custom_rule->target_rx->pool = r->pool;
custom_rule->target_rx->err.len = 0;
custom_rule->target_rx->err.data = NULL;
if (ngx_regex_compile(custom_rule->target_rx) != NGX_OK) {
NX_LOG_DEBUG(_debug_rx, NGX_LOG_EMERG, r, 0, "XX-FAILED RX:%V",
custom_rule->target);
return (NGX_CONF_ERROR);
}
}
custom_rule->hash = ngx_hash_key_lc(custom_rule->target.data,
custom_rule->target.len);
NX_LOG_DEBUG(_debug_zone, NGX_LOG_EMERG, r, 0, "XX- ZONE:[%V]",
&(custom_rule->target));
tmp_ptr += tmp_len;
continue;
}
else
return (NGX_CONF_ERROR);
}
/*
** ensure the match-zone actually returns a zone :)
*/
if (has_zone == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, r, 0,
"matchzone doesn't target an actual zone.");
return (NGX_CONF_ERROR);
}
return (NGX_CONF_OK);
}
void *
dummy_id(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
rule->rule_id = atoi((const char *) tmp->data+strlen(ID_T));
return (NGX_CONF_OK);
}
void *
dummy_str(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
ngx_str_t *str;
uint i;
if (!rule->br)
return (NGX_CONF_ERROR);
rule->br->match_type = STR;
str = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
if (!str)
return (NGX_CONF_ERROR);
str->data = tmp->data + strlen(STR_T);
str->len = tmp->len - strlen(STR_T);
for (i = 0; i < str->len; i++)
str->data[i] = tolower(str->data[i]);
rule->br->str = str;
return (NGX_CONF_OK);
}
void *
dummy_msg(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
ngx_str_t *str;
if (!rule->br)
return (NGX_CONF_ERROR);
str = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
if (!str)
return (NGX_CONF_ERROR);
str->data = tmp->data + strlen(STR_T);
str->len = tmp->len - strlen(STR_T);
rule->log_msg = str;
return (NGX_CONF_OK);
}
void *
dummy_whitelist(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
ngx_array_t *wl_ar;
unsigned int i, ct;
ngx_int_t *id;
ngx_str_t str;
str.data = tmp->data + strlen(WHITELIST_T);
str.len = tmp->len - strlen(WHITELIST_T);
for (ct = 1, i = 0; i < str.len; i++)
if (str.data[i] == ',')
ct++;
wl_ar = ngx_array_create(r->pool, ct, sizeof(ngx_int_t));
if (!wl_ar)
return (NGX_CONF_ERROR);
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, r, 0, "XX- allocated %d elems for WL", ct);
for (i = 0; i < str.len; i++) {
if (i == 0 || str.data[i-1] == ',') {
id = (ngx_int_t *) ngx_array_push(wl_ar);
if (!id)
return (NGX_CONF_ERROR);
*id = (ngx_int_t) atoi((const char *)str.data+i);
}
}
rule->wlid_array = wl_ar;
return (NGX_CONF_OK);
}
void *
dummy_rx(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule)
{
ngx_regex_compile_t *rgc;
ngx_str_t ha;
if (!rule->br)
return (NGX_CONF_ERROR);
rule->br->match_type = RX;
//just prepare a string to hold the directive without 'rx:'
ha.data = tmp->data+strlen(RX_T);
ha.len = tmp->len-strlen(RX_T);
rgc = ngx_pcalloc(r->pool, sizeof(ngx_regex_compile_t));
if (!rgc)
return (NGX_CONF_ERROR);
rgc->options = PCRE_CASELESS|PCRE_MULTILINE;
rgc->pattern = ha;
rgc->pool = r->pool;
rgc->err.len = 0;
rgc->err.data = NULL;
if (ngx_regex_compile(rgc) != NGX_OK) {
NX_LOG_DEBUG(_debug_rx, NGX_LOG_EMERG, r, 0, "XX-FAILED RX:%V",
tmp);
return (NGX_CONF_ERROR);
}
rule->br->rx = rgc;
NX_LOG_DEBUG(_debug_rx, NGX_LOG_EMERG, r, 0, "XX- RX:[%V]",
&(rule->br->rx->pattern));
return (NGX_CONF_OK);
}
/* Parse one rule line */
/*
** in : nb elem, value array, rule to fill
** does : creates a rule struct from configuration line
** For each element name matching a tag
** (cf. rule_parser), then call the associated func.
*/
void *
ngx_http_dummy_cfg_parse_one_rule(ngx_conf_t *cf,
ngx_str_t *value,
ngx_http_rule_t *current_rule,
ngx_int_t nb_elem)
{
int i, z;
void *ret;
int valid;
if (!value || !value[0].data)
return NGX_CONF_ERROR;
/*
** parse basic rule
*/
if (!ngx_strcmp(value[0].data, TOP_CHECK_RULE_T) ||
!ngx_strcmp(value[0].data, TOP_CHECK_RULE_N) ||
!ngx_strcmp(value[0].data, TOP_BASIC_RULE_T) ||
!ngx_strcmp(value[0].data, TOP_BASIC_RULE_N) ||
!ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_T) ||
!ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_N)) {
NX_LOG_DEBUG(_debug_cfg_parse_one_rule, NGX_LOG_EMERG, cf, 0, "naxsi-basic rule %V", &(value[1]));
current_rule->type = BR;
current_rule->br = ngx_pcalloc(cf->pool, sizeof(ngx_http_basic_rule_t));
if (!current_rule->br)
return (NGX_CONF_ERROR);
}
else {
NX_LOG_DEBUG(_debug_cfg_parse_one_rule, NGX_LOG_EMERG, cf, 0,
"Unknown start keyword in rule %V", &(value[1]));
return (NGX_CONF_ERROR);
}
// check each word of config line against each rule
for(i = 1; i < nb_elem && value[i].len > 0; i++) {
valid = 0;
for (z = 0; rule_parser[z].pars; z++) {
if (!ngx_strncmp(value[i].data,
rule_parser[z].prefix,
strlen(rule_parser[z].prefix))) {
ret = rule_parser[z].pars(cf, &(value[i]),
current_rule);
if (ret != NGX_CONF_OK) {
NX_LOG_DEBUG(_debug_cfg_parse_one_rule, NGX_LOG_EMERG, cf, 0,
"XX-FAILED PARSING '%s'",
value[i].data);
return (ret);
}
valid = 1;
}
}
if (!valid)
return (NGX_CONF_ERROR);
}
/* validate the structure, and fill empty fields.*/
if (!current_rule->log_msg)
{
current_rule->log_msg = ngx_pcalloc(cf->pool, sizeof(ngx_str_t));
current_rule->log_msg->data = NULL;
current_rule->log_msg->len = 0;
}
return (NGX_CONF_OK);
}

View File

@@ -0,0 +1,347 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "naxsi.h"
//#define _debug_json 1
ngx_http_rule_t nx_int__invalid_json = {/*type*/ 0, /*whitelist flag*/ 0,
/*wl_id ptr*/ NULL, /*rule_id*/ 15,
/*log_msg*/ NULL, /*score*/ 0,
/*sscores*/ NULL,
/*sc_block*/ 1, /*sc_allow*/ 0,
/*block*/ 1, /*allow*/ 0, /*drop*/ 0, /*log*/ 0,
/*br ptrs*/ NULL};
ngx_int_t
ngx_http_nx_json_forward(ngx_json_t *js)
{
while ((*(js->src+js->off) == ' ' ||
*(js->src+js->off) == '\t' ||
*(js->src+js->off) == '\n' ||
*(js->src+js->off) == '\r') && js->off < js->len) {
js->off++;
}
js->c = *(js->src + js->off);
return (NGX_OK);
}
/*
** used to fast forward in json POSTS,
** we skip whitespaces/tab/CR/LF
*/
ngx_int_t
ngx_http_nx_json_seek(ngx_json_t *js, unsigned char seek)
{
ngx_http_nx_json_forward(js);
if (js->c != seek)
return (NGX_ERROR);
return (NGX_OK);
}
/*
** extract a quoted strings,
** JSON spec only supports double-quoted strings,
** so do we.
*/
ngx_int_t
ngx_http_nx_json_quoted(ngx_json_t *js, ngx_str_t *ve)
{
u_char *vn_start, *vn_end;
vn_start = vn_end = NULL;
if (*(js->src+js->off) != '"')
return (NGX_ERROR);
js->off++;
vn_start = js->src+js->off;
/* extract varname inbetween "..."*/
while (js->off < js->len) {
/* skip next character if backslashed */
if (*(js->src+js->off) == '\\') {
js->off += 2;
if (js->off >= js->len) break;
}
if (*(js->src+js->off) == '"') {
vn_end = js->src+js->off;
js->off++;
break;
}
js->off++;
}
if (!vn_start || !vn_end)
return (NGX_ERROR);
if (!*vn_start || !*vn_end)
return (NGX_ERROR);
ve->data = vn_start;
ve->len = vn_end - vn_start;
return (NGX_OK);
}
/*
** an array is values separated by ','
*/
ngx_int_t
ngx_http_nx_json_array(ngx_json_t *js) {
ngx_int_t rc;
js->c = *(js->src + js->off);
if (js->c != '[' || js->depth > JSON_MAX_DEPTH)
return (NGX_ERROR);
js->off++;
do {
rc = ngx_http_nx_json_val(js);
/* if we cannot extract the value,
we may have reached array end. */
if (rc != NGX_OK)
break;
ngx_http_nx_json_forward(js);
if (js->c == ',') {
js->off++;
ngx_http_nx_json_forward(js);
} else break;
} while (rc == NGX_OK);
if (js->c != ']')
return (NGX_ERROR);
return (NGX_OK);
}
ngx_int_t
ngx_http_nx_json_val(ngx_json_t *js) {
ngx_str_t val;
ngx_int_t ret;
ngx_str_t empty = ngx_string("");
val.data = NULL;
val.len = 0;
ngx_http_nx_json_forward(js);
if (js->c == '"') {
ret = ngx_http_nx_json_quoted(js, &val);
if (ret == NGX_OK)
{
/* parse extracted values. */
if (js->loc_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->loc_cf->body_rules, js->r, js->ctx,
BODY);
if (js->main_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->main_cf->body_rules, js->r, js->ctx,
BODY);
NX_DEBUG(_debug_json, NGX_LOG_DEBUG_HTTP, js->r->connection->log, 0, "JSON '%V' : '%V'",
&(js->ckey), &(val));
}
return (ret);
}
if ((js->c >= '0' && js->c <= '9') || js->c == '-') {
val.data = js->src+js->off;
while ( ((*(js->src+js->off) >= '0' && *(js->src+js->off) <= '9') ||
*(js->src+js->off) == '.' || *(js->src+js->off) == '-') && js->off < js->len) {
val.len++;
js->off++;
}
/* parse extracted values. */
if (js->loc_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->loc_cf->body_rules, js->r, js->ctx,
BODY);
if (js->main_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->main_cf->body_rules, js->r, js->ctx,
BODY);
NX_DEBUG(_debug_json, NGX_LOG_DEBUG_HTTP, js->r->connection->log, 0, "JSON '%V' : '%V'",
&(js->ckey), &(val));
return (NGX_OK);
}
if (!strncasecmp((const char *) (js->src + js->off), (const char *) "true", 4) ||
!strncasecmp((const char *) (js->src + js->off), (const char *) "false", 5) ||
!strncasecmp((const char *) (js->src + js->off), (const char *) "null", 4)) {
js->c = *(js->src + js->off);
/* we don't check static values, do we ?! */
val.data = js->src+js->off;
if (js->c == 'F' || js->c == 'f') {
js->off += 5;
val.len = 5;
}
else {
js->off += 4;
val.len = 4;
}
/* parse extracted values. */
if (js->loc_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->loc_cf->body_rules, js->r, js->ctx,
BODY);
if (js->main_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &val,
js->main_cf->body_rules, js->r, js->ctx,
BODY);
NX_DEBUG(_debug_json, NGX_LOG_DEBUG_HTTP, js->r->connection->log, 0, "JSON '%V' : '%V'",
&(js->ckey), &(val));
return (NGX_OK);
}
if (js->c == '[') {
ret = ngx_http_nx_json_array(js);
if (js->c != ']')
return (NGX_ERROR);
js->off++;
return (ret);
}
if (js->c == '{') {
/*
** if sub-struct, parse key without value :
** "foobar" : { "bar" : [1,2,3]} => "foobar" parsed alone.
** this is to avoid "foobar" left unparsed, as we won't have
** key/value here with "foobar" as a key.
*/
if (js->loc_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &empty,
js->loc_cf->body_rules, js->r, js->ctx,
BODY);
if (js->main_cf->body_rules)
ngx_http_basestr_ruleset_n(js->r->pool, &js->ckey, &empty,
js->main_cf->body_rules, js->r, js->ctx,
BODY);
ret = ngx_http_nx_json_obj(js);
ngx_http_nx_json_forward(js);
if (js->c != '}')
return (NGX_ERROR);
js->off++;
return (ret);
}
return (NGX_ERROR);
}
ngx_int_t
ngx_http_nx_json_obj(ngx_json_t *js)
{
js->c = *(js->src + js->off);
if (js->c != '{' || js->depth > JSON_MAX_DEPTH)
return (NGX_ERROR);
js->off++;
do {
ngx_http_nx_json_forward(js);
/* check subs (arrays, objects) */
switch (js->c) {
case '[': /* array */
js->depth++;
ngx_http_nx_json_array(js);
if (ngx_http_nx_json_seek(js, ']'))
return (NGX_ERROR);
js->off++;
js->depth--;
break;
case '{': /* sub-object */
js->depth++;
ngx_http_nx_json_obj(js);
if (js->c != '}')
return (NGX_ERROR);
js->off++;
js->depth--;
break;
case '"': /* key : value, extract and parse. */
if (ngx_http_nx_json_quoted(js, &(js->ckey)) != NGX_OK)
return (NGX_ERROR);
if (ngx_http_nx_json_seek(js, ':'))
return (NGX_ERROR);
js->off++;
ngx_http_nx_json_forward(js);
if (ngx_http_nx_json_val(js) != NGX_OK)
return (NGX_ERROR);
}
ngx_http_nx_json_forward(js);
/* another element ? */
if (js->c == ',') {
js->off++;
ngx_http_nx_json_forward(js);
continue;
} else if (js->c == '}') {
js->depth--;
/* or maybe we just finished parsing this object */
return (NGX_OK);
} else {
/* nothing we expected, die. */
return (NGX_ERROR);
}
} while (js->off < js->len);
return (NGX_ERROR);
}
/*
** Parse a JSON request
*/
void
ngx_http_dummy_json_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r,
u_char *src,
u_int len)
{
ngx_json_t *js;
js = ngx_pcalloc(r->pool, sizeof(ngx_json_t));
if (!js) return ;
js->json.data = js->src = src;
js->json.len = js->len = len;
js->r = r;
js->ctx = ctx;
js->loc_cf = ngx_http_get_module_loc_conf(r, ngx_http_naxsi_module);
js->main_cf = ngx_http_get_module_main_conf(r, ngx_http_naxsi_module);
if (ngx_http_nx_json_seek(js, '{')) {
ngx_http_apply_rulematch_v_n(&nx_int__invalid_json, ctx, r, NULL, NULL, BODY, 1, 0);
return ;
}
if (ngx_http_nx_json_obj(js) != NGX_OK) {
ngx_http_apply_rulematch_v_n(&nx_int__invalid_json, ctx, r, NULL, NULL, BODY, 1, 0);
NX_DEBUG(_debug_json, NGX_LOG_DEBUG_HTTP, js->r->connection->log, 0, "nx_json_obj returned error, apply invalid_json.");
}
/* we are now on closing bracket, check for garbage. */
js->off++;
ngx_http_nx_json_forward(js);
if (js->off != js->len)
ngx_http_apply_rulematch_v_n(&nx_int__invalid_json, ctx, r, NULL, NULL, BODY, 1, 0);
return ;
}

View File

@@ -0,0 +1,70 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "naxsi.h"
void
ngx_http_dummy_rawbody_parse(ngx_http_request_ctx_t *ctx,
ngx_http_request_t *r,
u_char *src,
u_int len)
{
ngx_http_dummy_loc_conf_t *cf;
ngx_str_t body;
ngx_http_dummy_main_conf_t *main_cf;
ngx_str_t empty = ngx_string("");
NX_DEBUG(_debug_rawbody, NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "XX-RAWBODY CALLED len:%d",len);
if (len <= 0 || !src)
return;
cf = ngx_http_get_module_loc_conf(r, ngx_http_naxsi_module);
main_cf = ngx_http_get_module_main_conf(r, ngx_http_naxsi_module);
body.data = src;
body.len = len;
naxsi_unescape(&body);
/* here we got val name + val content !*/
if (cf->raw_body_rules) {
NX_DEBUG(_debug_rawbody, NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "XX-(local) RAW BODY RULES");
ngx_http_basestr_ruleset_n(r->pool, &empty, &body,
cf->raw_body_rules, r, ctx, BODY);
}
if (main_cf->raw_body_rules) {
NX_DEBUG(_debug_rawbody, NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "XX-(global) RAW BODY RULES");
ngx_http_basestr_ruleset_n(r->pool, &empty, &body,
main_cf->raw_body_rules, r, ctx, BODY);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,826 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "naxsi.h"
static int naxsi_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);
char *
strnchr(const char *s, int c, int len)
{
int cpt;
for (cpt = 0; cpt < len && s[cpt]; cpt++)
if (s[cpt] == c)
return ((char *) s+cpt);
return (NULL);
}
static char *
strncasechr(const char *s, int c, int len)
{
int cpt;
for (cpt = 0; cpt < len && s[cpt]; cpt++)
if (tolower(s[cpt]) == c)
return ((char *) s+cpt);
return (NULL);
}
/*
** strstr: faster, stronger, harder
** (because strstr from libc is very slow)
*/
char *
strfaststr(unsigned char *haystack, unsigned int hl,
unsigned char *needle, unsigned int nl)
{
char *cpt, *found, *end;
if (hl < nl || !haystack || !needle || !nl || !hl) return (NULL);
cpt = (char *) haystack;
end = (char *) haystack + hl;
while (cpt < end) {
found = strncasechr((const char *) cpt, (int) needle[0], hl);
if (!found) return (NULL);
if (nl == 1) return (found);
if (!strncasecmp((const char *)found+1, (const char *) needle+1, nl-1))
return ((char *) found);
else {
if (found+nl >= end)
break;
if (found+nl < end) {
/* the haystack is shrinking */
cpt = found+1;
hl = (unsigned int) (end - cpt);
}
}
}
return (NULL);
}
/* unescape routine, returns number of nullbytes present */
int naxsi_unescape(ngx_str_t *str) {
u_char *dst, *src;
u_int nullbytes = 0, bad = 0, i;
dst = str->data;
src = str->data;
bad = naxsi_unescape_uri(&src, &dst,
str->len, 0);
str->len = src - str->data;
//tmp hack fix, avoid %00 & co (null byte) encoding :p
for (i = 0; i < str->len; i++)
if (str->data[i] == 0x0)
{
nullbytes++;
str->data[i] = '0';
}
return (nullbytes+bad);
}
/*
** Patched ngx_unescape_uri :
** The original one does not care if the character following % is in valid range.
** For example, with the original one :
** '%uff' -> 'uff'
*/
static int
naxsi_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type)
{
u_char *d, *s, ch, c, decoded;
int bad = 0;
enum {
sw_usual = 0,
sw_quoted,
sw_quoted_second
} state;
d = *dst;
s = *src;
state = 0;
decoded = 0;
while (size--) {
ch = *s++;
switch (state) {
case sw_usual:
if (ch == '%') {
state = sw_quoted;
break;
}
*d++ = ch;
break;
case sw_quoted:
if (ch >= '0' && ch <= '9') {
decoded = (u_char) (ch - '0');
state = sw_quoted_second;
break;
}
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'f') {
decoded = (u_char) (c - 'a' + 10);
state = sw_quoted_second;
break;
}
/* the invalid quoted character */
bad++;
state = sw_usual;
*d++ = '%';
*d++ = ch;
break;
case sw_quoted_second:
state = sw_usual;
if (ch >= '0' && ch <= '9') {
ch = (u_char) ((decoded << 4) + ch - '0');
*d++ = ch;
break;
}
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'f') {
ch = (u_char) ((decoded << 4) + c - 'a' + 10);
*d++ = ch;
break;
}
/* the invalid quoted character */
/* as it happened in the 2nd part of quoted character,
we need to restore the decoded char as well. */
*d++ = '%';
*d++ = *(s - 2);
*d++ = *(s - 1);
bad++;
break;
}
}
*dst = d;
*src = s;
return (bad);
}
/* push rule into disabled rules. */
static ngx_int_t
ngx_http_wlr_push_disabled(ngx_conf_t *cf, ngx_http_dummy_loc_conf_t *dlc,
ngx_http_rule_t *curr) {
ngx_http_rule_t **dr;
if (!dlc->disabled_rules)
dlc->disabled_rules = ngx_array_create(cf->pool, 4,
sizeof(ngx_http_rule_t *));
if (!dlc->disabled_rules)
return (NGX_ERROR);
dr = ngx_array_push(dlc->disabled_rules);
if (!dr)
return (NGX_ERROR);
*dr = (ngx_http_rule_t *) curr;
return (NGX_OK);
}
/* merge the two rules into father_wl, meaning
ids. Not locations, as we are getting rid of it */
static ngx_int_t
ngx_http_wlr_merge(ngx_conf_t *cf, ngx_http_whitelist_rule_t *father_wl,
ngx_http_rule_t *curr) {
uint i;
ngx_int_t *tmp_ptr;
NX_LOG_DEBUG(_debug_whitelist,
NGX_LOG_EMERG, cf, 0, "[naxsi] merging similar wl(s)");
if (!father_wl->ids)
{
father_wl->ids = ngx_array_create(cf->pool, 3, sizeof(ngx_int_t));
if (!father_wl->ids)
return (NGX_ERROR);
}
for (i = 0; i < curr->wlid_array->nelts; i++) {
tmp_ptr = ngx_array_push(father_wl->ids);
if (!tmp_ptr)
return (NGX_ERROR);
*tmp_ptr = ((ngx_int_t *)curr->wlid_array->elts)[i];
//*tmp_ptr = curr->wlid_array->elts[i];
}
return (NGX_OK);
}
/*check rule, returns associed zone, as well as location index.
location index refers to $URL:bla or $ARGS_VAR:bla */
#define custloc_array(x) ((ngx_http_custom_rule_location_t *) x)
static ngx_int_t
ngx_http_wlr_identify(ngx_conf_t *cf, ngx_http_dummy_loc_conf_t *dlc,
ngx_http_rule_t *curr, int *zone,
int *uri_idx, int *name_idx) {
uint i;
/*
identify global match zones (|ARGS|BODY|HEADERS|URL|FILE_EXT)
*/
if (curr->br->body || curr->br->body_var)
*zone = BODY;
else if (curr->br->headers || curr->br->headers_var)
*zone = HEADERS;
else if (curr->br->args || curr->br->args_var)
*zone = ARGS;
else if (curr->br->url) /*don't assume that named $URL means zone is URL.*/
*zone = URL;
else if (curr->br->file_ext)
*zone = FILE_EXT;
/*
if we're facing a WL in the style $URL:/bla|ARGS (or any other zone),
push it to
*/
for (i = 0; i < curr->br->custom_locations->nelts; i++) {
/*
locate target URL if exists ($URL:/bla|ARGS) or ($URL:/bla|$ARGS_VAR:foo)
*/
if (custloc_array(curr->br->custom_locations->elts)[i].specific_url) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has URI %V", &(custloc_array(curr->br->custom_locations->elts)[i].target));
*uri_idx = i;
}
/*
identify named match zones ($ARGS_VAR:bla|$HEADERS_VAR:bla|$BODY_VAR:bla)
*/
if (custloc_array(curr->br->custom_locations->elts)[i].body_var) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has body_var %V", &(custloc_array(curr->br->custom_locations->elts)[i].target));
/*#217 : scream on incorrect rules*/
if (*name_idx != -1) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist can't target more than one BODY item.");
return (NGX_ERROR);
}
*name_idx = i;
*zone = BODY;
}
if (custloc_array(curr->br->custom_locations->elts)[i].headers_var) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has header_var %V", &(custloc_array(curr->br->custom_locations->elts)[i].target));
/*#217 : scream on incorrect rules*/
if (*name_idx != -1) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist can't target more than one HEADER item.");
return (NGX_ERROR);
}
*name_idx = i;
*zone = HEADERS;
}
if (custloc_array(curr->br->custom_locations->elts)[i].args_var) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has arg_var %V", &(custloc_array(curr->br->custom_locations->elts)[i].target));
/*#217 : scream on incorrect rules*/
if (*name_idx != -1) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist can't target more than one ARGS item.");
return (NGX_ERROR);
}
*name_idx = i;
*zone = ARGS;
}
}
if (*zone == -1)
return (NGX_ERROR);
return (NGX_OK);
}
ngx_http_whitelist_rule_t *
ngx_http_wlr_find(ngx_conf_t *cf, ngx_http_dummy_loc_conf_t *dlc,
ngx_http_rule_t *curr, int zone, int uri_idx,
int name_idx, char **fullname) {
uint i;
/* Create unique string for rule, and try to find it in existing rules.*/
/*name AND uri*/
if (uri_idx != -1 && name_idx != -1) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has uri + name");
/* allocate one extra byte in case curr->br->target_name is set. */
*fullname = ngx_pcalloc(cf->pool, custloc_array(curr->br->custom_locations->elts)[name_idx].target.len +
custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len + 3);
/* if WL targets variable name instead of content, prefix hash with '#' */
if (curr->br->target_name) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist targets |NAME");
strncat(*fullname, (const char *) "#", 1);
}
strncat(*fullname, (const char *) custloc_array(curr->br->custom_locations->elts)[uri_idx].target.data,
custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len);
strncat(*fullname, (const char *) "#", 1);
strncat(*fullname, (const char *) custloc_array(curr->br->custom_locations->elts)[name_idx].target.data,
custloc_array(curr->br->custom_locations->elts)[name_idx].target.len);
}
/* only uri */
else if (uri_idx != -1 && name_idx == -1) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has uri");
//XXX set flag only_uri
*fullname = ngx_pcalloc(cf->pool, custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len + 3);
if (curr->br->target_name) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist targets |NAME");
strncat(*fullname, (const char *) "#", 1);
}
strncat(*fullname, (const char *) custloc_array(curr->br->custom_locations->elts)[uri_idx].target.data,
custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len);
}
/* only name */
else if (name_idx != -1) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has name");
*fullname = ngx_pcalloc(cf->pool, custloc_array(curr->br->custom_locations->elts)[name_idx].target.len + 2);
if (curr->br->target_name)
strncat(*fullname, (const char *) "#", 1);
strncat(*fullname, (const char *) custloc_array(curr->br->custom_locations->elts)[name_idx].target.data,
custloc_array(curr->br->custom_locations->elts)[name_idx].target.len);
}
/* problem houston */
else
return (NULL);
for (i = 0; i < dlc->tmp_wlr->nelts; i++)
if (!strcmp((const char *)*fullname, (const char *)((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name->data) &&
((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].zone == (uint) zone)
{
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"found existing 'same' WL : %V", ((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name);
return (&((ngx_http_whitelist_rule_t *)dlc->tmp_wlr->elts)[i]);
}
return (NULL);
}
static ngx_int_t
ngx_http_wlr_finalize_hashtables(ngx_conf_t *cf, ngx_http_dummy_loc_conf_t *dlc) {
int get_sz = 0, headers_sz = 0, body_sz = 0, uri_sz = 0;
ngx_array_t *get_ar = NULL, *headers_ar = NULL, *body_ar = NULL, *uri_ar = NULL;
ngx_hash_key_t *arr_node;
ngx_hash_init_t hash_init;
uint i;
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"finalizing hashtables");
for (i = 0; i < dlc->tmp_wlr->nelts; i++) {
switch (((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].zone) {
case FILE_EXT:
case BODY:
body_sz++;
break;
case HEADERS:
headers_sz++;
break;
case URL:
uri_sz++;
break;
case ARGS:
get_sz++;
break;
case UNKNOWN:
default:
return (NGX_ERROR);
}
}
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"nb items : body:%d headers:%d uri:%d get:%d",
body_sz, headers_sz, uri_sz, get_sz);
if (get_sz)
get_ar = ngx_array_create(cf->pool, get_sz, sizeof(ngx_hash_key_t));
if (headers_sz)
headers_ar = ngx_array_create(cf->pool, headers_sz, sizeof(ngx_hash_key_t));
if (body_sz)
body_ar = ngx_array_create(cf->pool, body_sz, sizeof(ngx_hash_key_t));
if (uri_sz)
uri_ar = ngx_array_create(cf->pool, uri_sz, sizeof(ngx_hash_key_t));
for (i = 0; i < dlc->tmp_wlr->nelts; i++) {
switch (((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].zone) {
case FILE_EXT:
case BODY:
arr_node = (ngx_hash_key_t*) ngx_array_push(body_ar);
break;
case HEADERS:
arr_node = (ngx_hash_key_t*) ngx_array_push(headers_ar);
break;
case URL:
arr_node = (ngx_hash_key_t*) ngx_array_push(uri_ar);
break;
case ARGS:
arr_node = (ngx_hash_key_t*) ngx_array_push(get_ar);
break;
default:
return (NGX_ERROR);
}
if (!arr_node)
return (NGX_ERROR);
ngx_memset(arr_node, 0, sizeof(ngx_hash_key_t));
arr_node->key = *(((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name);
arr_node->key_hash = ngx_hash_key_lc(((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name->data,
((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name->len);
arr_node->value = (void *) &(((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i]);
#ifdef SPECIAL__debug_whitelist_heavy
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "pushing new WL, zone:%d, target:%V, %d IDs",
((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].zone , ((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name,
((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].ids->nelts);
unsigned int z;
for (z = 0; z < ((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].ids->nelts; z++)
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "id:%d",
((int *)((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].ids->elts)[z]);
#endif
}
hash_init.key = &ngx_hash_key_lc;
hash_init.pool = cf->pool;
hash_init.temp_pool = NULL;
hash_init.max_size = 1024;
hash_init.bucket_size = 512;
if (body_ar) {
dlc->wlr_body_hash = (ngx_hash_t*) ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
hash_init.hash = dlc->wlr_body_hash;
hash_init.name = "wlr_body_hash";
if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) body_ar->elts,
body_ar->nelts) != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "$BODY hashtable init failed"); /* LCOV_EXCL_LINE */
return (NGX_ERROR); /* LCOV_EXCL_LINE */
}
else
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$BODY hashtable init successed !");
}
if (uri_ar) {
dlc->wlr_url_hash = (ngx_hash_t*) ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
hash_init.hash = dlc->wlr_url_hash;
hash_init.name = "wlr_url_hash";
if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) uri_ar->elts,
uri_ar->nelts) != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "$URL hashtable init failed"); /* LCOV_EXCL_LINE */
return (NGX_ERROR); /* LCOV_EXCL_LINE */
}
else
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$URL hashtable init successed !");
}
if (get_ar) {
dlc->wlr_args_hash = (ngx_hash_t*) ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
hash_init.hash = dlc->wlr_args_hash;
hash_init.name = "wlr_args_hash";
if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) get_ar->elts,
get_ar->nelts) != NGX_OK) { /* LCOV_EXCL_LINE */
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "$ARGS hashtable init failed"); /* LCOV_EXCL_LINE */
return (NGX_ERROR);
}
else
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$ARGS hashtable init successed %d !",
dlc->wlr_args_hash->size);
}
if (headers_ar) {
dlc->wlr_headers_hash = (ngx_hash_t*) ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
hash_init.hash = dlc->wlr_headers_hash;
hash_init.name = "wlr_headers_hash";
if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) headers_ar->elts,
headers_ar->nelts) != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "$HEADERS hashtable init failed"); /* LCOV_EXCL_LINE */
return (NGX_ERROR); /* LCOV_EXCL_LINE */
}
else
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$HEADERS hashtable init successed %d !",
dlc->wlr_headers_hash->size);
}
return (NGX_OK);
}
/*
** This function will take the whitelist basicrules generated during the configuration
** parsing phase, and aggregate them to build hashtables according to the matchzones.
**
** As whitelist can be in the form :
** "mz:$URL:bla|$ARGS_VAR:foo"
** "mz:$URL:bla|ARGS"
** "mz:$HEADERS_VAR:Cookie"
** ...
**
** So, we will aggregate all the rules that are pointing to the same URL together,
** as well as rules targetting the same argument name / zone.
*/
ngx_int_t
ngx_http_dummy_create_hashtables_n(ngx_http_dummy_loc_conf_t *dlc,
ngx_conf_t *cf)
{
int zone, uri_idx, name_idx, ret;
ngx_http_rule_t *curr_r/*, *father_r*/;
ngx_http_whitelist_rule_t *father_wlr;
ngx_http_rule_t **rptr;
ngx_regex_compile_t *rgc;
char *fullname;
uint i;
if (!dlc->whitelist_rules || dlc->whitelist_rules->nelts < 1) {
NX_LOG_DEBUG(_debug_whitelist_heavy ,
NGX_LOG_EMERG, cf, 0,
"No whitelist registred, but it's your call.");
return (NGX_OK);
}
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"Building whitelist hashtables, %d items in list",
dlc->whitelist_rules->nelts);
dlc->tmp_wlr = ngx_array_create(cf->pool, dlc->whitelist_rules->nelts,
sizeof(ngx_http_whitelist_rule_t));
/* iterate through each stored whitelist rule. */
for (i = 0; i < dlc->whitelist_rules->nelts; i++) {
uri_idx = name_idx = zone = -1;
/*a whitelist is in fact just another basic_rule_t */
curr_r = &(((ngx_http_rule_t*)(dlc->whitelist_rules->elts))[i]);
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"Processing wl %d/%p", i, curr_r);
/*no custom location at all means that the rule is disabled */
if (!curr_r->br->custom_locations) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"WL %d is a disable rule.", i);
if (ngx_http_wlr_push_disabled(cf, dlc, curr_r) == NGX_ERROR)
return (NGX_ERROR);
continue;
}
ret = ngx_http_wlr_identify(cf, dlc, curr_r, &zone, &uri_idx, &name_idx);
if (ret != NGX_OK) /* LCOV_EXCL_START */
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"Following whitelist doesn't target any zone or is incorrect :");
if (name_idx != -1)
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist target name : %V",
&(custloc_array(curr_r->br->custom_locations->elts)[name_idx].target));
else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist has no target name.");
if (uri_idx != -1)
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist target uri : %V",
&(custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target));
else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelists has no target uri.");
return (NGX_ERROR);
} /* LCOV_EXCL_STOP */
curr_r->br->zone = zone;
/*
** Handle regular-expression-matchzone rules :
** Store them in a separate linked list, parsed
** at runtime.
*/
if (curr_r->br->rx_mz == 1) {
if (!dlc->rxmz_wlr) {
dlc->rxmz_wlr = ngx_array_create(cf->pool, 1,
sizeof(ngx_http_rule_t *));
if (!dlc->rxmz_wlr) return (NGX_ERROR); /* LCOV_EXCL_LINE */
}
if (name_idx != -1 && !custloc_array(curr_r->br->custom_locations->elts)[name_idx].target_rx) {
custloc_array(curr_r->br->custom_locations->elts)[name_idx].target_rx =
ngx_pcalloc(cf->pool, sizeof(ngx_regex_compile_t));
rgc = custloc_array(curr_r->br->custom_locations->elts)[name_idx].target_rx;
rgc->options = PCRE_CASELESS|PCRE_MULTILINE;
rgc->pattern = custloc_array(curr_r->br->custom_locations->elts)[name_idx].target;
rgc->pool = cf->pool;
rgc->err.len = 0;
rgc->err.data = NULL;
//custloc_array(curr_r->br->custom_locations->elts)[name_idx].target;
if (ngx_regex_compile(rgc) != NGX_OK)
return (NGX_ERROR);
}
if (uri_idx != -1 && !custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target_rx) {
custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target_rx =
ngx_pcalloc(cf->pool, sizeof(ngx_regex_compile_t));
rgc = custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target_rx;
rgc->options = PCRE_CASELESS|PCRE_MULTILINE;
rgc->pattern = custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target;
rgc->pool = cf->pool;
rgc->err.len = 0;
rgc->err.data = NULL;
//custloc_array(curr_r->br->custom_locations->elts)[name_idx].target;
if (ngx_regex_compile(rgc) != NGX_OK)
return (NGX_ERROR);
}
rptr = ngx_array_push(dlc->rxmz_wlr);
if (!rptr)
return (NGX_ERROR);
*rptr = curr_r;
continue;
}
/*
** Handle static match-zones for hashtables
*/
father_wlr = ngx_http_wlr_find(cf, dlc, curr_r, zone, uri_idx, name_idx, (char **) &fullname);
if (!father_wlr) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"creating fresh WL [%s].", fullname);
/* creates a new whitelist rule in the right place.
setup name and zone, create a new (empty) whitelist_location, as well
as a new (empty) id aray. */
father_wlr = ngx_array_push(dlc->tmp_wlr);
if (!father_wlr)
return (NGX_ERROR);
memset(father_wlr, 0, sizeof(ngx_http_whitelist_rule_t));
father_wlr->name = ngx_pcalloc(cf->pool, sizeof(ngx_str_t));
if (!father_wlr->name)
return (NGX_ERROR);
father_wlr->name->len = strlen((const char *) fullname);
father_wlr->name->data = (unsigned char *) fullname;
father_wlr->zone = zone;
/* If there is URI and no name idx, specify it,
so that WL system won't get fooled by an argname like an URL */
if (uri_idx != -1 && name_idx == -1)
father_wlr->uri_only = 1;
/* If target_name is present in son, report it. */
if (curr_r->br->target_name)
father_wlr->target_name = curr_r->br->target_name;
}
/*merges the two whitelist rules together, including custom_locations. */
if (ngx_http_wlr_merge(cf, father_wlr, curr_r) != NGX_OK)
return (NGX_ERROR);
}
/* and finally, build the hashtables for various zones. */
if (ngx_http_wlr_finalize_hashtables(cf, dlc) != NGX_OK)
return (NGX_ERROR);
/* TODO : Free old whitelist_rules (dlc->whitelist_rules)*/
return (NGX_OK);
}
/*
function used for intensive log if dynamic flag is set.
Output format :
ip=<ip>&server=<server>&uri=<uri>&id=<id>&zone=<zone>&content=<content>
*/
static const char *dummy_match_zones[] = {
"HEADERS",
"URL",
"ARGS",
"BODY",
"FILE_EXT",
"UNKNOWN",
NULL
};
void naxsi_log_offending(ngx_str_t *name, ngx_str_t *val, ngx_http_request_t *req, ngx_http_rule_t *rule,
enum DUMMY_MATCH_ZONE zone, ngx_int_t target_name) {
ngx_str_t tmp_uri, tmp_val, tmp_name;
ngx_str_t empty=ngx_string("");
//encode uri
tmp_uri.len = req->uri.len + (2 * ngx_escape_uri(NULL, req->uri.data, req->uri.len,
NGX_ESCAPE_ARGS));
tmp_uri.data = ngx_pcalloc(req->pool, tmp_uri.len+1);
if (tmp_uri.data == NULL)
return ;
ngx_escape_uri(tmp_uri.data, req->uri.data, req->uri.len, NGX_ESCAPE_ARGS);
//encode val
if (val->len <= 0)
tmp_val = empty;
else {
tmp_val.len = val->len + (2 * ngx_escape_uri(NULL, val->data, val->len,
NGX_ESCAPE_ARGS));
tmp_val.data = ngx_pcalloc(req->pool, tmp_val.len+1);
if (tmp_val.data == NULL)
return ;
ngx_escape_uri(tmp_val.data, val->data, val->len, NGX_ESCAPE_ARGS);
}
//encode name
if (name->len <= 0)
tmp_name = empty;
else {
tmp_name.len = name->len + (2 * ngx_escape_uri(NULL, name->data, name->len,
NGX_ESCAPE_ARGS));
tmp_name.data = ngx_pcalloc(req->pool, tmp_name.len+1);
if (tmp_name.data == NULL)
return ;
ngx_escape_uri(tmp_name.data, name->data, name->len, NGX_ESCAPE_ARGS);
}
ngx_log_error(NGX_LOG_ERR, req->connection->log, 0,
"NAXSI_EXLOG: ip=%V&server=%V&uri=%V&id=%d&zone=%s%s&var_name=%V&content=%V",
&(req->connection->addr_text), &(req->headers_in.server),
&(tmp_uri), rule->rule_id, dummy_match_zones[zone], target_name?"|NAME":"", &(tmp_name), &(tmp_val));
if (tmp_val.len > 0)
ngx_pfree(req->pool, tmp_val.data);
if (tmp_name.len > 0)
ngx_pfree(req->pool, tmp_name.data);
if (tmp_uri.len > 0)
ngx_pfree(req->pool, tmp_uri.data);
}
/*
** Used to check matched rule ID against wl IDs
** Returns 1 if rule is whitelisted, 0 else
*/
int nx_check_ids(ngx_int_t match_id, ngx_array_t *wl_ids) {
int negative=0;
unsigned int i;
for (i = 0; i < wl_ids->nelts; i++) {
if ( ((ngx_int_t *)wl_ids->elts)[i] == match_id)
return (1);
if ( ((ngx_int_t *)wl_ids->elts)[i] == 0)
return (1);
/* manage negative whitelists, except for internal rules */
if ( ((ngx_int_t *)wl_ids->elts)[i] < 0 && match_id >= 1000) {
negative = 1;
/* negative wl excludes this one.*/
if (match_id == -((ngx_int_t *)wl_ids->elts)[i]) {
return (0);
}
}
}
return (negative == 1);
}

View File

@@ -0,0 +1,36 @@
master_process off;
worker_processes 1;
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
events {
worker_connections 1024;
}
http {
include /tmp/naxsi_ut/naxsi_core.rules;
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 4242;
server_name localhost;
location / {
LearningMode;
SecRulesEnabled;
DeniedUrl "/50x.html";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
error_log /tmp/ngx_error.log debug;
access_log /tmp/ngx_access.log;
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

826
naxsi-0.55.3/naxsi_utils.c Normal file
View File

@@ -0,0 +1,826 @@
/*
* NAXSI, a web application firewall for NGINX
* Copyright (C) 2016, Thibault 'bui' Koechlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "naxsi.h"
static int naxsi_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);
char *
strnchr(const char *s, int c, int len)
{
int cpt;
for (cpt = 0; cpt < len && s[cpt]; cpt++)
if (s[cpt] == c)
return ((char *) s+cpt);
return (NULL);
}
static char *
strncasechr(const char *s, int c, int len)
{
int cpt;
for (cpt = 0; cpt < len && s[cpt]; cpt++)
if (tolower(s[cpt]) == c)
return ((char *) s+cpt);
return (NULL);
}
/*
** strstr: faster, stronger, harder
** (because strstr from libc is very slow)
*/
char *
strfaststr(unsigned char *haystack, unsigned int hl,
unsigned char *needle, unsigned int nl)
{
char *cpt, *found, *end;
if (hl < nl || !haystack || !needle || !nl || !hl) return (NULL);
cpt = (char *) haystack;
end = (char *) haystack + hl;
while (cpt < end) {
found = strncasechr((const char *) cpt, (int) needle[0], hl);
if (!found) return (NULL);
if (nl == 1) return (found);
if (!strncasecmp((const char *)found+1, (const char *) needle+1, nl-1))
return ((char *) found);
else {
if (found+nl >= end)
break;
if (found+nl < end) {
/* the haystack is shrinking */
cpt = found+1;
hl = (unsigned int) (end - cpt);
}
}
}
return (NULL);
}
/* unescape routine, returns number of nullbytes present */
int naxsi_unescape(ngx_str_t *str) {
u_char *dst, *src;
u_int nullbytes = 0, bad = 0, i;
dst = str->data;
src = str->data;
bad = naxsi_unescape_uri(&src, &dst,
str->len, 0);
str->len = src - str->data;
//tmp hack fix, avoid %00 & co (null byte) encoding :p
for (i = 0; i < str->len; i++)
if (str->data[i] == 0x0)
{
nullbytes++;
str->data[i] = '0';
}
return (nullbytes+bad);
}
/*
** Patched ngx_unescape_uri :
** The original one does not care if the character following % is in valid range.
** For example, with the original one :
** '%uff' -> 'uff'
*/
static int
naxsi_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type)
{
u_char *d, *s, ch, c, decoded;
int bad = 0;
enum {
sw_usual = 0,
sw_quoted,
sw_quoted_second
} state;
d = *dst;
s = *src;
state = 0;
decoded = 0;
while (size--) {
ch = *s++;
switch (state) {
case sw_usual:
if (ch == '%') {
state = sw_quoted;
break;
}
*d++ = ch;
break;
case sw_quoted:
if (ch >= '0' && ch <= '9') {
decoded = (u_char) (ch - '0');
state = sw_quoted_second;
break;
}
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'f') {
decoded = (u_char) (c - 'a' + 10);
state = sw_quoted_second;
break;
}
/* the invalid quoted character */
bad++;
state = sw_usual;
*d++ = '%';
*d++ = ch;
break;
case sw_quoted_second:
state = sw_usual;
if (ch >= '0' && ch <= '9') {
ch = (u_char) ((decoded << 4) + ch - '0');
*d++ = ch;
break;
}
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'f') {
ch = (u_char) ((decoded << 4) + c - 'a' + 10);
*d++ = ch;
break;
}
/* the invalid quoted character */
/* as it happened in the 2nd part of quoted character,
we need to restore the decoded char as well. */
*d++ = '%';
*d++ = *(s - 2);
*d++ = *(s - 1);
bad++;
break;
}
}
*dst = d;
*src = s;
return (bad);
}
/* push rule into disabled rules. */
static ngx_int_t
ngx_http_wlr_push_disabled(ngx_conf_t *cf, ngx_http_dummy_loc_conf_t *dlc,
ngx_http_rule_t *curr) {
ngx_http_rule_t **dr;
if (!dlc->disabled_rules)
dlc->disabled_rules = ngx_array_create(cf->pool, 4,
sizeof(ngx_http_rule_t *));
if (!dlc->disabled_rules)
return (NGX_ERROR);
dr = ngx_array_push(dlc->disabled_rules);
if (!dr)
return (NGX_ERROR);
*dr = (ngx_http_rule_t *) curr;
return (NGX_OK);
}
/* merge the two rules into father_wl, meaning
ids. Not locations, as we are getting rid of it */
static ngx_int_t
ngx_http_wlr_merge(ngx_conf_t *cf, ngx_http_whitelist_rule_t *father_wl,
ngx_http_rule_t *curr) {
uint i;
ngx_int_t *tmp_ptr;
NX_LOG_DEBUG(_debug_whitelist,
NGX_LOG_EMERG, cf, 0, "[naxsi] merging similar wl(s)");
if (!father_wl->ids)
{
father_wl->ids = ngx_array_create(cf->pool, 3, sizeof(ngx_int_t));
if (!father_wl->ids)
return (NGX_ERROR);
}
for (i = 0; i < curr->wlid_array->nelts; i++) {
tmp_ptr = ngx_array_push(father_wl->ids);
if (!tmp_ptr)
return (NGX_ERROR);
*tmp_ptr = ((ngx_int_t *)curr->wlid_array->elts)[i];
//*tmp_ptr = curr->wlid_array->elts[i];
}
return (NGX_OK);
}
/*check rule, returns associed zone, as well as location index.
location index refers to $URL:bla or $ARGS_VAR:bla */
#define custloc_array(x) ((ngx_http_custom_rule_location_t *) x)
static ngx_int_t
ngx_http_wlr_identify(ngx_conf_t *cf, ngx_http_dummy_loc_conf_t *dlc,
ngx_http_rule_t *curr, int *zone,
int *uri_idx, int *name_idx) {
uint i;
/*
identify global match zones (|ARGS|BODY|HEADERS|URL|FILE_EXT)
*/
if (curr->br->body || curr->br->body_var)
*zone = BODY;
else if (curr->br->headers || curr->br->headers_var)
*zone = HEADERS;
else if (curr->br->args || curr->br->args_var)
*zone = ARGS;
else if (curr->br->url) /*don't assume that named $URL means zone is URL.*/
*zone = URL;
else if (curr->br->file_ext)
*zone = FILE_EXT;
/*
if we're facing a WL in the style $URL:/bla|ARGS (or any other zone),
push it to
*/
for (i = 0; i < curr->br->custom_locations->nelts; i++) {
/*
locate target URL if exists ($URL:/bla|ARGS) or ($URL:/bla|$ARGS_VAR:foo)
*/
if (custloc_array(curr->br->custom_locations->elts)[i].specific_url) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has URI %V", &(custloc_array(curr->br->custom_locations->elts)[i].target));
*uri_idx = i;
}
/*
identify named match zones ($ARGS_VAR:bla|$HEADERS_VAR:bla|$BODY_VAR:bla)
*/
if (custloc_array(curr->br->custom_locations->elts)[i].body_var) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has body_var %V", &(custloc_array(curr->br->custom_locations->elts)[i].target));
/*#217 : scream on incorrect rules*/
if (*name_idx != -1) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist can't target more than one BODY item.");
return (NGX_ERROR);
}
*name_idx = i;
*zone = BODY;
}
if (custloc_array(curr->br->custom_locations->elts)[i].headers_var) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has header_var %V", &(custloc_array(curr->br->custom_locations->elts)[i].target));
/*#217 : scream on incorrect rules*/
if (*name_idx != -1) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist can't target more than one HEADER item.");
return (NGX_ERROR);
}
*name_idx = i;
*zone = HEADERS;
}
if (custloc_array(curr->br->custom_locations->elts)[i].args_var) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has arg_var %V", &(custloc_array(curr->br->custom_locations->elts)[i].target));
/*#217 : scream on incorrect rules*/
if (*name_idx != -1) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist can't target more than one ARGS item.");
return (NGX_ERROR);
}
*name_idx = i;
*zone = ARGS;
}
}
if (*zone == -1)
return (NGX_ERROR);
return (NGX_OK);
}
ngx_http_whitelist_rule_t *
ngx_http_wlr_find(ngx_conf_t *cf, ngx_http_dummy_loc_conf_t *dlc,
ngx_http_rule_t *curr, int zone, int uri_idx,
int name_idx, char **fullname) {
uint i;
/* Create unique string for rule, and try to find it in existing rules.*/
/*name AND uri*/
if (uri_idx != -1 && name_idx != -1) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has uri + name");
/* allocate one extra byte in case curr->br->target_name is set. */
*fullname = ngx_pcalloc(cf->pool, custloc_array(curr->br->custom_locations->elts)[name_idx].target.len +
custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len + 3);
/* if WL targets variable name instead of content, prefix hash with '#' */
if (curr->br->target_name) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist targets |NAME");
strncat(*fullname, (const char *) "#", 1);
}
strncat(*fullname, (const char *) custloc_array(curr->br->custom_locations->elts)[uri_idx].target.data,
custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len);
strncat(*fullname, (const char *) "#", 1);
strncat(*fullname, (const char *) custloc_array(curr->br->custom_locations->elts)[name_idx].target.data,
custloc_array(curr->br->custom_locations->elts)[name_idx].target.len);
}
/* only uri */
else if (uri_idx != -1 && name_idx == -1) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has uri");
//XXX set flag only_uri
*fullname = ngx_pcalloc(cf->pool, custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len + 3);
if (curr->br->target_name) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist targets |NAME");
strncat(*fullname, (const char *) "#", 1);
}
strncat(*fullname, (const char *) custloc_array(curr->br->custom_locations->elts)[uri_idx].target.data,
custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len);
}
/* only name */
else if (name_idx != -1) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"whitelist has name");
*fullname = ngx_pcalloc(cf->pool, custloc_array(curr->br->custom_locations->elts)[name_idx].target.len + 2);
if (curr->br->target_name)
strncat(*fullname, (const char *) "#", 1);
strncat(*fullname, (const char *) custloc_array(curr->br->custom_locations->elts)[name_idx].target.data,
custloc_array(curr->br->custom_locations->elts)[name_idx].target.len);
}
/* problem houston */
else
return (NULL);
for (i = 0; i < dlc->tmp_wlr->nelts; i++)
if (!strcmp((const char *)*fullname, (const char *)((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name->data) &&
((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].zone == (uint) zone)
{
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"found existing 'same' WL : %V", ((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name);
return (&((ngx_http_whitelist_rule_t *)dlc->tmp_wlr->elts)[i]);
}
return (NULL);
}
static ngx_int_t
ngx_http_wlr_finalize_hashtables(ngx_conf_t *cf, ngx_http_dummy_loc_conf_t *dlc) {
int get_sz = 0, headers_sz = 0, body_sz = 0, uri_sz = 0;
ngx_array_t *get_ar = NULL, *headers_ar = NULL, *body_ar = NULL, *uri_ar = NULL;
ngx_hash_key_t *arr_node;
ngx_hash_init_t hash_init;
uint i;
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"finalizing hashtables");
for (i = 0; i < dlc->tmp_wlr->nelts; i++) {
switch (((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].zone) {
case FILE_EXT:
case BODY:
body_sz++;
break;
case HEADERS:
headers_sz++;
break;
case URL:
uri_sz++;
break;
case ARGS:
get_sz++;
break;
case UNKNOWN:
default:
return (NGX_ERROR);
}
}
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"nb items : body:%d headers:%d uri:%d get:%d",
body_sz, headers_sz, uri_sz, get_sz);
if (get_sz)
get_ar = ngx_array_create(cf->pool, get_sz, sizeof(ngx_hash_key_t));
if (headers_sz)
headers_ar = ngx_array_create(cf->pool, headers_sz, sizeof(ngx_hash_key_t));
if (body_sz)
body_ar = ngx_array_create(cf->pool, body_sz, sizeof(ngx_hash_key_t));
if (uri_sz)
uri_ar = ngx_array_create(cf->pool, uri_sz, sizeof(ngx_hash_key_t));
for (i = 0; i < dlc->tmp_wlr->nelts; i++) {
switch (((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].zone) {
case FILE_EXT:
case BODY:
arr_node = (ngx_hash_key_t*) ngx_array_push(body_ar);
break;
case HEADERS:
arr_node = (ngx_hash_key_t*) ngx_array_push(headers_ar);
break;
case URL:
arr_node = (ngx_hash_key_t*) ngx_array_push(uri_ar);
break;
case ARGS:
arr_node = (ngx_hash_key_t*) ngx_array_push(get_ar);
break;
default:
return (NGX_ERROR);
}
if (!arr_node)
return (NGX_ERROR);
ngx_memset(arr_node, 0, sizeof(ngx_hash_key_t));
arr_node->key = *(((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name);
arr_node->key_hash = ngx_hash_key_lc(((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name->data,
((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name->len);
arr_node->value = (void *) &(((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i]);
#ifdef SPECIAL__debug_whitelist_heavy
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "pushing new WL, zone:%d, target:%V, %d IDs",
((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].zone , ((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].name,
((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].ids->nelts);
unsigned int z;
for (z = 0; z < ((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].ids->nelts; z++)
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "id:%d",
((int *)((ngx_http_whitelist_rule_t *) dlc->tmp_wlr->elts)[i].ids->elts)[z]);
#endif
}
hash_init.key = &ngx_hash_key_lc;
hash_init.pool = cf->pool;
hash_init.temp_pool = NULL;
hash_init.max_size = 1024;
hash_init.bucket_size = 512;
if (body_ar) {
dlc->wlr_body_hash = (ngx_hash_t*) ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
hash_init.hash = dlc->wlr_body_hash;
hash_init.name = "wlr_body_hash";
if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) body_ar->elts,
body_ar->nelts) != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "$BODY hashtable init failed"); /* LCOV_EXCL_LINE */
return (NGX_ERROR); /* LCOV_EXCL_LINE */
}
else
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$BODY hashtable init successed !");
}
if (uri_ar) {
dlc->wlr_url_hash = (ngx_hash_t*) ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
hash_init.hash = dlc->wlr_url_hash;
hash_init.name = "wlr_url_hash";
if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) uri_ar->elts,
uri_ar->nelts) != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "$URL hashtable init failed"); /* LCOV_EXCL_LINE */
return (NGX_ERROR); /* LCOV_EXCL_LINE */
}
else
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$URL hashtable init successed !");
}
if (get_ar) {
dlc->wlr_args_hash = (ngx_hash_t*) ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
hash_init.hash = dlc->wlr_args_hash;
hash_init.name = "wlr_args_hash";
if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) get_ar->elts,
get_ar->nelts) != NGX_OK) { /* LCOV_EXCL_LINE */
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "$ARGS hashtable init failed"); /* LCOV_EXCL_LINE */
return (NGX_ERROR);
}
else
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$ARGS hashtable init successed %d !",
dlc->wlr_args_hash->size);
}
if (headers_ar) {
dlc->wlr_headers_hash = (ngx_hash_t*) ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
hash_init.hash = dlc->wlr_headers_hash;
hash_init.name = "wlr_headers_hash";
if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) headers_ar->elts,
headers_ar->nelts) != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "$HEADERS hashtable init failed"); /* LCOV_EXCL_LINE */
return (NGX_ERROR); /* LCOV_EXCL_LINE */
}
else
NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$HEADERS hashtable init successed %d !",
dlc->wlr_headers_hash->size);
}
return (NGX_OK);
}
/*
** This function will take the whitelist basicrules generated during the configuration
** parsing phase, and aggregate them to build hashtables according to the matchzones.
**
** As whitelist can be in the form :
** "mz:$URL:bla|$ARGS_VAR:foo"
** "mz:$URL:bla|ARGS"
** "mz:$HEADERS_VAR:Cookie"
** ...
**
** So, we will aggregate all the rules that are pointing to the same URL together,
** as well as rules targetting the same argument name / zone.
*/
ngx_int_t
ngx_http_dummy_create_hashtables_n(ngx_http_dummy_loc_conf_t *dlc,
ngx_conf_t *cf)
{
int zone, uri_idx, name_idx, ret;
ngx_http_rule_t *curr_r/*, *father_r*/;
ngx_http_whitelist_rule_t *father_wlr;
ngx_http_rule_t **rptr;
ngx_regex_compile_t *rgc;
char *fullname;
uint i;
if (!dlc->whitelist_rules || dlc->whitelist_rules->nelts < 1) {
NX_LOG_DEBUG(_debug_whitelist_heavy ,
NGX_LOG_EMERG, cf, 0,
"No whitelist registred, but it's your call.");
return (NGX_OK);
}
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"Building whitelist hashtables, %d items in list",
dlc->whitelist_rules->nelts);
dlc->tmp_wlr = ngx_array_create(cf->pool, dlc->whitelist_rules->nelts,
sizeof(ngx_http_whitelist_rule_t));
/* iterate through each stored whitelist rule. */
for (i = 0; i < dlc->whitelist_rules->nelts; i++) {
uri_idx = name_idx = zone = -1;
/*a whitelist is in fact just another basic_rule_t */
curr_r = &(((ngx_http_rule_t*)(dlc->whitelist_rules->elts))[i]);
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"Processing wl %d/%p", i, curr_r);
/*no custom location at all means that the rule is disabled */
if (!curr_r->br->custom_locations) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"WL %d is a disable rule.", i);
if (ngx_http_wlr_push_disabled(cf, dlc, curr_r) == NGX_ERROR)
return (NGX_ERROR);
continue;
}
ret = ngx_http_wlr_identify(cf, dlc, curr_r, &zone, &uri_idx, &name_idx);
if (ret != NGX_OK) /* LCOV_EXCL_START */
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"Following whitelist doesn't target any zone or is incorrect :");
if (name_idx != -1)
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist target name : %V",
&(custloc_array(curr_r->br->custom_locations->elts)[name_idx].target));
else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist has no target name.");
if (uri_idx != -1)
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist target uri : %V",
&(custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target));
else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelists has no target uri.");
return (NGX_ERROR);
} /* LCOV_EXCL_STOP */
curr_r->br->zone = zone;
/*
** Handle regular-expression-matchzone rules :
** Store them in a separate linked list, parsed
** at runtime.
*/
if (curr_r->br->rx_mz == 1) {
if (!dlc->rxmz_wlr) {
dlc->rxmz_wlr = ngx_array_create(cf->pool, 1,
sizeof(ngx_http_rule_t *));
if (!dlc->rxmz_wlr) return (NGX_ERROR); /* LCOV_EXCL_LINE */
}
if (name_idx != -1 && !custloc_array(curr_r->br->custom_locations->elts)[name_idx].target_rx) {
custloc_array(curr_r->br->custom_locations->elts)[name_idx].target_rx =
ngx_pcalloc(cf->pool, sizeof(ngx_regex_compile_t));
rgc = custloc_array(curr_r->br->custom_locations->elts)[name_idx].target_rx;
rgc->options = PCRE_CASELESS|PCRE_MULTILINE;
rgc->pattern = custloc_array(curr_r->br->custom_locations->elts)[name_idx].target;
rgc->pool = cf->pool;
rgc->err.len = 0;
rgc->err.data = NULL;
//custloc_array(curr_r->br->custom_locations->elts)[name_idx].target;
if (ngx_regex_compile(rgc) != NGX_OK)
return (NGX_ERROR);
}
if (uri_idx != -1 && !custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target_rx) {
custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target_rx =
ngx_pcalloc(cf->pool, sizeof(ngx_regex_compile_t));
rgc = custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target_rx;
rgc->options = PCRE_CASELESS|PCRE_MULTILINE;
rgc->pattern = custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target;
rgc->pool = cf->pool;
rgc->err.len = 0;
rgc->err.data = NULL;
//custloc_array(curr_r->br->custom_locations->elts)[name_idx].target;
if (ngx_regex_compile(rgc) != NGX_OK)
return (NGX_ERROR);
}
rptr = ngx_array_push(dlc->rxmz_wlr);
if (!rptr)
return (NGX_ERROR);
*rptr = curr_r;
continue;
}
/*
** Handle static match-zones for hashtables
*/
father_wlr = ngx_http_wlr_find(cf, dlc, curr_r, zone, uri_idx, name_idx, (char **) &fullname);
if (!father_wlr) {
NX_LOG_DEBUG(_debug_whitelist_heavy,
NGX_LOG_EMERG, cf, 0,
"creating fresh WL [%s].", fullname);
/* creates a new whitelist rule in the right place.
setup name and zone, create a new (empty) whitelist_location, as well
as a new (empty) id aray. */
father_wlr = ngx_array_push(dlc->tmp_wlr);
if (!father_wlr)
return (NGX_ERROR);
memset(father_wlr, 0, sizeof(ngx_http_whitelist_rule_t));
father_wlr->name = ngx_pcalloc(cf->pool, sizeof(ngx_str_t));
if (!father_wlr->name)
return (NGX_ERROR);
father_wlr->name->len = strlen((const char *) fullname);
father_wlr->name->data = (unsigned char *) fullname;
father_wlr->zone = zone;
/* If there is URI and no name idx, specify it,
so that WL system won't get fooled by an argname like an URL */
if (uri_idx != -1 && name_idx == -1)
father_wlr->uri_only = 1;
/* If target_name is present in son, report it. */
if (curr_r->br->target_name)
father_wlr->target_name = curr_r->br->target_name;
}
/*merges the two whitelist rules together, including custom_locations. */
if (ngx_http_wlr_merge(cf, father_wlr, curr_r) != NGX_OK)
return (NGX_ERROR);
}
/* and finally, build the hashtables for various zones. */
if (ngx_http_wlr_finalize_hashtables(cf, dlc) != NGX_OK)
return (NGX_ERROR);
/* TODO : Free old whitelist_rules (dlc->whitelist_rules)*/
return (NGX_OK);
}
/*
function used for intensive log if dynamic flag is set.
Output format :
ip=<ip>&server=<server>&uri=<uri>&id=<id>&zone=<zone>&content=<content>
*/
static const char *dummy_match_zones[] = {
"HEADERS",
"URL",
"ARGS",
"BODY",
"FILE_EXT",
"UNKNOWN",
NULL
};
void naxsi_log_offending(ngx_str_t *name, ngx_str_t *val, ngx_http_request_t *req, ngx_http_rule_t *rule,
enum DUMMY_MATCH_ZONE zone, ngx_int_t target_name) {
ngx_str_t tmp_uri, tmp_val, tmp_name;
ngx_str_t empty=ngx_string("");
//encode uri
tmp_uri.len = req->uri.len + (2 * ngx_escape_uri(NULL, req->uri.data, req->uri.len,
NGX_ESCAPE_ARGS));
tmp_uri.data = ngx_pcalloc(req->pool, tmp_uri.len+1);
if (tmp_uri.data == NULL)
return ;
ngx_escape_uri(tmp_uri.data, req->uri.data, req->uri.len, NGX_ESCAPE_ARGS);
//encode val
if (val->len <= 0)
tmp_val = empty;
else {
tmp_val.len = val->len + (2 * ngx_escape_uri(NULL, val->data, val->len,
NGX_ESCAPE_ARGS));
tmp_val.data = ngx_pcalloc(req->pool, tmp_val.len+1);
if (tmp_val.data == NULL)
return ;
ngx_escape_uri(tmp_val.data, val->data, val->len, NGX_ESCAPE_ARGS);
}
//encode name
if (name->len <= 0)
tmp_name = empty;
else {
tmp_name.len = name->len + (2 * ngx_escape_uri(NULL, name->data, name->len,
NGX_ESCAPE_ARGS));
tmp_name.data = ngx_pcalloc(req->pool, tmp_name.len+1);
if (tmp_name.data == NULL)
return ;
ngx_escape_uri(tmp_name.data, name->data, name->len, NGX_ESCAPE_ARGS);
}
ngx_log_error(NGX_LOG_ERR, req->connection->log, 0,
"NAXSI_EXLOG: ip=%V&server=%V&uri=%V&id=%d&zone=%s%s&var_name=%V&content=%V",
&(req->connection->addr_text), &(req->headers_in.server),
&(tmp_uri), rule->rule_id, dummy_match_zones[zone], target_name?"|NAME":"", &(tmp_name), &(tmp_val));
if (tmp_val.len > 0)
ngx_pfree(req->pool, tmp_val.data);
if (tmp_name.len > 0)
ngx_pfree(req->pool, tmp_name.data);
if (tmp_uri.len > 0)
ngx_pfree(req->pool, tmp_uri.data);
}
/*
** Used to check matched rule ID against wl IDs
** Returns 1 if rule is whitelisted, 0 else
*/
int nx_check_ids(ngx_int_t match_id, ngx_array_t *wl_ids) {
int negative=0;
unsigned int i;
for (i = 0; i < wl_ids->nelts; i++) {
if ( ((ngx_int_t *)wl_ids->elts)[i] == match_id)
return (1);
if ( ((ngx_int_t *)wl_ids->elts)[i] == 0)
return (1);
/* manage negative whitelists, except for internal rules */
if ( ((ngx_int_t *)wl_ids->elts)[i] < 0 && match_id >= 1000) {
negative = 1;
/* negative wl excludes this one.*/
if (match_id == -((ngx_int_t *)wl_ids->elts)[i]) {
return (0);
}
}
}
return (negative == 1);
}

View File

@@ -0,0 +1,36 @@
master_process off;
worker_processes 1;
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
events {
worker_connections 1024;
}
http {
include /tmp/naxsi_ut/naxsi_core.rules;
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 4242;
server_name localhost;
location / {
LearningMode;
SecRulesEnabled;
DeniedUrl "/50x.html";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
error_log /tmp/ngx_error.log debug;
access_log /tmp/ngx_access.log;
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

View File

@@ -0,0 +1,399 @@
# A quick word
nxapi/nxtool is the new learning tool, that attempts to perform the following :
* Events import : Importing naxsi events into an elasticsearch database
* Whitelist generation : Generate whitelists, from templates rather than from purely statistical aspects
* Events management : Allow tagging of events into database to exclude them from wl gen process
* Reporting : Display information about current DB content
# Configuration file : nxapi.json
nxapi uses a JSON file for its settings, such as :
$ cat nxapi.json
{
# elasticsearch setup, must point to the right instance.
"elastic" : {
"host" : "127.0.0.1:9200",
"index" : "nxapi",
"doctype" : "events",
"default_ttl" : "7200",
"max_size" : "1000"
},
# filter used for any issued requests, you shouldn't modify it yet
"global_filters" : {
"whitelisted" : "false"
},
# global warning and global success rules, used to distinguish good and 'bad' whitelists
"global_warning_rules" : {
"rule_uri" : [ ">", "5" ],
"rule_var_name" : [ ">", "5" ],
"rule_ip" : ["<=", 10 ],
"global_rule_ip_ratio" : ["<", 5]
},
"global_success_rules" : {
"global_rule_ip_ratio" : [">=", 30],
"rule_ip" : [">=", 10]
},
# path to naxsi core rules, path to template files,
# path to geoloc database.
"naxsi" : {
"rules_path" : "/etc/nginx/naxsi_core.rules",
"template_path" : "tpl/",
"geoipdb_path" : "nx_datas/country2coords.txt"
},
# controls default colors and verbosity behavior
"output" : {
"colors" : "true",
"verbosity" : "5"
}
}
# Prequisites
## Set up ElasticSearch
* Download the archive with the binary files from https://www.elastic.co/downloads/elasticsearch
* Extract the archive
* Start ElasticSearch by executing `bin/elasticsearch` in the extracted folder
* Check whether ElasticSearch is running correctly:
`curl -XGET http://localhost:9200/`
* Add a nxapi index with the following command:
`curl -XPUT 'http://localhost:9200/nxapi/'`
## Populating ElasticSearch with data
* Enable learning mode
* Browse website to generate data in the logfile
* Change into nxapi directory
* Load the data from the log file into ElasticSearch with the following command:
`./nxtool.py -c nxapi.json --files=/PATH/TO/LOGFILE.LOG`
* Check if data was added correctly:
`curl -XPOST "http://localhost:9200/nxapi/events/_search?pretty" -d '{}' `
* Check if nxtool sees it correctly:
`./nxtool.py -c nxapi.json -x`
# Simple usage approach
##1. Get info about db
$ ./nxtool.py -x --colors -c nxapi.json
Will issue a summary of database content, including :
* Ratio between tagged/untagged events.
Tagging of events is an important notion that allows you to know how well you are doing on learning.
Let's say you just started learning. You will have a tag ratio of 0%, which means you didn't write any
whitelists for recent events. Once you start generating whitelists, you can provide those (`-w /tmp/wl.cf --tag`)
and nxapi will mark those events in the database as whitelisted, excluding them from future generation process.
It allows you to speed up the generation process, but mainly to know how well you dealt with recent false positives.
You can also use the tagging mechanism to exclude obvious attack patterns from learning. If X.Y.Z.W keeps hammering my website and polluting my log, I can provide nxapi with the ip (`-i /tmp/ips.txt --tag`) to tag and exclude them from process.
* Top servers.
A TOP10 list of dst hosts raising the most exceptions.
* Top URI(s).
A TOP10 list of dst URIs raising the most exceptions. It is very useful in combination with --filter to generate whitelists for specific URI(s).
* Top Zones.
List of most active zones of exceptions.
##2. Generate whitelists
Let's say I had the following output :
./nxtool.py -c nxapi.json -x --colors
# Whitelist(ing) ratio :
# false 79.96 % (total:196902/246244)
# true 20.04 % (total:49342/246244)
# Top servers :
# www.x1.fr 21.93 % (total:43181/196915)
# www.x2.fr 15.21 % (total:29945/196915)
...
# Top URI(s) :
# /foo/bar/test 8.55 % (total:16831/196915)
# /user/register 5.62 % (total:11060/196915)
# /index.php/ 4.26 % (total:8385/196915)
...
# Top Zone(s) :
# BODY 41.29 % (total:81309/196924)
# HEADERS 23.2 % (total:45677/196924)
# BODY|NAME 16.88 % (total:33243/196924)
# ARGS 12.47 % (total:24566/196924)
# URL 5.56 % (total:10947/196924)
# ARGS|NAME 0.4 % (total:787/196924)
# FILE_EXT 0.2 % (total:395/196924)
# Top Peer(s) :
# ...
I want to generate whitelists for x1.fr, so I will get more precise statistics first :
./nxtool.py -c nxapi.json -x --colors -s www.x1.fr
...
# Top URI(s) :
# /foo/bar/test 8.55 % (total:16831/196915)
# /index.php/ 4.26 % (total:8385/196915)
...
I will then attempt to generate whitelists for the `/foo/bar/test` page, that seems to trigger most events :
`Take note of the --filter option, that allows me to work whitelists only for this URI.
Filters can specify any field : var_name, zone, uri, id, whitelisted, content, country, date ...
However, take care, they don't support regexp yet.
Take note as well of --slack usage, that allows to ignore success/warning criterias, as my website has too few
visitors, making legitimate exceptions appear as false positives.`
./nxtool.py -c nxapi.json -s www.x1.fr -f --filter 'uri /foo/bar/test' --slack
...
#msg: A generic whitelist, true for the whole uri
#Rule (1303) html close tag
#total hits 126
#content : lyiuqhfnp,+<a+href="http://preemptivelove.org/">Cialis+forum</a>,+KKSXJyE,+[url=http://preemptivelove.org/]Viagra+or+cialis[/url],+XGRgnjn,+http
#content : 4ThLQ6++<a+href="http://aoeymqcqbdby.com/">aoeymqcqbdby</a>,+[url=http://ndtofuvzhpgq.com/]ndtofuvzhpgq[/url],+[link..
#peers : x.y.z.w
...
#uri : /faq/
#var_name : numcommande
#var_name : comment
...
# success : global_rule_ip_ratio is 58.82
# warnings : rule_ip is 10
BasicRule wl:1303 "mz:$URL:/foo/bar/test|BODY";
nxtool attempts to provide extra information to allow user to decides wether it's a false positive :
* content : actual HTTP content, only present if $naxsi_extensive_log is set to 1
* uri : example(s) of URI on which the event was triggered
* var_name : example(s) of variable names in which the content was triggered
* success and warnings : nxapi will provide you with scoring information (see 'scores').
##3. Interactive whitelist generation
Another way of creating whitelists is to use the -g option. This option provide
an interactive way to generate whitelists. This option use the EDITOR env
variable and uses it to iterate over all the servers available inside your elastic
search instance (if the EDITOR env variable isn't set it will try to use `vi`.
You can either delete or comment with a `#` at the beginning the line you don't
want to keep. After the server selection, it will iterate on each available uri
and zone for earch server. If you want to use regex, only available for uri,
you can add a `?` at the beginning of each line where you want to use a regex:
uri /fr/foo/ ...
?uri /[a-z]{2,}/foo ...
The -g options once all the selection is done, will attempt to generate the wl
with the same behaviour as -f option, and write the result inside the path the
typical output when generating wl is:
generating wl with filters {u'whitelisted': u'false', 'uri': '/fr/foo', 'server': 'x.com'}
Writing in file: /tmp/server_x.com_0.wl
As you can see you'll see each filter and each file for each selections.
##4. Tagging events
Once I chose the whitelists that I think are appropriate, I will write them in a whitelist file.
Then, I can tag corresponding events :
nxtool.py -c nxapi.json -w /tmp/whitelist.conf --tag
And then, if I look at the report again, I will see a bump in the tagged ratio of events.
Once the ratio is high enough or the most active URLs & IPs are false positives, it's done!
# Tips and tricks for whitelist generation
* `--filter`
--filter is your friend, especially if you have a lot of exceptions.
By narrowing the search field for whitelists, it will increase speed, and reduce false positives.
* use `-t` instead of `-f`
-f is the "dumb" generation mode, where all templates will be attempted.
if you provide something like `-t "ARGS/*"` only templates specific to ARGS whitelists will be attempted.
* Create your own templates
If you manage applications that do share code/framework/technology, you will quickly find yourself
generating the same wl again and again. Stop that! Write your own templates, improving generation time,
accuracy and reducing false positives. Take a practical example:
I'm dealing with magento, like a *lot*. One of the recurring patterns is the "onepage" checkout, so I created specific templates:
{
"_success" : { "rule_ip" : [ ">", "1"]},
"_msg" : "Magento checkout page (BODY|NAME)",
"?uri" : "/checkout/onepage/.*",
"zone" : "BODY|NAME",
"id" : "1310 OR 1311"
}
# Supported options
## Scope/Filtering options
`-s SERVER, --server=SERVER`
Restrict context of whitelist generation or stats display to specific FQDN.
`--filter=FILTER`
A filter (in the form of a dict) to merge with
existing templates/filters: 'uri /foobar zone BODY'.
You can combine several filters, for example : `--filter "country FR" --filter "uri /foobar"`.
## Whitelist generation options
`-t TEMPLATE, --template=TEMPLATE`
Given a path to a template file, attempt to generate matching whitelists.
Possible whitelists will be tested versus database, only the ones with "good" scores will be kept.
if TEMPLATE starts with a '/' it's treated as an absolute path. Else, it's expanded starting in tpl/ directory.
`-f, --full-auto`
Attempts whitelist generation for all templates present in rules_path.
`--slack`
Sets nxtool to ignore scores and display all generated whitelists.
## Tagging options
`-w WL_FILE, --whitelist-path=WL_FILE`
Given a whitelist file, finds matching events in database.
`-i IPS, --ip-path=IPS`
Given a list of ips (separatated by \n), finds matching events in database.
`--tag`
Performs tagging. If not specified, matching events are simply displayed.
## Statistics generation options
`-x, --stats`
Generate statistics about current database.
## Importing data
**Note:** All acquisition features expect naxsi EXLOG/FMT content.
` --files=FILES_IN Path to log files to parse.̀`
Supports glob, gz bz2, ie. --files "/var/log/nginx/*mysite.com*error.log*"
`--fifo=FIFO_IN Path to a FIFO to be created & read from. [infinite]`
Creates a FIFO, increases F_SETPIPE_SZ, and reads on it. mostly useful for reading directly from syslog/nginx logs.
`--stdin Read from stdin.`
`--no-timeout Disable timeout on read operations (stdin/fifo).̀
# Understanding templates
Templates do have a central role within nxapi.
By default only generic ones are provided, you should create your own.
First, look at a generic one to understand how it works :
{
"zone" : "HEADERS",
"var_name" : "cookie",
"id" : "?"
}
Here is how nxtool will use this to generate whitelists:
1. extract global_filters from nxapi.json, and create the base ES filter :
{ "whitelisted" : "false" }
2. merge base ES filter with provided cmd line filter (--filter, -s www.x1.fr)
{ "whitelisted" : "false", "server" : "www.x1.fr" }
3. For each static field of the template, merge it in base ES filter :
{ "whitelisted" : "false", "server" : "www.x1.fr", "zone" : "HEADERS", "var_name" : "cookie" }
4. For each field to be expanded (value is `?`) :
4.1. select all possible values for this field (id) matching base ES filter, (ie. 1000 and 1001 here)
4.2. attempt to generate a whitelist for each possible value, and evaluate its scores.
{ "whitelisted" : "false", "server" : "www.x1.fr", "zone" : "HEADERS", "var_name" : "cookie", "id" : "1000"}
{ "whitelisted" : "false", "server" : "www.x1.fr", "zone" : "HEADERS", "var_name" : "cookie", "id" : "1001"}
5. For each final set that provided results, output a whitelist.
Templates support :
* `"field" : "value"` : A static value that must be present in exception for template to be true.
* `"field" : "?"` : A value that must be expanded from database content (while matching static&global filters).
unique values for "field" will then be used for whitelist generation (one whitelist per unique value).
* `"?field" : "regexp"` : A regular expression for a field content that will be searched in database.
unique values matching regexp for "field" will then be used for whitelist generation (one whitelist per unique value).
* `"_statics" : { "field" : "value" }` : A static value to be used at whitelist generation time. Does not take part in search process,
only at 'output' time. ie. `"_statics" : { "id" : "0" }` is the only way to have a whitelist outputing a 'wl:0'.
* `"_msg" : "string" ` : a text message to help the user understand the template purpose.
* `"_success" : { ... }` : A dict supplied to overwrite/complete 'global' scoring rules.
* `"_warnings" : { ... }` : A dict supplied to overwrite/complete 'global' scoring rules.
# Understanding scoring
Scoring mechanism :
* Scoring mechanism is a very trivial approach, relying on three kinds of "scoring" expressions : _success, _warning, _deny.
* Whenever a _success rule is met while generating a whitelist, it will INCREASE the "score" of the whitelist by 1.
* Whenever a _warning rule is met while generating a whitelist, it will DECREASE the "score" of the whitelist by 1.
* Whenever a _deny rule is met while generating a whitelist, it will disable the whitelist output.
_note:_
In order to understand scoring mechanism, it is crucial to tell the difference between a template and a rule.
A template is a .json file which can match many events. A rule is usually a subpart of a template results.
For example, if we have this data :
[ {"id" : 1, "zone" : HEADERS, ip:A.A.A.A},
{"id" : 2, "zone" : HEADERS, ip:A.A.A.A},
{"id" : 1, "zone" : ARGS, ip:A.B.C.D}
]
And this template :
{"id" : 1, "zone" : "?"}
Well, template_ip would be 2, as 2 peers triggered events with ID:1.
However, rule_ip would be 1, as the two generated rules ('id:1 mz:ARGS' and 'id:1 mz:HEADERS'),
were triggered each by one unique peer.
If --slack is present, scoring is ignored, and all possible whitelists are displayed.
In normal conditions, whitelists with more than 0 points are displayed.
The default filters enabled in nxapi, from nxapi.json :
"global_warning_rules" : {
"rule_ip" : ["<=", 10 ],
"global_rule_ip_ratio" : ["<", 5]
},
"global_success_rules" : {
"global_rule_ip_ratio" : [">=", 10],
"rule_ip" : [">=", 10]
},
"global_deny_rules" : {
"global_rule_ip_ratio" : ["<", 2]
},
* rule_N <= X : "at least" X uniq(N) where present in the specific events from which the WL is generated.
* '"rule_ip" : ["<=", 10 ],' : True if less than 10 unique IPs hit the event
* '"rule_var_name" : [ "<=", "5" ]' : True if less than 5 unique variable names hit the event
* template_N <= X : "at least" X uniq(N) where present in the specific events from which the WL is generated.
* Note the difference with "rule_X" rules.
* global_rule_ip_ratio < X : "at least" X% of the users that triggered events triggered this one as well.
* however, ration can theorically apply to anything, just ip_ratio is the most common.

View File

@@ -0,0 +1,318 @@
{
"title": "naxsi-current+inspect (last 1 hour)",
"services": {
"query": {
"idQueue": [
1,
2,
3,
4
],
"list": {
"0": {
"id": 0,
"type": "topN",
"query": "*",
"alias": "",
"color": "#6ED0E0",
"pin": false,
"enable": true,
"field": "server",
"size": 10,
"union": "AND"
}
},
"ids": [
0
]
},
"filter": {
"idQueue": [
0,
1,
2
],
"list": {
"0": {
"type": "time",
"field": "date",
"from": "now-1h",
"to": "now",
"mandate": "must",
"active": true,
"alias": "",
"id": 0
},
"1": {
"type": "querystring",
"query": "*preprod*",
"mandate": "mustNot",
"active": true,
"alias": "",
"id": 1
}
},
"ids": [
0,
1
]
}
},
"rows": [
{
"title": "events",
"height": "250px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 3,
"editable": true,
"type": "terms",
"loadingEditor": false,
"queries": {
"mode": "all",
"ids": [
0
]
},
"field": "server",
"exclude": [],
"missing": true,
"other": true,
"size": 30,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "vertical",
"chart": "bar",
"counter_pos": "below",
"spyable": true,
"title": "sites",
"tmode": "terms",
"tstat": "total",
"valuefield": ""
},
{
"span": 9,
"editable": true,
"type": "histogram",
"loadingEditor": false,
"mode": "count",
"time_field": "date",
"queries": {
"mode": "all",
"ids": [
0
]
},
"value_field": null,
"auto_int": false,
"resolution": 100,
"interval": "1m",
"intervals": [
"auto",
"1s",
"1m",
"5m",
"10m",
"30m",
"1h",
"3h",
"12h",
"1d",
"1w",
"1M",
"1y"
],
"fill": 1,
"linewidth": 3,
"timezone": "browser",
"spyable": true,
"zoomlinks": true,
"bars": true,
"stack": false,
"points": false,
"lines": false,
"legend": true,
"x-axis": true,
"y-axis": true,
"percentage": false,
"interactive": false,
"options": true,
"tooltip": {
"value_type": "individual",
"query_as_alias": true
},
"title": "history",
"scale": 1,
"y_format": "none",
"grid": {
"max": null,
"min": 0
},
"annotate": {
"enable": false,
"query": "*",
"size": 20,
"field": "_type",
"sort": [
"_score",
"desc"
]
},
"pointradius": 5,
"show_query": true,
"legend_counts": true,
"zerofill": true,
"derivative": false
}
],
"notice": false
},
{
"title": "timelines",
"height": "150px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 12,
"editable": true,
"type": "table",
"loadingEditor": false,
"size": 100,
"pages": 5,
"offset": 0,
"sort": [
"date",
"desc"
],
"overflow": "min-height",
"fields": [
"server",
"uri",
"zone",
"var_name",
"ip",
"id",
"content",
"date"
],
"highlight": [
null
],
"sortable": true,
"header": true,
"paging": true,
"field_list": false,
"all_fields": false,
"trimFactor": 300,
"localTime": false,
"timeField": "date",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"style": {
"font-size": "9pt"
},
"normTimes": true
}
],
"notice": false
}
],
"editable": true,
"failover": false,
"index": {
"interval": "none",
"pattern": "[logstash-]YYYY.MM.DD",
"default": "nxapi",
"warm_fields": true
},
"style": "dark",
"panel_hints": true,
"pulldowns": [
{
"type": "query",
"collapse": true,
"notice": false,
"enable": true,
"query": "*",
"pinned": true,
"history": [
"*",
"www.forum-fic.com"
],
"remember": 10
},
{
"type": "filtering",
"collapse": false,
"notice": true,
"enable": true
}
],
"nav": [
{
"type": "timepicker",
"collapse": false,
"notice": false,
"enable": true,
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"timefield": "date",
"now": true,
"filter_id": 0
}
],
"loader": {
"save_gist": true,
"save_elasticsearch": true,
"save_local": true,
"save_default": true,
"save_temp": true,
"save_temp_ttl_enable": true,
"save_temp_ttl": "30d",
"load_gist": false,
"load_elasticsearch": true,
"load_elasticsearch_size": 20,
"load_local": false,
"hide": false
},
"refresh": "10s"
}

View File

@@ -0,0 +1,261 @@
AD:42.5462450,1.6015540
AE:23.4240760,53.8478180
AF:33.939110,67.7099530
AG:47.38766640,8.25542950
AI:18.2205540,-63.06861499999999
AL:32.31823140,-86.9022980
AM:-3.41684270,-65.85606460
AN:12.2260790,-69.0600870
AO:47.5162310,14.5500720
AQ:-82.86275189999999,-135.0
AR:35.201050,-91.83183339999999
AS:-14.2709720,-170.1322170
AT:47.5162310,14.5500720
AU:-25.2743980,133.7751360
AW:12.521110,-69.9683380
AX:60.33854850,20.27125850
AZ:34.04892810,-111.09373110
BA:43.9158860,17.6790760
BB:13.1938870,-59.5431980
BD:23.6849940,90.3563310
BE:50.5038870,4.4699360
BF:12.2383330,-1.5615930
BG:42.7338830,25.485830
BH:-19.91906770,-43.93857470
BI:-3.3730560,29.9188860
BJ:9.307689999999999,2.3158340
BM:32.3213840,-64.75736999999999
BN:4.5352770,114.7276690
BO:7.95517910,-11.74099460
BR:-14.2350040,-51.925280
BS:25.034280,-77.39627999999999
BT:27.5141620,90.4336010
BV:47.65806030,-94.87917419999999
BW:-22.3284740,24.6848660
BY:53.7098070,27.9533890
BZ:17.1898770,-88.49764999999999
CA:36.7782610,-119.41793240
CC:-26.58576560,-60.95400730
CD:-4.0383330,21.7586640
CF:6.611110999999999,20.9394440
CG:-0.2280210,15.8276590
CH:46.8181880,8.227511999999999
CI:7.539988999999999,-5.547079999999999
CK:-21.2367360,-159.7776710
CL:-35.6751470,-71.5429690
CM:7.369721999999999,12.3547220
CN:35.861660,104.1953970
CO:39.55005070,-105.78206740
CR:9.748916999999999,-83.7534280
CS:39.56441050,16.25221430
CU:21.5217570,-77.7811670
CV:16.0020820,-24.0131970
CX:-10.4475250,105.6904490
CY:35.1264130,33.4298590
CZ:49.81749199999999,15.4729620
DE:51.165691,10.451526
DJ:11.8251380,42.5902750
DK:56.263920,9.5017850
DM:15.4149990,-61.37097600000001
DO:18.7356930,-70.1626510
DZ:28.0338860,1.6596260
EC:-32.29684020,26.4193890
EE:58.5952720,25.0136070
EG:26.8205530,30.8024980
EH:24.2155270,-12.8858340
ER:15.1793840,39.7823340
ES:-19.18342290,-40.30886260
ET:9.145000000000001,40.4896730
FI:61.92410999999999,25.7481510
FJ:-17.7133710,178.0650320
FK:-51.7962530,-59.5236130
FM:-25.39459690,-58.73736339999999
FO:-25.39459690,-58.73736339999999
FR:46.2276380,2.2137490
FX:27.9026210,-82.7447310
GA:32.15743510,-82.90712300000001
GB:55.3780510,-3.4359730
GD:12.11650,-61.67899999999999
GE:52.0451550,5.871823399999999
GF:3.9338890,-53.1257820
GH:7.9465270,-1.0231940
GI:36.1377410,-5.3453740
GL:71.7069360,-42.6043030
GM:13.4431820,-15.3101390
GN:9.9455870,-9.6966450
GP:-26.27075930,28.11226790
GQ:1.6508010,10.2678950
GR:39.0742080,21.8243120
GS:-54.4295790,-36.5879090
GT:15.7834710,-90.23075899999999
GU:13.4443040,144.7937310
GW:11.8037490,-15.1804130
GY:4.8604160,-58.930180
HK:22.3964280,114.1094970
HM:-53.081810,73.50415799999999
HN:15.1999990,-86.2419050
HR:45.10,15.20
HT:18.9711870,-72.28521499999999
HU:47.1624940,19.5033040
ID:44.06820190,-114.74204080
IE:53.412910,-8.243890
IL:40.63312490,-89.39852830
IN:40.26719410,-86.13490190
IO:-6.3431940,71.8765190
IQ:33.2231910,43.6792910
IR:32.4279080,53.6880460
IS:64.96305099999999,-19.0208350
IT:41.871940,12.567380
JM:18.1095810,-77.29750799999999
JO:30.5851640,36.2384140
JP:36.2048240,138.2529240
KE:-0.0235590,37.9061930
KG:41.204380,74.7660980
KH:12.5656790,104.9909630
KI:-3.3704170,-168.7340390
KM:-11.8750010,43.8722190
KN:17.3578220,-62.7829980
KP:40.3398520,127.5100930
KR:35.9077570,127.7669220
KW:29.311660,47.4817660
KY:37.83933320,-84.27001790
KZ:48.0195730,66.92368399999999
LA:31.24482340,-92.14502449999999
LB:33.8547210,35.8622850
LC:45.93829410,9.3857290
LI:51.44272380,6.06087260
LK:7.873053999999999,80.77179699999999
LR:6.4280550,-9.429499000000002
LS:-29.6099880,28.2336080
LT:55.1694380,23.8812750
LU:49.8152730,6.129582999999999
LV:56.8796350,24.6031890
LY:26.33510,17.2283310
MA:42.40721070,-71.38243740
MC:43.73841760000001,7.424615799999999
MD:39.04575490,-76.64127119999999
MG:-17.9301780,-43.79084530
MH:19.75147980,75.71388840
MK:41.6086350,21.7452750
ML:17.5706920,-3.9961660
MM:21.9139650,95.95622299999999
MN:46.7295530,-94.68589980
MO:37.96425290,-91.83183339999999
MP:-25.5653360,30.52790960
MQ:14.6415280,-61.0241740
MR:21.007890,-10.9408350
MS:32.35466790,-89.39852830
MT:46.87968220,-110.36256580
MU:-20.3484040,57.55215200000001
MV:3.2027780,73.220680
MW:-13.2543080,34.3015250
MX:23.6345010,-102.5527840
MY:4.2104840,101.9757660
MZ:-18.6656950,35.5295620
NA:-22.957640,18.490410
NC:35.75957310,-79.01929969999999
NE:41.49253740,-99.90181310
NF:-29.0408350,167.9547120
NG:9.0819990,8.675276999999999
NI:12.8654160,-85.2072290
NL:53.13550910,-57.66043640
NO:48.10807699999999,15.80495580
NP:28.3948570,84.12400799999999
NR:-0.5227780,166.9315030
NU:70.29977110,-83.10757690
NZ:-40.9005570,174.8859710
OM:21.5125830,55.9232550
PA:41.20332160,-77.19452470
PE:46.5107120,-63.41681359999999
PF:-17.6797420,-149.4068430
PG:5.263234100000001,100.48462270
PH:12.8797210,121.7740170
PK:30.3753210,69.34511599999999
PL:51.9194380,19.1451360
PM:46.9419360,-56.271110
PN:-24.7036150,-127.4393080
PR:-25.25208880,-52.02154150
PS:31.9521620,35.2331540
PT:39.39987199999999,-8.2244540
PW:7.514979999999999,134.582520
PY:-23.4425030,-58.4438320
QA:25.3548260,51.1838840
RE:-21.1151410,55.5363840
RO:45.9431610,24.966760
RU:61.524010,105.3187560
RW:-1.9402780,29.8738880
SA:23.8859420,45.0791620
SB:-9.645709999999999,160.1561940
SC:33.8360810,-81.16372450
SD:43.96951480,-99.90181310
SE:60.12816100000001,18.6435010
SG:1.3520830,103.8198360
SH:54.20907680,9.5889410
SI:46.1512410,14.9954630
SJ:-30.87245870,-68.52471489999999
SK:52.93991590,-106.45086390
SL:-33.87690180,-66.23671720
SM:43.942360,12.4577770
SN:14.4974010,-14.4523620
SO:5.1521490,46.1996160
SR:3.9193050,-56.0277830
ST:0.186360,6.613080999999999
SU:30.6516520,104.0759310
SV:46.8181880,8.227511999999999
SY:34.80207499999999,38.9968150
SZ:-26.5225030,31.4658660
TC:21.6940250,-71.7979280
TD:15.4541660,18.7322070
TF:-53.86711170,-69.2972140
TG:8.6195430,0.8247820
TH:15.8700320,100.9925410
TJ:38.8610340,71.2760930
TK:-8.967362999999999,-171.8558810
TL:-8.8742170,125.7275390
TM:38.9697190,59.5562780
TN:35.51749130,-86.58044730
TO:-11.40987370,-48.71914229999999
TP:37.87774020,12.71351210
TR:38.9637450,35.2433220
TT:10.6918030,-61.2225030
TV:45.78572920,12.19702880
TW:23.697810,120.9605150
TZ:-6.3690280,34.8888220
UA:48.3794330,31.165580
UG:1.3733330,32.2902750
UK:55.3780510,-3.4359730
UM:14.00451050,-176.70562750
US:37.090240,-95.7128910
UY:-32.5227790,-55.7658350
UZ:41.3774910,64.5852620
VA:37.43157340,-78.65689420
VC:12.9843050,-61.2872280
VE:6.423750,-66.589730
VG:18.4206950,-64.6399680
VI:18.3357650,-64.89633499999999
VN:14.0583240,108.2771990
VU:-15.3767060,166.9591580
WF:-13.7687520,-177.1560970
WS:-13.7590290,-172.1046290
YE:15.5527270,48.5163880
YT:64.28232740,-135.0
YU:39.8408430,114.5889030
ZA:-30.5594820,22.9375060
ZM:-13.1338970,27.8493320
ZR:51.80736570,5.70867610
ZW:-19.0154380,29.1548570
BIZ:42.91333330,44.17611110
COM:45.81203170,9.085614999999999
EDU:38.5333020,-121.7879780
GOV:-12.27130120,136.82331380
INT:36.13685970,-80.22767949999999
MIL:60.164480,132.6396450
NET:58.05874000000001,138.2498550
ORG:30.06971249999999,-93.79811680
PRO:41.82904250,-94.15938679999999
AERO:54.85890260,10.38748130
ARPA:39.70400050000001,45.12065270000001
COOP:34.14125450,-118.37270070
INFO:3.134430,101.686250
NAME:27.70287990,85.32163220
NATO:50.8762830,4.4219710

View File

@@ -0,0 +1,38 @@
{
"elastic" : {
"host" : "127.0.0.1:9200",
"use_ssl" : false,
"index" : "nxapi",
"doctype" : "events",
"default_ttl" : "7200",
"max_size" : "1000",
"version" : "2"
},
"syslogd": {
"host" : "0.0.0.0",
"port" : "51400"
},
"global_filters" : {
"whitelisted" : "false"
},
"global_warning_rules" : {
"rule_ip" : ["<=", 10 ],
"global_rule_ip_ratio" : ["<", 5]
},
"global_success_rules" : {
"global_rule_ip_ratio" : [">=", 10],
"rule_ip" : [">=", 10]
},
"global_deny_rules" : {
"global_rule_ip_ratio" : ["<", 2]
},
"naxsi" : {
"rules_path" : "/etc/nginx/naxsi_core.rules",
"template_path" : [ "tpl/"],
"geoipdb_path" : "nx_datas/country2coords.txt"
},
"output" : {
"colors" : "true",
"verbosity" : "5"
}
}

View File

View File

@@ -0,0 +1,540 @@
# Parses a line of log, and potentially returns a dict of dict.
import sys
import pprint
import time
import glob
import logging
import string
import urlparse
import itertools
import gzip
import bz2
from select import select
from functools import partial
import datetime
#import urllib2 as urllib
import json
import copy
from elasticsearch.helpers import bulk
import os
import socket
class NxReader():
""" Feeds the given injector from logfiles """
def __init__(self, acquire_fct, stdin=False, lglob=[], fd=None,
stdin_timeout=5, syslog=None, syslogport=None, sysloghost=None):
self.acquire_fct = acquire_fct
self.files = []
self.timeout = stdin_timeout
self.stdin = False
self.fd = fd
self.syslog = syslog
self.syslogport = syslogport
self.sysloghost = sysloghost
if stdin is not False:
logging.warning("Using stdin")
self.stdin = True
return
if len(lglob) > 0:
for regex in lglob:
self.files.extend(glob.glob(regex))
logging.warning("List of files :"+str(self.files))
if self.fd is not None:
logging.warning("Reading from supplied FD (fifo ?)")
if self.syslog is not None:
logging.warning("Reading from syslog socket")
def read_fd(self, fd):
if self.timeout is not None:
rlist, _, _ = select([fd], [], [], self.timeout)
else:
rlist, _, _ = select([fd], [], [])
success = discard = not_nx = malformed = 0
if rlist:
s = fd.readline()
if s == '':
return s
self.acquire_fct(s)
return True
else:
return False
def read_syslog(self, syslog):
if self.syslogport is not None:
host = self.sysloghost
port = int(self.syslogport)
else:
print "Unable to get syslog host and port"
sys.exit(1)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
s.bind((host,port))
s.listen(10)
except socket.error as msg:
print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
pass
print "Listening for syslog incoming "+host+" port "+ str(self.syslogport)
conn, addr = s.accept()
syslog = conn.recv(1024)
if syslog == '':
return False
conn.send(syslog)
self.acquire_fct(syslog)
return True
def read_files(self):
if self.fd is not None:
while True:
ret = self.read_fd(self.fd)
if ret == '':
return False
return 0
if self.syslog is not None:
ret = ""
while self.read_syslog(self.syslog) is True:
pass
return 0
count = 0
total = 0
for lfile in self.files:
success = not_nx = discard = malformed = fragmented = reunited = 0
logging.info("Importing file "+lfile)
try:
if lfile.endswith(".gz"):
print "GZ open"
fd = gzip.open(lfile, "rb")
elif lfile.endswith(".bz2"):
print "BZ2 open"
fd = bz2.BZ2File(lfile, "r")
else:
print "log open"
fd = open(lfile, "r")
except:
logging.critical("Unable to open file : "+lfile)
return 1
for line in fd:
self.acquire_fct(line)
fd.close()
return 0
class NxParser():
def __init__(self):
# output date format
self.out_date_format = "%Y/%m/%d %H:%M:%S"
# Start of Data / End of data marker
self.sod_marker = [' [error] ', ' [debug] ']
self.eod_marker = [', client: ', '']
# naxsi data keywords
self.naxsi_keywords = [" NAXSI_FMT: ", " NAXSI_EXLOG: "]
# keep track of fragmented lines (seed_start=X seed_end=X)
self.reunited_lines = 0
self.fragmented_lines = 0
self.multiline_buf = {}
# store generated objects
self.dict_buf = []
self.bad_line = 0
def unify_date(self, date):
""" tries to parse a text date,
returns date object or None on error """
idx = 0
res = ""
supported_formats = [
"%b %d %H:%M:%S",
"%b %d %H:%M:%S",
"%Y/%m/%d %H:%M:%S",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%dT%H:%M:%S"
# "%Y-%m-%dT%H:%M:%S+%:z"
]
while date[idx] == " " or date[idx] == "\t":
idx += 1
success = 0
for date_format in supported_formats:
nb_sp = date_format.count(" ")
clean_date = string.join(date.split(" ")[:nb_sp+1], " ")
# strptime does not support numeric time zone, hack.
idx = clean_date.find("+")
if idx != -1:
clean_date = clean_date[:idx]
try:
x = time.strptime(clean_date, date_format)
z = time.strftime(self.out_date_format, x)
success = 1
break
except:
#print "'"+clean_date+"' not in format '"+date_format+"'"
pass
if success == 0:
logging.critical("Unable to parse date format :'"+date+"'")
return None
return z
# returns line, ready for parsing.
# returns none if line contains no naxsi data
def clean_line(self, line):
""" returns an array of [date, "NAXSI_..."] from a
raw log line. 2nd item starts at first naxsi keyword
found. """
ret = [None, None]
# Don't try to parse if no naxsi keyword is found
for word in self.naxsi_keywords:
idx = line.find(word)
if idx != -1:
break
if idx == -1:
return None
line = line.rstrip('\n')
for mark in self.sod_marker:
date_end = line.find(mark)
if date_end != -1:
break
for mark in self.eod_marker:
if mark == '':
data_end = len(line)
break
data_end = line.find(mark)
if data_end != -1:
break
if date_end == -1 or data_end == 1:
self.bad_line += 1
return None
ret[0] = self.unify_date(line[:date_end])
chunk = line[date_end:data_end]
md = None
for word in self.naxsi_keywords:
idx = chunk.find(word)
if (idx != -1):
ret[1] = chunk[idx+len(word):]
if ret[1] is None:
self.bad_line += 1
return None
return ret
# attempts to clean and parse a line
def parse_raw_line(self, line):
clean_dict = self.clean_line(line)
if clean_dict is None:
logging.debug("not a naxsi line")
return None
nlist = self.parse_line(clean_dict[1])
if nlist is None:
return None
return {'date' : clean_dict[0], 'events' : nlist}
def parse_line(self, line):
ndict = self.tokenize_log(line)
if ndict is None:
logging.critical("Unable to tokenize line "+line)
return None
nlist = self.demult_exception(ndict)
return nlist
def demult_exception(self, event):
demult = []
if event.get('seed_start') and event.get('seed_end') is None:
#First line of a multiline naxsi fmt
self.multiline_buf[event['seed_start']] = event
self.fragmented_lines += 1
return demult
elif event.get('seed_start') and event.get('seed_end'):
# naxsi fmt is very long, at least 3 lines
self.fragmented_lines += 1
if self.multiline_buf.get(event['seed_end']) is None:
logging.critical("Orphans end {0} / start {1}".format(event['seed_end'],
event['seed_start']))
return demult
self.multiline_buf[event['seed_end']].update(event)
self.multiline_buf[event['seed_start']] = self.multiline_buf[event['seed_end']]
del self.multiline_buf[event['seed_end']]
return demult
elif event.get('seed_start') is None and event.get('seed_end'):
# last line of the naxsi_fmt, just update the dict, and parse it like a normal line
if self.multiline_buf.get(event['seed_end']) is None:
logging.critical('Got a line with seed_end {0}, but i cant find a matching seed_start...\nLine will probably be incomplete'.format(event['seed_end']))
return demult
self.fragmented_lines += 1
self.reunited_lines += 1
self.multiline_buf[event['seed_end']].update(event)
event = self.multiline_buf[event['seed_end']]
del self.multiline_buf[event['seed_end']]
entry = {}
for x in ['uri', 'server', 'content', 'ip', 'date', 'var_name', 'country']:
entry[x] = event.get(x, '')
clean = entry
# NAXSI_EXLOG lines only have one triple (zone,id,var_name), but has non-empty content
if 'zone' in event.keys():
if 'var_name' in event.keys():
entry['var_name'] = event['var_name']
entry['zone'] = event['zone']
entry['id'] = event['id']
demult.append(entry)
return demult
# NAXSI_FMT can have many (zone,id,var_name), but does not have content
# we iterate over triples.
elif 'zone0' in event.keys():
commit = True
for i in itertools.count():
entry = copy.deepcopy(clean)
zn = ''
vn = ''
rn = ''
if 'var_name' + str(i) in event.keys():
entry['var_name'] = event['var_name' + str(i)]
if 'zone' + str(i) in event.keys():
entry['zone'] = event['zone' + str(i)]
else:
commit = False
break
if 'id' + str(i) in event.keys():
entry['id'] = event['id' + str(i)]
else:
commit = False
break
if commit is True:
demult.append(entry)
else:
logging.warning("Malformed/incomplete event [missing subfield]")
logging.info(pprint.pformat(event))
return demult
return demult
else:
logging.warning("Malformed/incomplete event [no zone]")
logging.info(pprint.pformat(event))
return demult
def tokenize_log(self, line):
"""Parses a naxsi exception to a dict,
1 on error, 0 on success"""
odict = urlparse.parse_qs(line)
# one value per key, reduce.
for x in odict.keys():
odict[x][0] = odict[x][0].replace('\n', "\\n")
odict[x][0] = odict[x][0].replace('\r', "\\r")
odict[x] = odict[x][0]
# check for incomplete/truncated lines
if 'zone0' in odict.keys():
for i in itertools.count():
is_z = is_id = False
if 'zone' + str(i) in odict.keys():
is_z = True
if 'id' + str(i) in odict.keys():
is_id = True
if is_z is True and is_id is True:
continue
if is_z is False and is_id is False:
break
# clean our mess if we have to.
try:
del (odict['zone' + str(i)])
del (odict['id' + str(i)])
del (odict['var_name' + str(i)])
except:
pass
break
return odict
class NxInjector():
def __init__(self, auto_commit_limit=400):
self.nlist = []
self.auto_commit = auto_commit_limit
self.total_objs = 0
self.total_commits = 0
# optional
def get_ready(self):
pass
def insert(self, obj):
self.nlist.append(obj)
if self.auto_commit > 0 and len(self.nlist) > self.auto_commit:
return self.commit()
return True
def commit(self):
return False
def stop(self):
self.commit()
pass
class ESInject(NxInjector):
def __init__(self, es, cfg, auto_commit_limit=400):
#
# self.nlist = []
# self.auto_commit = auto_commit_limit
# super(ESInject, self).__init__(value=20)
NxInjector.__init__(self, auto_commit_limit)
self.es = es
self.cfg = cfg
self.es_version = cfg["elastic"]["version"]
# self.host = host
# self.index = index
# self.collection = collection
# self.login = login
# self.password = password
self.set_mappings()
# def esreq(self, pidx_uri, data, method="PUT"):
# try:
# body = json.dumps(data)
# except:
# print "Unable to dumps data :"+data
# return False
# try:
# print "=>>"+"http://"+self.host+"/"+self.index+pidx_uri
# req = urllib.Request("http://"+self.host+"/"+self.index+pidx_uri, data=body)
# f = urllib.urlopen(req)
# resp = f.read()
# print resp
# f.close()
# except:
# # import traceback
# # print 'generic exception: ' + traceback.format_exc()
# # print "!!Unexpected error:", sys.exc_info()[0]
# #print resp
# logging.critical("Unable to emit request.")
# sys.exit(-1)
# return False
# return True
def set_mappings(self):
if self.es_version == '5':
try:
self.es.indices.create(
index=self.cfg["elastic"]["index"],
ignore=400 # Ignore 400 cause by IndexAlreadyExistsException when creating an index
)
except Exception as idxadd_error:
print "Unable to create the index/collection for ES 5.X: "+self.cfg["elastic"]["index"]+" "+self.cfg["elastic"]["doctype"]+ ", Error: " + str(idxadd_error)
try:
self.es.indices.put_mapping(
index=self.cfg["elastic"]["index"],
doc_type=self.cfg["elastic"]["doctype"],
body={
"events" : {
# * (Note: The _timestamp and _ttl fields were deprecated and are now removed in ES 5.X.
# deleting documents from an index is very expensive compared to deleting whole indexes.
# That is why time based indexes are recommended over this sort of thing and why
# _ttl was deprecated in the first place)
#"_ttl" : { "enabled" : "true", "default" : "4d" },
"properties" : { "var_name" : {"type": "keyword"},
"uri" : {"type": "keyword"},
"zone" : {"type": "keyword"},
"server" : {"type": "keyword"},
"whitelisted" : {"type" : "keyword"},
"ip" : {"type" : "keyword"}
}
}
})
except Exception as mapset_error:
print "Unable to set mapping on index/collection for ES 5.X: "+self.cfg["elastic"]["index"]+" "+self.cfg["elastic"]["doctype"]+", Error: "+str(mapset_error)
return
else:
try:
self.es.create(
index=self.cfg["elastic"]["index"],
doc_type=self.cfg["elastic"]["doctype"],
# id=repo_name,
body={},
ignore=409 # 409 - conflict - would be returned if the document is already there
)
except Exception as idxadd_error:
print "Unable to create the index/collection : "+self.cfg["elastic"]["index"]+" "+self.cfg["elastic"]["doctype"]+", Error: "+str(idxadd_error)
return
try:
self.es.indices.put_mapping(
index=self.cfg["elastic"]["index"],
doc_type=self.cfg["elastic"]["doctype"],
body={
"events" : {
"_ttl" : { "enabled" : "true", "default" : "4d" },
"properties" : { "var_name" : {"type": "string", "index":"not_analyzed"},
"uri" : {"type": "string", "index":"not_analyzed"},
"zone" : {"type": "string", "index":"not_analyzed"},
"server" : {"type": "string", "index":"not_analyzed"},
"whitelisted" : {"type" : "string", "index":"not_analyzed"},
"content" : {"type" : "string", "index":"not_analyzed"},
"ip" : { "type" : "string", "index":"not_analyzed"}
}
}
})
except Exception as mapset_error:
print "Unable to set mapping on index/collection : "+self.cfg["elastic"]["index"]+" "+self.cfg["elastic"]["doctype"]+", Error: "+str(mapset_error)
return
def commit(self):
"""Process list of dict (yes) and push them to DB """
self.total_objs += len(self.nlist)
count = 0
full_body = ""
items = []
for evt_array in self.nlist:
for entry in evt_array['events']:
items.append({"index" : {}})
entry['whitelisted'] = "false"
entry['comments'] = "import:"+str(datetime.datetime.now())
# go utf-8 ?
for x in entry.keys():
if isinstance(entry[x], basestring):
entry[x] = unicode(entry[x], errors='replace')
items.append(entry)
count += 1
mapfunc = partial(json.dumps, ensure_ascii=False)
try:
full_body = "\n".join(map(mapfunc,items)) + "\n"
except:
print "Unexpected error:", sys.exc_info()[0]
print "Unable to json.dumps : "
pprint.pprint(items)
bulk(self.es, items, index=self.cfg["elastic"]["index"], doc_type="events", raise_on_error=True)
self.total_commits += count
logging.debug("Written "+str(self.total_commits)+" events")
print "Written "+str(self.total_commits)+" events"
del self.nlist[0:len(self.nlist)]
class NxGeoLoc():
def __init__(self, cfg):
self.cfg = cfg
try:
import GeoIP
except ImportError:
logging.warning("""Python's GeoIP module is not present.
'World Map' reports won't work,
and you can't use per-country filters.""")
raise
if not os.path.isfile(self.cfg["naxsi"]["geoipdb_path"]):
logging.error("Unable to load GeoIPdb.")
raise ValueError
self.gi = GeoIP.new(GeoIP.GEOIP_MEMORY_CACHE)
def cc2ll(self, country):
""" translates countrycode to lagitude, longitude """
# pun intended
coord = [37.090240,-95.7128910]
try:
fd = open(self.cfg["naxsi"]["geoipdb_path"], "r")
except:
return "Unable to open GeoLoc database, please check your setup."
fd.seek(0)
for cn in fd:
if cn.startswith(country+":"):
x = cn[len(country)+1:-1]
ar = x.split(',')
coord[0] = float(ar[1])
coord[1] = float(ar[0])
break
return coord
def ip2cc(self, ip):
""" translates an IP to a country code """
country = self.gi.country_code_by_addr(ip)
# pun intended
if country is None or len(country) < 2:
country = "CN"
return country
def ip2ll(self, ip):
return self.cc2ll(self.ip2cc(ip))

View File

@@ -0,0 +1,741 @@
import logging
import json
import copy
import operator
import os
import pprint
import shlex
import datetime
import glob
import sys
from nxtypificator import Typificator
class NxConfig():
""" Simple configuration loader """
cfg = {}
def __init__(self, fname):
try:
self.cfg = (json.loads(open(fname).read()))
except:
logging.critical("Unable to open/parse configuration file.")
raise ValueError
class NxRating():
""" A class that is used to check success criterias of rule.
attempts jit querying + caching """
def __init__(self, cfg, es, tr):
self.tr = tr
self.cfg = cfg
self.es = es
self.esq = {
'global' : None,
'template' : None,
'rule' : None}
self.stats = {
'global' : {},
'template' : {},
'rule' : {}
}
self.global_warnings = cfg["global_warning_rules"]
self.global_success = cfg["global_success_rules"]
self.global_deny = cfg["global_deny_rules"]
def drop(self):
""" clears all existing stats """
self.stats['template'] = {}
self.stats['global'] = {}
self.stats['rule'] = {}
def refresh_scope(self, scope, esq):
""" drops all datas for a named scope """
if scope not in self.esq.keys():
print "Unknown scope ?!"+scope
self.esq[scope] = esq
self.stats[scope] = {}
def query_ratio(self, scope, scope_small, score, force_refresh):
""" wrapper to calculate ratio between two vals, rounded float """
#print "ratio :"+str(self.get(scope_small, score))+" / "+str( self.get(scope, score))
ratio = round( (float(self.get(scope_small, score)) / self.get(scope, score)) * 100.0, 2)
return ratio
def get(self, scope, score, scope_small=None, force_refresh=False):
""" fetch a value from self.stats or query ES """
#print "#GET:"+scope+"_?"+str(scope_small)+"?_"+score+" = ?"
if scope not in self.stats.keys():
#print "unknown scope :"+scope
return None
if scope_small is not None:
return self.query_ratio(scope, scope_small, score, force_refresh)
elif score in self.stats[scope].keys() and force_refresh is False:
return self.stats[scope][score]
else:
if score is not 'total':
self.stats[scope][score] = self.tr.fetch_uniques(self.esq[scope], score)['total']
else:
res = self.tr.search(self.esq[scope])
self.stats[scope][score] = res['hits']['total']
return self.stats[scope][score]
def check_rule_score(self, tpl):
""" wrapper to check_score, TOFIX ? """
return self.check_score(tpl_success=tpl.get('_success', None),
tpl_warnings=tpl.get('_warnings', None),
tpl_deny=tpl.get('_deny', None))
def check_score(self, tpl_success=None, tpl_warnings=None, tpl_deny=None):
# pprint.pprint(self.stats)
debug = False
success = []
warning = []
deny = False
failed_tests = {"success" : [], "warnings" : []}
glb_success = self.global_success
glb_warnings = self.global_warnings
glb_deny = self.global_deny
for sdeny in [tpl_deny, glb_deny]:
if sdeny is None:
continue
for k in sdeny.keys():
res = self.check_rule(k, sdeny[k])
if res['check'] is True:
# print "WE SHOULD DENY THAT"
deny = True
break
for scheck in [glb_success, tpl_success]:
if scheck is None:
continue
for k in scheck.keys():
res = self.check_rule(k, scheck[k])
if res['check'] is True:
if debug is True:
print "[SUCCESS] OK, on "+k+" vs "+str(res['curr'])+", check :"+str(scheck[k][0])+" - "+str(scheck[k][1])
success.append({'key' : k, 'criteria' : scheck[k], 'curr' : res['curr']})
else:
if debug is True:
print "[SUCCESS] KO, on "+k+" vs "+str(res['curr'])+", check :"+str(scheck[k][0])+" - "+str(scheck[k][1])
failed_tests["success"].append({'key' : k, 'criteria' : scheck[k], 'curr' : res['curr']})
for fcheck in [glb_warnings, tpl_warnings]:
if fcheck is None:
continue
for k in fcheck.keys():
res = self.check_rule(k, fcheck[k])
if res['check'] is True:
if debug is True:
print "[WARNINGS] TRIGGERED, on "+k+" vs "+str(res['curr'])+", check :"+str(fcheck[k][0])+" - "+str(fcheck[k][1])
warning.append({'key' : k, 'criteria' : fcheck[k], 'curr' : res['curr']})
else:
if debug is True:
print "[WARNINGS] NOT TRIGGERED, on "+k+" vs "+str(res['curr'])+", check :"+str(fcheck[k][0])+" - "+str(fcheck[k][1])
failed_tests["warnings"].append({'key' : k, 'criteria' : fcheck[k], 'curr' : res['curr']})
x = { 'success' : success,
'warnings' : warning,
'failed_tests' : failed_tests,
'deny' : deny}
return x
def check_rule(self, label, check_rule):
""" check met/failed success/warning criterias
of a given template vs a set of results """
check = check_rule[0]
beat = check_rule[1]
if label.find("var_name") != -1:
label = label.replace("var_name", "var-name")
items = label.split('_')
for x in range(len(items)):
items[x] = items[x].replace("var-name", "var_name")
if len(items) == 2:
scope = items[0]
score = items[1]
x = self.get(scope, score)
# print "scope:"+str(scope)+" score:"+str(score)
return {'curr' : x, 'check' : check( int(self.get(scope, score)), int(beat))}
elif len(items) == 4:
scope = items[0]
scope_small = items[1]
score = items[2]
x = self.get(scope, score, scope_small=scope_small)
#Xpprint.pprint()
return {'curr' : x, 'check' : check(int(self.get(scope, score, scope_small=scope_small)), int(beat))}
else:
print "cannot understand rule ("+label+"):",
pprint.pprint(check_rule)
return { 'curr' : 0, 'check' : False }
class NxTranslate():
""" Transform Whitelists, template into
ElasticSearch queries, and vice-versa, conventions :
esq : elasticsearch query
tpl : template
cr : core rule
wl : whitelist """
def __init__(self, es, cfg):
self.es = es
self.debug = True
self.cfg = cfg.cfg
self.cfg["global_warning_rules"] = self.normalize_checks(self.cfg["global_warning_rules"])
self.cfg["global_success_rules"] = self.normalize_checks(self.cfg["global_success_rules"])
self.cfg["global_deny_rules"] = self.normalize_checks(self.cfg["global_deny_rules"])
self.core_msg = {}
# by default, es queries will return 1000 results max
self.es_max_size = self.cfg.get("elastic").get("max_size", 1000)
print "# size :"+str(self.es_max_size)
# purely for output coloring
self.red = u'{0}'
self.grn = u'{0}'
self.blu = u'{0}'
if self.cfg["output"]["colors"] == "true":
self.red = u"\033[91m{0}\033[0m"
self.grn = u"\033[92m{0}\033[0m"
self.blu = u"\033[94m{0}\033[0m"
# Attempt to parse provided core rules file
self.load_cr_file(self.cfg["naxsi"]["rules_path"])
def full_auto(self, to_fill_list=None):
""" Loads all tpl within template_path
If templates has hit, peers or url(s) ratio > 15%,
attempts to generate whitelists.
Only displays the wl that did not raise warnings, ranked by success"""
# gather total IPs, total URIs, total hit count
scoring = NxRating(self.cfg, self.es, self)
strict = True
if self.cfg.get("naxsi").get("strict", "") == "false":
strict = False
scoring.refresh_scope("global", self.cfg["global_filters"])
if scoring.get("global", "ip") <= 0:
return []
output = []
for sdir in self.cfg["naxsi"]["template_path"]:
for root, dirs, files in os.walk(sdir):
for file in files:
if file.endswith(".tpl"):
output.append("# {0}{1}/{2} ".format(
self.grn.format(" template :"),
root,
file
))
template = self.load_tpl_file(root+"/"+file)
scoring.refresh_scope('template', self.tpl2esq(template))
output.append("Nb of hits : {0}".format(scoring.get('template', 'total')))
if scoring.get('template', 'total') > 0:
output.append('{0}'.format(self.grn.format("# template matched, generating all rules.")))
whitelists = self.gen_wl(template, rule={})
# x add here
output.append('{0}'.format(len(whitelists))+" whitelists ...")
for genrule in whitelists:
scoring.refresh_scope('rule', genrule['rule'])
results = scoring.check_rule_score(template)
# XX1
if (len(results['success']) > len(results['warnings']) and results["deny"] == False) or self.cfg["naxsi"]["strict"] == "false":
# print "?deny "+str(results['deny'])
try:
str_genrule = '{0}'.format(self.grn.format(self.tpl2wl(genrule['rule']).encode('utf-8', 'replace'), template))
except UnicodeDecodeError:
logging.warning('WARNING: Unprocessable string found in the elastic search')
output.append(self.fancy_display(genrule, results, template))
output.append(str_genrule)
if to_fill_list is not None:
genrule.update({'genrule': str_genrule})
to_fill_list.append(genrule)
return output
def wl_on_type(self):
for rule in Typificator(self.es, self.cfg).get_rules():
print 'BasicRule negative "rx:{0}" "msg:{1}" "mz:${2}_VAR:{3}" "s:BLOCK";'.format(*rule)
def fancy_display(self, full_wl, scores, template=None):
output = []
if template is not None and '_msg' in template.keys():
output.append("#msg: {0}\n".format(template['_msg']))
rid = full_wl['rule'].get('id', "0")
output.append("#Rule ({0}) {1}\n".format(rid, self.core_msg.get(rid, 'Unknown ..')))
if self.cfg["output"]["verbosity"] >= 4:
output.append("#total hits {0}\n".format(full_wl['total_hits']))
for x in ["content", "peers", "uri", "var_name"]:
if x not in full_wl.keys():
continue
for y in full_wl[x]:
output.append("#{0} : {1}\n".format(x, unicode(y).encode("utf-8", 'replace')))
return ''.join(output)
# pprint.pprint(scores)
for x in scores['success']:
print "# success : "+self.grn.format(str(x['key'])+" is "+str(x['curr']))
for x in scores['warnings']:
print "# warnings : "+self.grn.format(str(x['key'])+" is "+str(x['curr']))
pass
def expand_tpl_path(self, template):
""" attempts to convert stuff to valid tpl paths.
if it starts with / or . it will consider it's a relative/absolute path,
else, that it's a regex on tpl names. """
clean_tpls = []
tpl_files = []
if template.startswith('/') or template.startswith('.'):
tpl_files.extend(glob.glob(template))
else:
for sdir in self.cfg['naxsi']['template_path']:
tpl_files.extend(glob.glob(sdir +"/"+template))
for x in tpl_files:
if x.endswith(".tpl") and x not in clean_tpls:
clean_tpls.append(x)
return clean_tpls
def load_tpl_file(self, tpl):
""" open, json.loads a tpl file,
cleanup data, return dict. """
try:
x = open(tpl)
except:
logging.error("Unable to open tpl file.")
return None
tpl_s = ""
for l in x.readlines():
if l.startswith('#'):
continue
else:
tpl_s += l
try:
template = json.loads(tpl_s)
except:
logging.error("Unable to load json from '"+tpl_s+"'")
return None
if '_success' in template.keys():
template['_success'] = self.normalize_checks(template['_success'])
if '_warnings' in template.keys():
template['_warnings'] = self.normalize_checks(template['_warnings'])
if '_deny' in template.keys():
template['_deny'] = self.normalize_checks(template['_deny'])
#return self.tpl_append_gfilter(template)
return template
def load_wl_file(self, wlf):
""" Loads a file of whitelists,
convert them to ES queries,
and returns them as a list """
esql = []
try:
wlfd = open(wlf, "r")
except:
logging.error("Unable to open whitelist file.")
return None
for wl in wlfd:
[res, esq] = self.wl2esq(wl)
if res is True:
esql.append(esq)
if len(esql) > 0:
return esql
return None
def load_cr_file(self, cr_file):
""" parses naxsi's core rule file, to
decorate output with "msg:" field content """
core_msg = {}
core_msg['0'] = "id:0 is wildcard (all rules) whitelist."
try:
fd = open(cr_file, 'r')
for i in fd:
if i.startswith('MainRule') or i.startswith('#@MainRule'):
pos = i.find('id:')
pos_msg = i.find('msg:')
self.core_msg[i[pos + 3:i[pos + 3].find(';') - 1]] = i[pos_msg + 4:][:i[pos_msg + 4:].find('"')]
fd.close()
except:
logging.warning("Unable to open rules file")
def tpl2esq(self, ob, full=True):
''' receives template or a rule, returns a valid
ElasticSearch query '''
qr = {
"query" : { "bool" : { "must" : [ ]} },
"size" : self.es_max_size
}
# A hack in case we were inadvertently given an esq
if 'query' in ob.keys():
return ob
for k in ob.keys():
if k.startswith("_"):
continue
# if key starts with '?' :
# use content for search, but use content from exceptions to generate WL
if k[0] == '?':
k = k[1:]
qr['query']['bool']['must'].append({"regexp" : { k : ob['?'+k] }})
# wildcard
elif ob[k] == '?':
pass
else:
qr['query']['bool']['must'].append({"match" : { k : ob[k]}})
qr = self.append_gfilter(qr)
return qr
def append_gfilter(self, esq):
""" append global filters parameters
to and existing elasticsearch query """
for x in self.cfg["global_filters"]:
if x.startswith('?'):
x = x[1:]
if {"regexp" : { x : self.cfg["global_filters"]['?'+x] }} not in esq['query']['bool']['must']:
esq['query']['bool']['must'].append({"regexp" : { x : self.cfg["global_filters"]['?'+x] }})
else:
if {"match" : { x : self.cfg["global_filters"][x] }} not in esq['query']['bool']['must']:
esq['query']['bool']['must'].append({"match" : { x : self.cfg["global_filters"][x] }})
return esq
def tpl_append_gfilter(self, tpl):
for x in self.cfg["global_filters"]:
tpl[x] = self.cfg["global_filters"][x]
return tpl
def wl2esq(self, raw_line):
""" parses a fulltext naxsi whitelist,
and outputs the matching es query (ie. for tagging),
returns [True|False, error_string|ESquery] """
esq = {
"query" : { "bool" : { "must" : [ ]} },
"size" : self.es_max_size
}
wl_id = ""
mz_str = ""
# do some pre-check to ensure it's a valid line
if raw_line.startswith("#"):
return [False, "commented out"]
if raw_line.find("BasicRule") == -1:
return [False, "not a BasicRule"]
# split line
strings = shlex.split(raw_line)
# bug #194 - drop everything after the first chunk starting with a '#' (inline comments)
for x in strings:
if x.startswith('#'):
strings = strings[:strings.index(x)]
# more checks
if len(strings) < 3:
return [False, "empty/incomplete line"]
if strings[0].startswith('#'):
return [False, "commented line"]
if strings[0] != "BasicRule":
return [False, "not a BasicRule, keyword '"+strings[0]+"'"]
if strings[len(strings) - 1].endswith(';'):
strings[len(strings) - 1] = strings[len(strings) - 1][:-1]
for x in strings:
if x.startswith("wl:"):
wl_id = x[3:]
# if ID contains "," replace them with OR for ES query
wl_id = wl_id.replace(",", " OR ")
# if ID != 0 add it, otherwise, it's a wildcard!
if wl_id != "0":
# if IDs are negative, we must exclude all IDs except
# those ones.
if wl_id.find("-") != -1:
wl_id = wl_id.replace("-", "")
#print "Negative query."
if not 'must_not' in esq['query']['bool'].keys():
esq['query']['bool']['must_not'] = []
esq['query']['bool']['must_not'].append({"match" : { "id" : wl_id}})
else:
esq['query']['bool']['must'].append({"match" : { "id" : wl_id}})
if x.startswith("mz:"):
mz_str = x[3:]
[res, filters] = self.parse_mz(mz_str, esq)
if res is False:
return [False, "matchzone parsing failed."]
esq = self.append_gfilter(esq)
return [True, filters]
def parse_mz(self, mz_str, esq):
""" parses a match zone from BasicRule, and updates
es query accordingly. Removes ^/$ chars from regexp """
forbidden_rx_chars = "^$"
kw = mz_str.split("|")
tpl = esq['query']['bool']['must']
uri = ""
zone = ""
var_name = ""
t_name = False
# |NAME flag
if "NAME" in kw:
t_name = True
kw.remove("NAME")
for k in kw:
# named var
if k.startswith('$'):
k = k[1:]
try:
[zone, var_name] = k.split(':')
except:
return [False, "Incoherent zone : "+k]
# *_VAR:<string>
if zone.endswith("_VAR"):
zone = zone[:-4]
if t_name is True:
zone += "|NAME"
tpl.append({"match" : { "zone" : zone}})
tpl.append({"match" : { "var_name" : var_name}})
# *_VAR_X:<regexp>
elif zone.endswith("_VAR_X"):
zone = zone[:-6]
if t_name is True:
zone += "|NAME"
tpl.append({"match" : { "zone" : zone}})
#.translate(string.maketrans(chars, newchars))
tpl.append({"regexp" : { "var_name" : var_name.translate(None, forbidden_rx_chars)}})
# URL_X:<regexp>
elif zone == "URL_X":
zone = zone[:-2]
tpl.append({"regexp" : { "uri" : var_name.translate(None, forbidden_rx_chars)}})
# URL:<string>
elif zone == "URL":
tpl.append({"match" : { "uri" : var_name }})
else:
print "huh, what's that ? "+zone
# |<ZONE>
else:
if k not in ["HEADERS", "BODY", "URL", "ARGS", "FILE_EXT"]:
return [False, "Unknown zone : '"+k+"'"]
zone = k
if t_name is True:
zone += "|NAME"
tpl.append({"match" : {"zone" : zone}})
# print "RULE :"
# pprint.pprint(esq)
return [True, esq]
def tpl2wl(self, rule, template=None):
""" transforms a rule/esq
to a valid BasicRule. """
tname = False
zone = ""
if template is not None and '_statics' in template.keys():
for x in template['_statics'].keys():
rule[x] = template['_statics'][x]
wl = "BasicRule "
wl += " wl:"+str(rule.get('id', 0)).replace("OR", ",").replace("|", ",").replace(" ", "")
wl += ' "mz:'
if rule.get('uri', None) is not None:
wl += "$URL:"+rule['uri']
wl += "|"
# whitelist targets name
if rule.get('zone', '').endswith("|NAME"):
tname = True
zone = rule['zone'][:-5]
else:
zone = rule['zone']
if rule.get('var_name', '') not in ['', '?'] and zone != "FILE_EXT":
wl += "$"+zone+"_VAR:"+rule['var_name']
else:
wl += zone
if tname is True:
wl += "|NAME"
wl += '";'
return wl
def fetch_top(self, template, field, limit=10):
""" fetch top items for a given field,
clears the field if exists in gfilters """
x = None
if field in template.keys():
x = template[field]
del template[field]
esq = self.tpl2esq(template)
if x is not None:
template[field] = x
if self.cfg["elastic"].get("version", None) == "1":
esq['facets'] = { "facet_results" : {"terms": { "field": field, "size" : self.es_max_size} }}
elif self.cfg["elastic"].get("version", None) in ["2", "5"]:
esq['aggregations'] = { "agg1" : {"terms": { "field": field, "size" : self.es_max_size} }}
else:
print "Unknown / Unspecified ES version in nxapi.json : {0}".format(self.cfg["elastic"].get("version", "#UNDEFINED"))
sys.exit(1)
res = self.search(esq)
if self.cfg["elastic"].get("version", None) == "1":
total = res['facets']['facet_results']['total']
elif self.cfg["elastic"].get("version", None) in ["2", "5"]:
total = res['hits']['total']
else:
print "Unknown / Unspecified ES version in nxapi.json : {0}".format(self.cfg["elastic"].get("version", "#UNDEFINED"))
sys.exit(1)
count = 0
ret = []
if self.cfg["elastic"].get("version", None) == "1":
for x in res['facets']['facet_results']['terms']:
ret.append('{0} {1}% (total: {2}/{3})'.format(x['term'], round((float(x['count']) / total) * 100, 2), x['count'], total))
count += 1
if count > limit:
break
elif self.cfg["elastic"].get("version", None) in ["2", "5"]:
for x in res['aggregations']['agg1']['buckets']:
ret.append('{0} {1}% (total: {2}/{3})'.format(x['key'], round((float(x['doc_count']) / total) * 100, 2), x['doc_count'], total))
count += 1
if count > limit:
break
else:
print "Unknown / Unspecified ES version in nxapi.json : {0}".format(self.cfg["elastic"].get("version", "#UNDEFINED"))
sys.exit(1)
return ret
def fetch_uniques(self, rule, key):
""" shortcut function to gather unique
values and their associated match count """
uniques = []
esq = self.tpl2esq(rule)
#
if self.cfg["elastic"].get("version", None) == "1":
esq['facets'] = { "facet_results" : {"terms": { "field": key, "size" : 50000} }}
elif self.cfg["elastic"].get("version", None) in ["2", "5"]:
esq['aggregations'] = { "agg1" : {"terms": { "field": key, "size" : 50000} }}
else:
print "Unknown / Unspecified ES version in nxapi.json : {0}".format(self.cfg["elastic"].get("version", "#UNDEFINED"))
sys.exit(1)
res = self.search(esq)
if self.cfg["elastic"].get("version", None) == "1":
for x in res['facets']['facet_results']['terms']:
if x['term'] not in uniques:
uniques.append(x['term'])
elif self.cfg["elastic"].get("version", None) in ["2", "5"]:
for x in res['aggregations']['agg1']['buckets']:
if x['key'] not in uniques:
uniques.append(x['key'])
else:
print "Unknown / Unspecified ES version in nxapi.json : {0}".format(self.cfg["elastic"].get("version", "#UNDEFINED"))
sys.exit(1)
return { 'list' : uniques, 'total' : len(uniques) }
def index(self, body, eid):
return self.es.index(index=self.cfg["elastic"]["index"], doc_type=self.cfg["elastic"]["doctype"], body=body, id=eid)
def search(self, esq, stats=False):
""" search wrapper with debug """
debug = False
if debug is True:
print "#SEARCH:PARAMS:index="+self.cfg["elastic"]["index"]+", doc_type="+self.cfg["elastic"]["doctype"]+", body=",
print "#SEARCH:QUERY:",
pprint.pprint (esq)
if len(esq["query"]["bool"]["must"]) == 0:
del esq["query"]
x = self.es.search(index=self.cfg["elastic"]["index"], doc_type=self.cfg["elastic"]["doctype"], body=esq)
if debug is True:
print "#RESULT:",
pprint.pprint(x)
return x
def normalize_checks(self, tpl):
""" replace check signs (<, >, <=, >=) by
operator.X in a dict-form tpl """
replace = {
'>' : operator.gt,
'<' : operator.lt,
'>=' : operator.ge,
'<=' : operator.le
}
for tpl_key in tpl.keys():
for token in replace.keys():
if tpl[tpl_key][0] == token:
tpl[tpl_key][0] = replace[token]
return tpl
def tag_events(self, esq, msg, tag=False):
""" tag events with msg + tstamp if they match esq """
count = 0
total_events = 0
esq["size"] = "0"
print "TAG RULE :",
pprint.pprint(esq)
x = self.search(esq)
total_events = int(str(x["hits"]["total"]))
print str(self.grn.format(total_events)) + " items to be tagged ..."
size = int(x['hits']['total'])
if size > 20000:
size = size / 100
elif size > 100:
size = size / 10
while count < total_events:
esq["size"] = size
esq["from"] = 0
res = self.search(esq)
# Iterate through matched evts to tag them.
if int(res['hits']['total']) == 0:
break
for item in res['hits']['hits']:
eid = item['_id']
body = item['_source']
cm = item['_source']['comments']
body['comments'] += ","+msg+":"+str(datetime.datetime.now())
body['whitelisted'] = "true"
if tag is True:
self.index(body, eid)
else:
print eid+",",
count += 1
print "Tagged {0} events out of {1}".format(count, total_events)
if total_events - count < size:
size = total_events - count
print ""
#--
if not tag or tag is False:
return 0
else:
return count
def gen_wl(self, tpl, rule={}):
""" recursive whitelist generation function,
returns a list of all possible witelists. """
retlist = []
for tpl_key in tpl.keys():
if tpl_key in rule.keys():
continue
if tpl_key[0] in ['_', '?']:
continue
if tpl[tpl_key] == '?':
continue
rule[tpl_key] = tpl[tpl_key]
for tpl_key in tpl.keys():
if tpl_key.startswith('_'):
continue
elif tpl_key.startswith('?'):
if tpl_key[1:] in rule.keys():
continue
unique_vals = self.fetch_uniques(rule, tpl_key[1:])['list']
for uval in unique_vals:
rule[tpl_key[1:]] = uval
retlist += self.gen_wl(tpl, copy.copy(rule))
return retlist
elif tpl[tpl_key] == '?':
if tpl_key in rule.keys():
continue
unique_vals = self.fetch_uniques(rule, tpl_key)['list']
for uval in unique_vals:
rule[tpl_key] = uval
retlist += self.gen_wl(tpl, copy.copy(rule))
return retlist
elif tpl_key not in rule.keys():
rule[tpl_key] = tpl[tpl_key]
retlist += self.gen_wl(tpl, copy.copy(rule))
return retlist
esq = self.tpl2esq(rule)
res = self.search(esq)
if res['hits']['total'] > 0:
clist = []
peers = []
uri = []
var_name = []
for x in res['hits']['hits']:
if len(x.get("_source").get("ip", "")) > 0 and x.get("_source").get("ip", "") not in peers:
peers.append(x["_source"]["ip"])
if len(x.get("_source").get("uri", "")) > 0 and x.get("_source").get("uri", "") not in uri:
uri.append(x["_source"]["uri"])
if len(x.get("_source").get("var_name", "")) > 0 and x.get("_source").get("var_name", "") not in var_name:
var_name.append(x["_source"]["var_name"])
if len(x.get("_source").get("content", "")) > 0 and x.get("_source").get("content", "") not in clist:
clist.append(x["_source"]["content"])
if len(clist) >= 5:
break
retlist.append({'rule' : rule, 'content' : clist[:5], 'total_hits' : res['hits']['total'], 'peers' : peers[:5], 'uri' : uri[:5],
'var_name' : var_name[:5]})
return retlist
return []

View File

@@ -0,0 +1,101 @@
'''
This modules generate types for url parameters.
'''
import re
import sys
import collections
from elasticsearch import Elasticsearch
# Each regexp is a subset of the next one
REGEXPS = [
[r'^$', 'empty'],
[r'^[01]$', 'boolean'],
[r'^\d+$', 'integer'],
[r'^#[0-9a-f]+$', 'colour'], # hex + '#'
[r'^[0-9a-f]+$', 'hexadecimal'],
[r'^[0-9a-z]+$', 'alphanum'],
[r'^https?://([0-9a-z-.]+\.)+[\w?+-=&/ ]+$', 'url'], # like http://pouet.net?hello=1&id=3
[r'^\w+$', 'alphanumdash'],
[r'^[0-9a-z?&=+_-]+$', 'url parameter'],
[r'^[\w[] ,&=+-]+$', 'array'],
[r'^[' + r'\s\w' + r'!$%^&*()[]:;@~#?/.,' + r']+$', 'plaintext'],
[r'', 'none'], # untypables parameters
]
class Typificator(object):
''' Classes that:
1. Fetch data from ES
2. Generate types for parameters
3. Returns a dict of dict
'''
def __init__(self, es, cfg):
self.es_instance = es
self.cfg = cfg
def __get_data(self, nb_samples=1e5):
''' Get (in a lazy way) data from the ES instance
'''
data = set()
position = 0
size = min(10000, nb_samples) # if nb_samples if inferiour to our size, we'll get it in a single request.
while nb_samples:
if not data:
body = {'query': {}}
for k,v in self.cfg['global_filters'].iteritems():
body['query'].update({'match':{k:v}})
data = self.es_instance.search(index=self.cfg["elastic"]["index"], doc_type='events',
size=size, from_=position,
body=body)
data = data['hits']['hits'] # we don't care about metadata
if not data: # we got all data from ES
return
position += size
nb_samples -= size
for log in data:
yield log['_source']
def get_rules(self, nb_samples=1e5):
''' Generate (in a lazy way) types for parameters
'''
# Thank you defaultdict <3
# rules = {zone1: {var1:0, var2:0}, zone2: {var6:0, ...}, ...}
rules = collections.defaultdict(lambda: collections.defaultdict(int))
# Compile regexp for speed
regexps = [re.compile(reg, re.IGNORECASE) for reg, _ in REGEXPS]
for line in self.__get_data(nb_samples):
try: # some events are fucked up^w^w empty
#naxsi inverts the var_name and the content
#when a rule match on var_name
if line['zone'].endswith('|NAME'):
continue
zone = line['zone']
content = line['content']
var_name = line['var_name']
except KeyError as e:
print 'Error with : {0} ({1})'.format(line, e)
continue
if not var_name: # No types for empty varnames.
continue
# Bump regexps until one matches
# Since every regexp is a subset of the next one,
# this works great.
while not regexps[rules[zone][var_name]].match(content):
rules[zone][var_name] += 1
for zone, zone_data in rules.iteritems():
for var_name, index in zone_data.iteritems():
if index < len(REGEXPS) - 1: # Don't return untyped things
yield [REGEXPS[index][0], REGEXPS[index][1], zone, var_name]
if __name__ == '__main__':
nb_samples = 1e6 if len(sys.argv) == 1 else int(sys.argv[1])
for rule in Typificator().get_rules(nb_samples):
print 'TypeRule "rx:{0}" "msg:typed ({1}) parameter" "mz:${2}_VAR:{3}"'.format(rule[0], rule[1], rule[2], rule[3])

479
naxsi-0.55.3/nxapi/nxtool.py Executable file
View File

@@ -0,0 +1,479 @@
#!/usr/bin/env python
import glob, fcntl, termios
import sys
import socket
import elasticsearch
import time
import os
import tempfile
import subprocess
import json
from collections import defaultdict
from optparse import OptionParser, OptionGroup
from nxapi.nxtransform import *
from nxapi.nxparse import *
F_SETPIPE_SZ = 1031 # Linux 2.6.35+
F_GETPIPE_SZ = 1032 # Linux 2.6.35+
def open_fifo(fifo):
try:
os.mkfifo(fifo)
except OSError:
print "Fifo ["+fifo+"] already exists (non fatal)."
except Exception, e:
print "Unable to create fifo ["+fifo+"]"
try:
print "Opening fifo ... will return when data is available."
fifo_fd = open(fifo, 'r')
fcntl.fcntl(fifo_fd, F_SETPIPE_SZ, 1000000)
print "Pipe (modified) size : "+str(fcntl.fcntl(fifo_fd, F_GETPIPE_SZ))
except Exception, e:
print "Unable to create fifo, error: "+str(e)
return None
return fifo_fd
def macquire(line):
z = parser.parse_raw_line(line)
# add data str and country
if z is not None:
for event in z['events']:
event['date'] = z['date']
try:
event['coords'] = geoloc.ip2ll(event['ip'])
event['country'] = geoloc.ip2cc(event['ip'])
except NameError:
pass
# print "Got data :)"
# pprint.pprint(z)
#print ".",
print z
injector.insert(z)
else:
pass
#print "No data ? "+line
#print ""
opt = OptionParser()
# group : config
p = OptionGroup(opt, "Configuration options")
p.add_option('-c', '--config', dest="cfg_path", default="/usr/local/etc/nxapi.json", help="Path to nxapi.json (config).")
p.add_option('--colors', dest="colors", action="store_false", default="true", help="Disable output colorz.")
# p.add_option('-q', '--quiet', dest="quiet_flag", action="store_true", help="Be quiet.")
# p.add_option('-v', '--verbose', dest="verb_flag", action="store_true", help="Be verbose.")
opt.add_option_group(p)
# group : in option
p = OptionGroup(opt, "Input options (log acquisition)")
p.add_option('--files', dest="files_in", help="Path to log files to parse.")
p.add_option('--fifo', dest="fifo_in", help="Path to a FIFO to be created & read from. [infinite]")
p.add_option('--stdin', dest="stdin", action="store_true", help="Read from stdin.")
p.add_option('--no-timeout', dest="infinite_flag", action="store_true", help="Disable timeout on read operations (stdin/fifo).")
p.add_option('--syslog', dest="syslog_in", action="store_true", help="Listen on tcp port for syslog logging.")
opt.add_option_group(p)
# group : filtering
p = OptionGroup(opt, "Filtering options (for whitelist generation)")
p.add_option('-s', '--server', dest="server", help="FQDN to which we should restrict operations.")
p.add_option('--filter', dest="filter", action="append", help="This option specify a filter for each type of filter, filter are merge with existing templates/filters. (--filter 'uri /foobar')")
opt.add_option_group(p)
# group : tagging
p = OptionGroup(opt, "Tagging options (tag existing events in database)")
p.add_option('-w', '--whitelist-path', dest="wl_file", help="A path to whitelist file, will find matching events in DB.")
p.add_option('-i', '--ip-path', dest="ips", help="A path to IP list file, will find matching events in DB.")
p.add_option('--tag', dest="tag", action="store_true", help="Actually tag matching items in DB.")
opt.add_option_group(p)
# group : whitelist generation
p = OptionGroup(opt, "Whitelist Generation")
p.add_option('-f', '--full-auto', dest="full_auto", action="store_true", help="Attempt fully automatic whitelist generation process.")
p.add_option('-t', '--template', dest="template", help="Path to template to apply.")
p.add_option('--slack', dest="slack", action="store_false", help="Enables less strict mode.")
p.add_option('--type', dest="type_wl", action="store_true", help="Generate whitelists based on param type")
opt.add_option_group(p)
# group : statistics
p = OptionGroup(opt, "Statistics Generation")
p.add_option('-x', '--stats', dest="stats", action="store_true", help="Generate statistics about current's db content.")
opt.add_option_group(p)
# group : interactive generation
p = OptionGroup(opt, "Interactive Whitelists Generation")
p.add_option('-g', '--interactive-generation', dest="int_gen", action="store_true", help="Use your favorite text editor for whitelist generation.")
opt.add_option_group(p)
(options, args) = opt.parse_args()
try:
cfg = NxConfig(options.cfg_path)
except ValueError:
sys.exit(-1)
if options.server is not None:
cfg.cfg["global_filters"]["server"] = options.server
# https://github.com/nbs-system/naxsi/issues/231
mutally_exclusive = ['stats', 'full_auto', 'template', 'wl_file', 'ips', 'files_in', 'fifo_in', 'syslog_in']
count=0
for x in mutally_exclusive:
if options.ensure_value(x, None) is not None:
count += 1
if count > 1:
print "Mutually exclusive options are present (ie. import and stats), aborting."
sys.exit(-1)
cfg.cfg["output"]["colors"] = "false" if options.int_gen else str(options.colors).lower()
cfg.cfg["naxsi"]["strict"] = str(options.slack).lower()
def get_filter(arg_filter):
x = {}
to_parse = []
kwlist = ['server', 'uri', 'zone', 'var_name', 'ip', 'id', 'content', 'country', 'date',
'?server', '?uri', '?var_name', '?content']
try:
for argstr in arg_filter:
argstr = ' '.join(argstr.split())
to_parse += argstr.split(' ')
if [a for a in kwlist if a in to_parse]:
for kw in to_parse:
if kw in kwlist:
x[kw] = to_parse[to_parse.index(kw)+1]
else:
raise
except:
logging.critical('option --filter must have at least one option')
sys.exit(-1)
return x
if options.filter is not None:
cfg.cfg["global_filters"].update(get_filter(options.filter))
try:
use_ssl = bool(cfg.cfg["elastic"]["use_ssl"])
except KeyError:
use_ssl = False
es = elasticsearch.Elasticsearch(cfg.cfg["elastic"]["host"], use_ssl=use_ssl)
# Get ES version from the client and avail it at cfg
es_version = es.info()['version'].get('number', None)
if es_version is not None:
cfg.cfg["elastic"]["version"] = es_version.split(".")[0]
if cfg.cfg["elastic"].get("version", None) is None:
print "Failed to get version from ES, Specify version ['1'/'2'/'5'] in [elasticsearch] section"
sys.exit(-1)
translate = NxTranslate(es, cfg)
if options.type_wl is True:
translate.wl_on_type()
sys.exit(0)
# whitelist generation options
if options.full_auto is True:
translate.load_cr_file(translate.cfg["naxsi"]["rules_path"])
results = translate.full_auto()
if results:
for result in results:
print "{0}".format(result)
else:
print "No hits for this filter."
sys.exit(1)
sys.exit(0)
if options.template is not None:
scoring = NxRating(cfg.cfg, es, translate)
tpls = translate.expand_tpl_path(options.template)
gstats = {}
if len(tpls) <= 0:
print "No template matching"
sys.exit(1)
# prepare statistics for global scope
scoring.refresh_scope('global', translate.tpl2esq(cfg.cfg["global_filters"]))
for tpl_f in tpls:
scoring.refresh_scope('rule', {})
scoring.refresh_scope('template', {})
print translate.grn.format("#Loading tpl '"+tpl_f+"'")
tpl = translate.load_tpl_file(tpl_f)
# prepare statistics for filter scope
scoring.refresh_scope('template', translate.tpl2esq(tpl))
#pprint.pprint(tpl)
print "Hits of template : "+str(scoring.get('template', 'total'))
whitelists = translate.gen_wl(tpl, rule={})
print str(len(whitelists))+" whitelists ..."
for genrule in whitelists:
#pprint.pprint(genrule)
scoring.refresh_scope('rule', genrule['rule'])
scores = scoring.check_rule_score(tpl)
if (len(scores['success']) > len(scores['warnings']) and scores['deny'] == False) or cfg.cfg["naxsi"]["strict"] == "false":
#print "?deny "+str(scores['deny'])
print translate.fancy_display(genrule, scores, tpl)
print translate.grn.format(translate.tpl2wl(genrule['rule'], tpl)).encode('utf-8')
sys.exit(0)
# tagging options
if options.wl_file is not None and options.server is None:
print translate.red.format("Cannot tag events in database without a server name !")
sys.exit(2)
if options.wl_file is not None:
wl_files = []
wl_files.extend(glob.glob(options.wl_file))
count = 0
for wlf in wl_files:
print translate.grn.format("#Loading tpl '"+wlf+"'")
try:
wlfd = open(wlf, "r")
except:
print translate.red.format("Unable to open wl file '"+wlf+"'")
sys.exit(-1)
for wl in wlfd:
[res, esq] = translate.wl2esq(wl)
if res is True:
count = 0
while True:
x = translate.tag_events(esq, "Whitelisted", tag=options.tag)
count += x
if x == 0:
break
print translate.grn.format(str(count)) + " items tagged ..."
count = 0
sys.exit(0)
if options.ips is not None:
ip_files = []
ip_files.extend(glob.glob(options.ips))
tpl = {}
count = 0
# esq = translate.tpl2esq(cfg.cfg["global_filters"])
for wlf in ip_files:
try:
wlfd = open(wlf, "r")
except:
print "Unable to open ip file '"+wlf+"'"
sys.exit(-1)
for wl in wlfd:
print "=>"+wl
tpl["ip"] = wl.strip('\n')
esq = translate.tpl2esq(tpl)
pprint.pprint(esq)
pprint.pprint(tpl)
count += translate.tag_events(esq, "BadIPS", tag=options.tag)
print translate.grn.format(str(count)) + " items to be tagged ..."
count = 0
sys.exit(0)
# statistics
if options.stats is True:
print translate.red.format("# Whitelist(ing) ratio :")
translate.fetch_top(cfg.cfg["global_filters"], "whitelisted", limit=2)
print translate.red.format("# Top servers :")
for e in translate.fetch_top(cfg.cfg["global_filters"], "server", limit=10):
try:
list_e = e.split()
print '# {0} {1} {2}{3}'.format(translate.grn.format(list_e[0]), list_e[1], list_e[2], list_e[3])
except:
print "--malformed--"
print translate.red.format("# Top URI(s) :")
for e in translate.fetch_top(cfg.cfg["global_filters"], "uri", limit=10):
try:
list_e = e.split()
print '# {0} {1} {2}{3}'.format(translate.grn.format(list_e[0]), list_e[1], list_e[2], list_e[3])
except:
print "--malformed--"
print translate.red.format("# Top Zone(s) :")
for e in translate.fetch_top(cfg.cfg["global_filters"], "zone", limit=10):
try:
list_e = e.split()
print '# {0} {1} {2}{3}'.format(translate.grn.format(list_e[0]), list_e[1], list_e[2], list_e[3])
except:
print "--malformed--"
print translate.red.format("# Top Peer(s) :")
for e in translate.fetch_top(cfg.cfg["global_filters"], "ip", limit=10):
try:
list_e = e.split()
print '# {0} {1} {2}{3}'.format(translate.grn.format(list_e[0]), list_e[1], list_e[2], list_e[3])
except:
print "--malformed--"
sys.exit(0)
def write_generated_wl(filename, results):
with open('/tmp/{0}'.format(filename), 'w') as wl_file:
for result in results:
for key, items in result.iteritems():
if items:
print "{} {}".format(key, items)
if key == 'genrule':
wl_file.write("# {}\n{}\n".format(key, items))
else:
wl_file.write("# {} {}\n".format(key, items))
wl_file.flush()
def ask_user_for_server_selection(editor, welcome_sentences, selection):
with tempfile.NamedTemporaryFile(suffix='.tmp') as temporary_file:
top_selection = translate.fetch_top(cfg.cfg["global_filters"],
selection,
limit=10
)
temporary_file.write(welcome_sentences)
for line in top_selection:
temporary_file.write('{0}\n'.format(line))
temporary_file.flush()
subprocess.call([editor, temporary_file.name])
temporary_file.seek(len(welcome_sentences))
ret = []
for line in temporary_file:
if not line.startswith('#'):
ret.append(line.strip().split()[0])
return ret
def ask_user_for_selection(editor, welcome_sentences, selection, servers):
regex_message = "# as in the --filter option you can add ? for regex\n"
ret = {}
for server in servers:
server_reminder = "server: {0}\n\n".format(server)
ret[server] = []
with tempfile.NamedTemporaryFile(suffix='.tmp') as temporary_file:
temporary_file.write(welcome_sentences + regex_message + server_reminder)
cfg.cfg["global_filters"]["server"] = server
top_selection = translate.fetch_top(cfg.cfg["global_filters"],
selection,
limit=10
)
for line in top_selection:
temporary_file.write('{0} {1}\n'.format(selection, line))
temporary_file.flush()
subprocess.call([editor, temporary_file.name])
temporary_file.seek(len(welcome_sentences) + len(server_reminder) + len(regex_message))
for line in temporary_file:
if not line.startswith('#'):
res = line.strip().split()
ret[server].append((res[0], res[1]))
return ret
def generate_wl(selection_dict):
for key, items in selection_dict.iteritems():
if not items:
return False
global_filters_context = cfg.cfg["global_filters"]
global_filters_context["server"] = key
for idx, (selection, item) in enumerate(items):
global_filters_context[selection] = item
translate.cfg["global_filters"] = global_filters_context
print 'generating wl with filters {0}'.format(global_filters_context)
wl_dict_list = []
res = translate.full_auto(wl_dict_list)
del global_filters_context[selection]
write_generated_wl(
"server_{0}_{1}.wl".format(
key,
idx if (selection == "uri") else "zone_{0}".format(item),
),
wl_dict_list
)
if options.int_gen is True:
editor = os.environ.get('EDITOR', 'vi')
welcome_sentences = '{0}\n{1}\n'.format(
'# all deleted line or starting with a # will be ignore',
'# if you want to use slack option you have to specify it on the command line options'
)
servers = ask_user_for_server_selection(editor, welcome_sentences, "server")
uris = ask_user_for_selection(editor, welcome_sentences, "uri", servers)
zones = ask_user_for_selection(editor, welcome_sentences, "zone", servers)
if uris:
generate_wl(uris)
if zones:
generate_wl(zones)
# in case the user let uri and zone files empty generate wl for all
# selected server(s)
if not uris and not zones:
for server in servers:
translate.cfg["global_filters"]["server"] = server
print 'generating with filters: {0}'.format(translate.cfg["global_filters"])
res = translate.full_auto()
writing_generated_wl("server_{0}.wl".format(server), res)
sys.exit(0)
# input options, only setup injector if one input option is present
if options.files_in is not None or options.fifo_in is not None or options.stdin is not None or options.syslog_in is not None:
if options.fifo_in is not None or options.syslog_in is not None:
injector = ESInject(es, cfg.cfg, auto_commit_limit=1)
else:
injector = ESInject(es, cfg.cfg)
parser = NxParser()
offset = time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
offset = offset / 60 / 60 * -1
if offset < 0:
offset = str(-offset)
else:
offset = str(offset)
offset = offset.zfill(2)
parser.out_date_format = "%Y-%m-%dT%H:%M:%S+"+offset #ES-friendly
try:
geoloc = NxGeoLoc(cfg.cfg)
except:
print "Unable to get GeoIP"
if options.files_in is not None:
reader = NxReader(macquire, lglob=[options.files_in])
reader.read_files()
injector.stop()
sys.exit(0)
if options.fifo_in is not None:
fd = open_fifo(options.fifo_in)
if options.infinite_flag is True:
reader = NxReader(macquire, fd=fd, stdin_timeout=None)
else:
reader = NxReader(macquire, fd=fd)
while True:
print "start-",
if reader.read_files() == False:
break
print "stop"
print 'End of fifo input...'
injector.stop()
sys.exit(0)
if options.syslog_in is not None:
sysloghost = cfg.cfg["syslogd"]["host"]
syslogport = cfg.cfg["syslogd"]["port"]
while 1:
reader = NxReader(macquire, syslog=True, syslogport=syslogport, sysloghost=sysloghost)
reader.read_files()
injector.stop()
sys.exit(0)
if options.stdin is True:
if options.infinite_flag:
reader = NxReader(macquire, lglob=[], fd=sys.stdin, stdin_timeout=None)
else:
reader = NxReader(macquire, lglob=[], fd=sys.stdin)
while True:
print "start-",
if reader.read_files() == False:
break
print "stop"
print 'End of stdin input...'
injector.stop()
sys.exit(0)
opt.print_help()
sys.exit(0)

View File

@@ -0,0 +1 @@
elasticsearch

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python
from distutils.core import setup
import os
import glob
import pprint
f = {}
data_files = [('/usr/local/nxapi/', ['nx_datas/country2coords.txt']),
('/usr/local/etc/', ['nxapi.json'])]
#modules = []
for dirname, dirnames, filenames in os.walk('tpl/'):
for filename in filenames:
if filename.endswith(".tpl"):
print dirname+"#"+filename
if "/usr/local/nxapi/"+dirname not in f.keys():
f["/usr/local/nxapi/"+dirname] = []
f["/usr/local/nxapi/"+dirname].append(os.path.join(dirname, filename))
for z in f.keys():
data_files.append( (z, f[z]))
setup(name='nxtool',
version='1.0',
description='Naxsi log parser, whitelist & report generator',
author='Naxsi Dev Team',
author_email='thibault.koechlin@nbs-system.com',
url='http://github.com/nbs-system/naxsi',
scripts=['nxtool.py'],
packages=['nxapi'],
data_files=data_files
)

View File

@@ -0,0 +1,11 @@
{
"var_name" : "__utmz",
"id" : "1009 or 1010 or 1005 or 1011",
"zone" : "ARGS",
"_statics" : {
"id" : "1009,1010,1005,1011"
},
"_msg" : "google analytics, __utmz var in ARGS"
}

View File

@@ -0,0 +1,8 @@
{
"_msg" : "A generic, precise wl tpl (url+var+id)",
"zone" : "ARGS",
"var_name" : "?",
"id" : "?",
"uri" : "?",
"_warnings" : { "template_uri" : [ ">=", "5"]}
}

View File

@@ -0,0 +1,11 @@
{
"_msg" : "A generic, wide (id+zone) wl",
"_success" : { "template_uri" : [ ">", "5"],
"rule_uri" : [ ">", "5"]},
"_warnings" : { "rule_var_name" : [ "<=", "5" ],
"rule_uri" : [ "<=", "5" ] },
"_deny" : { "rule_var_name" : [ "<", "10" ] },
"zone" : "ARGS",
"id" : "?"
}

View File

@@ -0,0 +1,8 @@
{
"_msg" : "A generic whitelist, true for the whole uri",
"zone" : "ARGS|NAME",
"uri" : "?",
"id" : "?",
"_warnings" : { "template_uri" : [ ">", "5" ] },
"_success" : { "rule_var_name" : [ ">", "5" ] }
}

View File

@@ -0,0 +1,8 @@
{
"_msg" : "A generic whitelist, true for the whole uri",
"zone" : "ARGS",
"uri" : "?",
"id" : "?",
"_deny" : { "rule_var_name" : [ "<=", "3" ]},
"_success" : { "rule_var_name" : [ ">", "3" ]}
}

View File

@@ -0,0 +1,9 @@
{
"_msg" : "A generic, precise wl tpl (url+var+id)",
"zone" : "BODY",
"var_name" : "?",
"id" : "?",
"uri" : "?",
"_warnings" : { "template_uri" : [ "<", "5"],
"template_var_name" : [ "<", "5"]}
}

View File

@@ -0,0 +1,8 @@
{
"_msg" : "A generic, wide (id+zone) wl",
"zone" : "BODY",
"id" : "?",
"_success" : { "template_uri" : [ ">", "5"],
"template_var_name" : [ ">", "5"]},
"_deny" : { "rule_var_name" : [ "<", "10" ] }
}

View File

@@ -0,0 +1,7 @@
{
"_msg" : "A generic whitelist, true for the whole uri, BODY|NAME",
"zone" : "BODY|NAME",
"uri" : "?",
"id" : "?",
"_warnings" : { "template_uri" : [ ">", "5"] }
}

View File

@@ -0,0 +1,8 @@
{
"_warnings" : { "template_uri" : [ ">", "5"]},
"_deny" : {"rule_var_name" : ["<", "5"]},
"_msg" : "A generic whitelist, true for the whole uri",
"zone" : "BODY",
"uri" : "?",
"id" : "?"
}

View File

@@ -0,0 +1,8 @@
{
"zone" : "BODY",
"var_name" : "?",
"id" : "?",
"_msg" : "A generic rule to spot var-name specific WL",
"_success" : { "rule_uri" : [ ">", "2"]},
"_deny" : { "rule_uri" : ["<", "2"]}
}

View File

@@ -0,0 +1,5 @@
{
"_success" : { "template_uri" : [ ">=", "5"] },
"zone" : "HEADERS",
"var_name" : "cookie",
"id" : "?"}

View File

@@ -0,0 +1,6 @@
{
"id" : "1002",
"zone" : "URL",
"_success" : { "template_uri" : [ ">", "5"],
"rule_uri" : [ ">", "5"]}
}

View File

@@ -0,0 +1,7 @@
{
"_success" : { "template_uri" : [ ">=", "5"],
"rule_uri" : [ ">=", "5"]},
"zone" : "URL",
"id" : "?"
}

View File

@@ -0,0 +1,6 @@
{
"_deny" : { "template_uri" : [ ">", "5" ] },
"uri" : "?",
"zone" : "URL",
"id" : "?"
}

1208
naxsi-0.55.3/t/00naxsi_base.t vendored Normal file

File diff suppressed because it is too large Load Diff

1194
naxsi-0.55.3/t/01naxsi_whitelists.t vendored Normal file

File diff suppressed because it is too large Load Diff

319
naxsi-0.55.3/t/02naxsi_bypass.t vendored Normal file
View File

@@ -0,0 +1,319 @@
#vi:filetype=perl
# A AJOUTER :
# TEST CASE AVEC UNE REGLE SUR UN HEADER GENERIQUE
# La même sur des arguments :)
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== TEST 1: Basic GET request
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=buibui
--- error_code: 200
=== TEST 2: DENY : XSS bypass vector 1 (basic url encode)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=%2f%3cSc%3E
--- error_code: 412
=== TEST 2.1: DENY : XSS bypass vector 2 (\x encode)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=\x2f\x3cSc\x3E
--- error_code: 412
=== TEST 2.2: DENY : XSS bypass vector %00 (nullbyte)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=a%00<%00script
--- error_code: 412
=== TEST 2.3: DENY : XSS bypass vector %00 (nullbyte) URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a%00aaa
--- error_code: 400
=== TEST 3.0: DENY : bypass vector ? (multi arg break)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=a?<x
--- error_code: 412
=== TEST 3.1: DENY : ? break (multi ?)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<a?a
--- error_code: 412
=== TEST 4.0: malformed URIs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?&&val
--- error_code: 200
=== TEST 4.01: malformed URIs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?&&va<l
--- error_code: 412
=== TEST 4.1: malformed URIs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?val&&
--- error_code: 412
=== TEST 4.2: malformed URIs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?&val
--- error_code: 200
=== TEST 4.21: malformed URIs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?&va<l
--- error_code: 412
=== TEST 4.3: malformed URIs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?val&
--- error_code: 412

156
naxsi-0.55.3/t/03naxsi_profile.t vendored Normal file
View File

@@ -0,0 +1,156 @@
#vi:filetype=perl
# A AJOUTER :
# TEST CASE AVEC UNE REGLE SUR UN HEADER GENERIQUE
# La même sur des arguments :)
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== TEST 1: Basic GET request
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=buibui
--- error_code: 200
=== TEST 2: DENY : Obvious GET XSS
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a="><ScRiPt>alert(1)</scRiPt>
--- error_code: 412
=== TEST 2.1: DENY : Obvious RFI
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=http://evil.com/eva.txt
--- error_code: 412
=== TEST 2.3: DENY : Obvious LFI
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 2" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=../../../../../bar.txt
--- error_code: 412
=== TEST 3: OBVIOUS GET SQL INJECTION
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=1'+Or+'1'='1
--- error_code: 412
=== TEST 3bis: OBVIOUS (quoteless) GET SQL INJECTION
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=1+UnIoN+SeLeCt+1
--- error_code: 412

1003
naxsi-0.55.3/t/04naxsi_files.t vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,359 @@
#vi:filetype=perl
# A AJOUTER :
# TEST CASE AVEC UNE REGLE SUR UN HEADER GENERIQUE
# La même sur des arguments :)
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== WL TEST 5.0: Two whitelists on two named arguments, same URL
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:bla|$URL:/buixor";
BasicRule wl:1998 "mz:$ARGS_VAR:blu|$URL:/buixor";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?bla=1999
--- error_code: 200
=== WL TEST 5.1: Two whitelists on two named arguments, same URL
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:bla|$URL:/buixor";
BasicRule wl:1998 "mz:$ARGS_VAR:blu|$URL:/buixor";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?blu=1999
--- error_code: 412
=== WL TEST 5.2: Two whitelists on two named arguments, same URL
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:bla|$URL:/buixor";
BasicRule wl:1998 "mz:$ARGS_VAR:blu|$URL:/buixor";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?bla=1999&blu=1998
--- error_code: 200
=== WL TEST 5.3: Two whitelists on two named arguments, same URL
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:bla|$URL:/buixor";
BasicRule wl:1998 "mz:$ARGS_VAR:blu|$URL:/buixor";
}
location /RequestDenied {
return 412;
}
--- request
GET /?buixor=1998
--- error_code: 412
=== WL TEST 5.4: Whitelists on ARGS/URLs that are URLencoded
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:b_@_la|$URL:/buixor";
BasicRule wl:1998 "mz:$ARGS_VAR:blu|$URL:/buixor";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?b_@_la=1999
--- error_code: 200
=== WL TEST 5.5: Whitelists on ARGS/URLs that are URLencoded
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:b[]la|$URL:/buixor";
BasicRule wl:1998 "mz:$ARGS_VAR:blu|$URL:/buixor";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?b]la=1999
--- error_code: 412
=== WL TEST 6: Whitelists trying to provoke collisions ($ARGS_VAR:x + $URL:x|ARGS)
--- user_files
>>> buixor
eh yo
>>> bla
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
# BasicRule wl:1999 "mz:$ARGS_VAR:/bla";
BasicRule wl:1998 "mz:$URL:/bla|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /bla?1998
--- error_code: 200
=== WL TEST 6.0: Whitelists trying to provoke collisions ($ARGS_VAR:x + $URL:x|ARGS)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
# BasicRule wl:1999 "mz:$ARGS_VAR:/bla";
BasicRule wl:1998 "mz:$URL:/bla|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?/bla=1998
--- error_code: 412
=== WL TEST 6.1: Whitelists trying to provoke collisions ($ARGS_VAR:x + $URL:x|ARGS)
--- user_files
>>> buixor
eh yo
>>> bla
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:bla";
BasicRule wl:1998 "mz:$URL:/bla|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /bla?bla=1999&toto=1998
--- error_code: 200
=== WL TEST 6.2: Whitelists trying to provoke collisions ($ARGS_VAR:x + $URL:x|ARGS)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:/bla";
BasicRule wl:1998 "mz:$URL:/bla|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?/bla=1999
--- error_code: 200
=== WL TEST 6.3: Whitelists trying to provoke collisions ($ARGS_VAR:x + $URL:x|ARGS)
--- user_files
>>> buixor
eh yo
>>> bla
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:/bla";
BasicRule wl:1998 "mz:$URL:/bla|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /bla?/bla=1999&bu=1998
--- error_code: 200

130
naxsi-0.55.3/t/06naxsi_weirds.t vendored Normal file
View File

@@ -0,0 +1,130 @@
#vi:filetype=perl
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== WL TEST 1.0: weird request in URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?&&&&a&&&&&
--- error_code: 412
=== WL TEST 1.01: weird request in URL (wl on fullzone)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:12 "mz:ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?&&&&a&&&&&
--- error_code: 200
=== WL TEST 1.02: weird request in URL (wl on zone+URL)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:12 "mz:$URL:/|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?&&&&a&&&&&
--- error_code: 200
=== WL TEST 1.03: weird request in URL (fail wl on zone+bad URL)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:12 "mz:$URL:/a|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?&&&&a&&&&&
--- error_code: 412
=== WL TEST 1.04: weird request in URL (fail wl on bad zone+URL)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:12 "mz:$URL:/|URL";
}
location /RequestDenied {
return 412;
}
--- request
GET /?&&&&a&&&&&
--- error_code: 412

730
naxsi-0.55.3/t/07naxsi_argnames.t vendored Normal file
View File

@@ -0,0 +1,730 @@
#vi:filetype=perl
# A AJOUTER :
# TEST CASE AVEC UNE REGLE SUR UN HEADER GENERIQUE
# La même sur des arguments :)
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== WL TEST 1.0: Obvious test in arg
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=a
--- error_code: 412
=== WL TEST 1.01: Check non-collision of zone and 'name' flag
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule id:5 "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42";
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=foobar
--- error_code: 412
=== WL TEST 1.1: Generic whitelist in ARGS_NAME
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:ARGS|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=a
--- error_code: 200
=== WL TEST 1.11: Generic whitelist in ARGS_NAME, limit
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=a
--- error_code: 412
=== WL TEST 1.12: Generic whitelist in ARGS_NAME, limit
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:ARGS|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=foobar
--- error_code: 412
=== WL TEST 1.2: whitelist in ARGS_NAME+$URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=a
--- error_code: 200
=== WL TEST 1.21: whitelist in ARGS_NAME+$URL, limit
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=a
--- error_code: 200
=== WL TEST 1.22: whitelist in ARGS_NAME+$URL, limit
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=foobar
--- error_code: 412
=== WL TEST 1.3: failed whitelist in ARGS_NAME+$URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/z|ARGS|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=a
--- error_code: 412
=== WL TEST 1.31: failed whitelist in ARGS_NAME+$URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=foobar
--- error_code: 412
=== WL TEST 1.32: failed whitelist in ARGS_NAME+$URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:b|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?b=foobar
--- error_code: 412
=== WL TEST 1.33: failed whitelist in ARGS_NAME+$URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=bui
--- error_code: 200
=== WL TEST 1.34: failed whitelist in ARGS_NAME+$URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:foobra" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:2999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar|NAME";
BasicRule wl:2999 "mz:$URL:/|$ARGS_VAR:foobar";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=foobra
--- error_code: 200
=== WL TEST 1.35: failed whitelist in ARGS_NAME+$URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:foobra" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:2999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar|NAME";
BasicRule wl:2999 "mz:$URL:/|$ARGS_VAR:foobar";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=foobar
--- error_code: 412
=== WL TEST 1.36: failed whitelist in ARGS_NAME+$URL
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:foobra" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:2999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar|NAME";
BasicRule wl:2999 "mz:$URL:/|$ARGS_VAR:foobar";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=foobar
--- error_code: 412
=== WL TEST 1.4: whitelist in ARGS_NAME+$URL+$ARGS_VAR
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=a
--- error_code: 200
=== WL TEST 1.41: whitelist in ARGS_NAME+$URL+$ARGS_VAR
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=foobar
--- error_code: 412
=== WL TEST 1.5: whitelist in ARGS_NAME+$URL+$ARGS_VAR, limit
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=foobar
--- error_code: 412
=== WL TEST 1.51: whitelist in ARGS_NAME+$URL+$ARGS_VAR, limit
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=foo
--- error_code: 200
=== WL TEST 1.6: whitelist in $URL+$ARGS_VAR | NAME, (collision)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar|NAME";
BasicRule wl:1999 "mz:$URL:/|$ARGS_VAR:foobar";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=foobar
--- error_code: 200
=== WL TEST 1.6.1: whitelist in $URL+ARGS | NAME, (collision)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
BasicRule wl:1999 "mz:$URL:/|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=foobar
--- error_code: 200
=== WL TEST 1.6.2: whitelist in $URL+ARGS | NAME, (collision)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
BasicRule wl:1999 "mz:$URL:/|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=lol
--- error_code: 200
=== WL TEST 1.6.3: whitelist in $URL+ARGS | NAME, (collision)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
BasicRule wl:1999 "mz:$URL:/|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?lol=foobar
--- error_code: 200
=== WL TEST 1.6.4: whitelist in $URL+ARGS | NAME, (collision)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
# BasicRule wl:1999 "mz:$URL:/|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?lol=foobar
--- error_code: 412
=== WL TEST 1.6.5: whitelist in $URL+ARGS | NAME, (collision)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
# BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
BasicRule wl:1999 "mz:$URL:/|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=lol
--- error_code: 412
=== WL TEST 1.6.6: whitelist in $URL+ARGS | NAME, (collision)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
# BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
BasicRule wl:1999 "mz:$URL:/|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?lol=foobar
--- error_code: 200
=== WL TEST 1.6.7: whitelist in $URL+ARGS | NAME, (collision)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/|ARGS|NAME";
# BasicRule wl:1999 "mz:$URL:/|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?lol=foobar
--- error_code: 412

372
naxsi-0.55.3/t/08negative_whitelists.t vendored Normal file
View File

@@ -0,0 +1,372 @@
#vi:filetype=perl
# A AJOUTER :
# TEST CASE AVEC UNE REGLE SUR UN HEADER GENERIQUE
# La même sur des arguments :)
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== WL TEST 1.0
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "str:foobar" "msg:foobar test pattern" "mz:$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?b=toto
--- error_code: 412
=== WL TEST 1.01
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "str:foobar" "msg:foobar test pattern" "mz:$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?b=foobar
--- error_code: 200
=== WL TEST 1.03
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "str:foobar" "msg:foobar test pattern" "mz:$URL:/|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=foobar
--- error_code: 404
=== WL TEST 1.04
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "str:foobar" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=foobrar
--- error_code: 412
=== WL TEST 2.0
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "rx:foobar" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=foobrar
--- error_code: 412
=== WL TEST 2.01
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "rx:foobar" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=foobar
--- error_code: 404
=== WL TEST 2.02
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "rx:^foobar" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?b=foobar
--- error_code: 200
=== WL TEST 2.03
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "rx:^foobar" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=rfoobar
--- error_code: 412
=== WL TEST 2.04
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "rx:^foobar" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=foobar
--- error_code: 404
=== WL TEST 2.05
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "rx:^foobar$" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=foobar
--- error_code: 404
=== WL TEST 2.06
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "rx:^foobar$" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=foobara
--- error_code: 412
=== WL TEST 2.07
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "rx:^[0-9]+$" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=foobara
--- error_code: 412
=== WL TEST 2.08
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule negative "rx:^[0-9]+$" "msg:foobar test pattern" "mz:$URL:/a|$ARGS_VAR:b" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /a?b=1234
--- error_code: 404

883
naxsi-0.55.3/t/09sqlmap_tamper.t vendored Normal file
View File

@@ -0,0 +1,883 @@
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== TODO: naxsi does not support utf8, potential bypass. Still too marginal to be worth checking
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=AND+%EF%BC%871%EF%BC%87=%EF%BC%871%EF%BC%87 HTTP/1.0
"
--- error_code: 200
=== TEST 1: hey 2
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=AND+%00%271%00%27=%00%271%00%27 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 3
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=AND+1=1%00 Union select 1 HTTP/1.0
"
--- error_code: 412
=== NOT TODO: base64, not worthing checking
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=MScgQU5EIFNMRUVQKDUpIw== HTTP/1.0
"
--- error_code: 200
=== TEST 1: hey 5
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a='A+NOT+BETWEEN+0+AND+B' HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 6
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=%2553%2545%254c%2545%2543%2554%2520%2546%2549%2545%254c%2544%2520%2546%2552%254f%254d%2520%2554%2541%2542%254c%2545 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 7
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=%53%45%4c%45%43%54%20%46%49%45%4c%44%20%46%52%4f%4d%20%54%41%42%4c%45 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 8
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=%u0053%u0045%u004c%u0045%u0043%u0054%u0020%u0046%u0049%u0045%u004c%u0044%u0020%u0046%u0052%u004f%u004d%u0020%u0054%u0041%u0042%u004c%u0045' HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 9
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=SELECT+*+FROM+users+WHERE+id+LIKE+1 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 10
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=value'/*!0UNION/*!0ALL/*!0SELECT/*!0CONCAT(/*!0CHAR(58,107,112,113,58),/*!0IFNULL(CAST(/*!0CURRENT_USER()/*!0AS/*!0CHAR),/*!0CHAR(32)),/*!0CHAR(58,97,110,121,58)),+NULL,+NULL#/*!0AND+'QDWa'='QDWa HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 11
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=IF(ISNULL(1),+2,+1) HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 12
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1+/*!30000AND+2>1*/-- HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 13
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1+/*!00000AND+2>1*/-- HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 14
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=+UNION+++SELECT++ HTTP/1.0
"
--- error_code: 412
=== IIS/ASP Encoding
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=%S%E%L%E%C%T+%F%I%E%L%D+%F%R%O%M+%T%A%B%L%E HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 16
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1 UnioN SeLEct 1 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 17
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=AND+1=1+and+'0having'='0having' HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 18
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=SELECT/**/id/**/FROM/**/users HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 19
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1--PTTmJopxdWJ%0AAND--cWfcVRPV%0A9227=9227 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 20
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1%23PTTmJopxdWJ%0AAND%23cWfcVRPV%0A9227=9227 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 21
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1%23PTTmJopxdWJ%0AAND%23cWfcVRPV%0A9227=9227 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 22
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=SELECT%08id%02FROM%0Fusers HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 23
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1%23%0A9227=922%237 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 24
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=SELECT%0Bid%0BFROM%A0users HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 25
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1--%0AAND--%0A9227=9227 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 26
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=SELECT+id+FROM+users HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 28
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1%bf%27+AND+1=1--%20 HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 29
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1/*!UNION*//*!ALL*//*!SELECT*//*!NULL*/,/*!NULL*/,+CONCAT(CHAR(58,104,116,116,58),IFNULL(CAST(CURRENT_USER()/*!AS*//*!CHAR*/),CHAR(32)),CHAR(58,100,114,117,58))# HTTP/1.0
"
--- error_code: 412
=== TEST 1: hey 30
--- main_config
working_directory /tmp/;
worker_rlimit_core 25M;
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- raw_request eval
"GET /?a=1/*!UNION*//*!ALL*//*!SELECT*//*!NULL*/,/*!NULL*/,/*!CONCAT*/(/*!CHAR*/(58,122,114,115,58),/*!IFNULL*/(CAST(/*!CURRENT_USER*/()/*!AS*//*!CHAR*/),/*!CHAR*/(32)),/*!CHAR*/(58,115,114,121,58))# HTTP/1.0
"
--- error_code: 412

423
naxsi-0.55.3/t/10naxsi_modifiers.t vendored Normal file
View File

@@ -0,0 +1,423 @@
#vi:filetype=perl
use lib 'lib';
use Test::Nginx::Socket;
repeat_each(3);
plan tests => repeat_each(1) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== TEST 1.0 : Runtime Learning force (per ip)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
if ($remote_addr = "127.0.0.1") {
set $naxsi_flag_learning 1;
}
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 1.01 : Runtime Learning force (absolute)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_flag_learning 1;
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 1.1: Runtime Learning force (fail - per ip)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
if ($remote_addr = "127.0.0.42") {
set $naxsi_flag_learning 1;
}
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 412
=== TEST 1.2: Runtime Learning force (fail - in location)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
# this will not work, as naxsi
# is processed before var set in location.
set $naxsi_flag_learning 1;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 412
=== TEST 1.3: Runtime Learning disable (per ip)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
if ($remote_addr = "127.0.0.1") {
set $naxsi_flag_learning 0;
}
location / {
SecRulesEnabled;
LearningMode;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 412
=== TEST 1.4: Runtime Learning disable (fail - per ip)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
if ($remote_addr = "127.0.0.42") {
set $naxsi_flag_learning 0;
}
location / {
SecRulesEnabled;
LearningMode;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 2.00 : Check that SecRulesDisabled correctly works
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
SecRulesDisabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 2: Runtime disable force (absolute)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_flag_enable 0;
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 2.2: Runtime enable force
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_flag_enable 1;
location / {
SecRulesEnabled;
SecRulesDisabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 412
=== TEST 2.3: Runtime enable force, with static learning (which is pointless)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_flag_enable 1;
location / {
LearningMode;
SecRulesEnabled;
SecRulesDisabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 2.4: Runtime enable + learning mode (absolute)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_flag_learning 1;
set $naxsi_flag_enable 1;
location / {
SecRulesEnabled;
SecRulesDisabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 3.0: Runtime enable + learning mode (per ip)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
if ($remote_addr = "127.0.0.1") {
set $naxsi_flag_enable 1;
set $naxsi_flag_learning 1;
}
location / {
SecRulesEnabled;
SecRulesDisabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 3.1: Runtime enable + learning mode (per ip)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
if ($remote_addr = "127.0.0.42") {
set $naxsi_flag_enable 1;
set $naxsi_flag_learning 1;
}
location / {
SecRulesEnabled;
SecRulesDisabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 3.2: Runtime enable + learning mode (per ip)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_flag_enable 1;
if ($remote_addr = "127.0.0.1") {
set $naxsi_flag_learning 1;
}
location / {
SecRulesEnabled;
SecRulesDisabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 200
=== TEST 3.3: Runtime enable (success) + learning mode (fail - per ip)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_flag_enable 1;
if ($remote_addr = "127.0.0.42") {
set $naxsi_flag_learning 1;
}
location / {
SecRulesEnabled;
SecRulesDisabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?a=<>
--- error_code: 412

2363
naxsi-0.55.3/t/11naxsi_newstyle_config.t vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
#vi:filetype=perl
# A AJOUTER :
# TEST CASE AVEC UNE REGLE SUR UN HEADER GENERIQUE
# La même sur des arguments :)
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== WL TEST 1.0: Obvious test in arg
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=a
--- error_code: 412
=== WL TEST 1.1: Obvious test in arg
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:foobar|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foobar=a
--- error_code: 200

80
naxsi-0.55.3/t/13test.t vendored Normal file
View File

@@ -0,0 +1,80 @@
# This File is used for broken tests.
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
# This one should actually return 200, but a hashtable collision happens
=== WL TEST 6.1: Whitelist provoking collision
--- user_files
>>> buixor
eh yo
>>> bla
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/bla|ARGS|NAME";
BasicRule wl:1998 "mz:$URL:/bla|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /bla?blx=1998&1999=bla
--- error_code: 200
=== WL TEST 6.2: Trigger multi-line logs
--- user_files
>>> buixor
eh yo
>>> bla
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL:/bla|ARGS|NAME";
BasicRule wl:1998 "mz:$URL:/bla|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA10=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA11=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA13=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA14=1998&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA15=1998
--- error_code: 200

742
naxsi-0.55.3/t/14json.t vendored Normal file
View File

@@ -0,0 +1,742 @@
#vi:filetype=perl
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== JSON0 : Valid JSON
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAlso\": [\"GML\", \"XML\"]
},
\"GlossSee\": \"markup\"
}
}
}
}
}
"
--- error_code: 200
=== JSON1 : invalid JSON (double closing ']')
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAlso\": [\"GML\", \"XML\"]]
},
\"GlossSee\": \"markup\"
}
}
}
}
}
"
--- error_code: 412
=== JSON2 : invalid JSON (missing closing ']')
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAlso\": [\"GML\", \"XML\"
},
\"GlossSee\": \"markup\"
}
}
}
}
}
"
--- error_code: 412
=== JSON3 : invalid JSON (closing array with '}' instead of ']')
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAlso\": [\"GML\", \"XML\"}
},
\"GlossSee\": \"markup\"
}
}
}
}
}
"
--- error_code: 412
=== JSON4 : invalid JSON (Missing final closing '}')
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAlso\": [\"GML\", \"XML\"]
},
\"GlossSee\": \"markup\"
}
}
}
}
"
--- error_code: 412
=== JSON5 : invalid JSON (Extra closing '}')
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAlso\": [\"GML\", \"XML\"]
},
\"GlossSee\": \"markup\"
}
}
}
}
}}"
--- error_code: 412
=== JSON6 : invalid JSON (Missing ',' in array)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAlso\": [\"GML\" \"XML\"]
},
\"GlossSee\": \"markup\"
}
}
}
}
}"
--- error_code: 412
=== JSON7 : Valid JSON with empty array item (Extra ',' in array)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAlso\": [\"GML\",\"XML\",]
},
\"GlossSee\": \"markup\"
}
}
}
}
}"
--- error_code: 200
=== JSON8 : valid JSON - too deep !
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{{{{{{{{{{{{[\"lol\"]}}}}}}}}}}}}"
--- error_code: 412
=== JSON9 : Valid JSON with ev0l stuff (array => var content)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAlso\": [\"G<ML\",\"XML\",]
},
\"GlossSee\": \"markup\"
}
}
}
}
}"
--- error_code: 412
=== JSON10 : Valid JSON with ev0l stuff (array => var name)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"glossary\": {
\"title\": \"example glossary\",
\"GlossDiv\": {
\"title\": \"S\",
\"GlossList\": {
\"GlossEntry\": {
\"ID\": \"SGML\",
\"SortAs\": \"SGML\",
\"GlossTerm\": \"Standard Generalized Markup Language\",
\"Acronym\": \"SGML\",
\"Abbrev\": \"ISO 8879:1986\",
\"GlossDef\": {
\"para\": \"A meta-markup language used to create markup languages such as DocBook.\",
\"GlossSeeAl<so\": [\"GML\",\"XML\",]
},
\"GlossSee\": \"markup\"
}
}
}
}
}"
--- error_code: 412
=== JSON11 : Empty JSON object
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
}"
--- error_code: 200
=== JSON12 : malformed (closing object before array) Json
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"fuu\" : [\"laul\", {\"die\" : \"nope\" ]}
}"
--- error_code: 412
=== JSON12 : malformed (unescaped quotes)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"fuu\" : [\"laul\", {\"die\" : \"n\"ope\" }]
}"
--- error_code: 412
=== JSON12 : escaped quotes
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_extensive_log 1;
location / {
BasicRule wl:1001,1205;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"fuu\" : [\"laul\", {\"die\" : \"n\\\"ope\" }]
}"
--- error_code: 200
=== JSON13 : concatenation attempt (ie "foo":"bar"+eval(evil)+"foo")
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_extensive_log 1;
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"fuu\" : \"oh \"+eval(evil)+\" my\"]
}"
--- error_code: 412
=== JSON13 : concatenation attempt (ie "foo":"bar"+eval(evil)+"foo")
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_extensive_log 1;
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"obvious\" : \"a<a\"]
}"
--- error_code: 412
=== JSON14 : unfinished sub object
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
set $naxsi_extensive_log 1;
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"obvious\" : \"a<a\",
\"fu\" : { \"aa\" : \"bb\"
}"
--- error_code: 412

559
naxsi-0.55.3/t/15json_wl.t vendored Normal file
View File

@@ -0,0 +1,559 @@
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== json wl 0.1 : no rulematch
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"lol\" : \"bar\"
}
"
--- error_code: 200
=== json wl 0.2 : rulematch
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"lol\" : \"foobar\"
}
"
--- error_code: 412
=== json wl 0.3 : rulematch + wl on full zone
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:BODY";
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"lol\" : \"foobar\"
}
"
--- error_code: 200
=== json wl 0.4 : rulematch + wl on zone + varname
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$BODY_VAR:lol";
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"lol\" : \"foobar\"
}
"
--- error_code: 200
=== json wl 0.5 : rulematch + wl on zone + varname + url
--- user_files
>>> test_uri
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$BODY_VAR:lol|$URL:/test_uri";
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /test_uri
{
\"lol\" : \"foobar\"
}
"
--- error_code: 200
=== json wl 0.6 : rulematch + wl on zone + varname + url [fail]
--- user_files
>>> test_uri
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$BODY_VAR:lol|$URL:/test_uri";
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"lol\" : \"foobar\"
}
"
--- error_code: 412
=== json wl 0.7 : rulematch + wl on zone + varname (in sub-json element)
--- user_files
>>> test_uri
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$BODY_VAR:test_123|$URL:/test_uri";
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /test_uri
{
\"oh\" : [\"there\", \"is\", \"no\", \"way\"],
\"this\" : { \"will\" : [\"work\", \"does\"],
\"it\" : \"??\" },
\"trigger\" : {\"test_123\" : [\"foobar\", \"will\", \"trigger\", \"it\"]},
\"foo\" : \"baar\"
}
"
--- error_code: 200
=== json wl 0.8 : rulematch + wl on zone + varname (in sub-json element) [fail]
--- user_files
>>> test_uri
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$BODY_VAR:test_123|$URL:/test_uri";
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /test_uri
{
\"oh\" : [\"there\", \"is\", \"no\", \"way\"],
\"this\" : { \"will\" : [\"work\", \"does\"],
\"it\" : \"??\" },
\"trigger\" : {\"test_1234\" : [\"foobar\", \"will\", \"trigger\", \"it\"]},
\"foo\" : \"baar\"
}
"
--- error_code: 412
=== json wl 0.9 : match in varname
--- user_files
>>> test_uri
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /test_uri
{
\"oh\" : [\"there\", \"is\", \"no\", \"way\"],
\"this\" : { \"will\" : [\"work\", \"does\"],
\"it\" : \"??\" },
\"tr<igger\" : {\"test_1234\" : [\"foobar\", \"will\", \"trigger\", \"it\"]},
\"foo\" : \"baar\"
}
"
--- error_code: 412
=== json wl 1.0 : match in varname + wl on varname
--- user_files
>>> test_uri
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1302 "mz:$BODY_VAR:tr<igger|NAME";
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /test_uri
{
\"oh\" : [\"there\", \"is\", \"no\", \"way\"],
\"this\" : { \"will\" : [\"work\", \"does\"],
\"it\" : \"??\" },
\"tr<igger\" : {\"test_1234\" : [\"foobar\", \"will\", \"trigger\", \"it\"]},
\"foo\" : \"baar\"
}
"
--- error_code: 200
=== json wl 1.1 : match (empty variable name)
--- user_files
>>> test_uri
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /test_uri
{
\"\" : [\"there\", \"is\", \"no\", \"way\"]
}
"
--- error_code: 200
=== json wl 1.1 : match (no variable name)
--- user_files
>>> test_uri
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /test_uri
{
[\"there\", \"is\", \"no\", \"way\"]
}
"
--- error_code: 200
=== json wl 2.0 : malformed json (missing opening {)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
\"lol\" : \"bar\"
}
"
--- error_code: 412
=== json wl 2.1 : Numeric content json
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"lol\" : 372
}
"
--- error_code: 200
=== json wl 2.2 : true/false content json
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"lol\" : false,
\"serious_stuff\" : true,
\"extra_coverage\" : null
}
"
--- error_code: 200
=== json wl 2.3 : malformed json
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:BODY" "s:$SQL:42" id:1999;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
error_page 405 = $uri;
}
location /RequestDenied {
return 412;
}
--- more_headers
Content-Type: application/json
--- request eval
use URI::Escape;
"POST /
{
\"lol\" : false,
\"serious_stuff\" : true,
\"extra_coverage\" : null
"
--- error_code: 412

574
naxsi-0.55.3/t/16rx_mz.t vendored Normal file
View File

@@ -0,0 +1,574 @@
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== RXWL TEST 1.0: simple wide regex ($args_var)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:bla";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?bla=1999
--- error_code: 200
=== RXWL TEST 1.1: simple wide regex ($args_var)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:bla";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?bra=1999
--- error_code: 412
=== RXWL TEST 1.2: simple wide regex ($args_var)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:bla";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?aablaaa=1999
--- error_code: 200
=== RXWL TEST 1.3: simple end-restrictive regex ($args_var_x:..$)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:bla$";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?aabla=1999
--- error_code: 200
=== RXWL TEST 1.3: simple end-restrictive regex ($args_var_x:..$)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:bla$";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?aabla=1999
--- error_code: 200
=== RXWL TEST 1.4: simple end-restrictive regex ($args_var_x:..$)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:bla$";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?aablaa=1999
--- error_code: 412
=== RXWL TEST 1.5: simple begin-restrictive regex ($args_var_x:^..)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:^bla";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?blaa=1999
--- error_code: 200
=== RXWL TEST 1.6: simple begin-restrictive regex ($args_var_x:^..)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:^bla";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?blaa=1999
--- error_code: 200
=== RXWL TEST 1.7: simple begin-restrictive regex ($args_var_x:^..)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:^bla";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?ablaa=1999
--- error_code: 412
=== RXWL TEST 1.8: simple full-restrictive regex ($args_var_x:^..$)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:^bla$";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?abla=1999
--- error_code: 412
=== RXWL TEST 1.9: simple full-restrictive regex ($args_var_x:^..$)
--- user_files
>>> buixor
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:^bla$";
}
location /RequestDenied {
return 412;
}
--- request
GET /buixor?bla=1999
--- error_code: 200
=== RXWL TEST 2.0: simple wide regex ($args_var|$url)
--- user_files
>>> foo
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:bla|$URL_X:/foo";
}
location /RequestDenied {
return 412;
}
--- request
GET /foo?bla=1999
--- error_code: 200
=== RXWL TEST 2.1: simple wide regex ($args_var|$url)
--- user_files
>>> foo
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:bla|$URL_X:/foo";
}
location /RequestDenied {
return 412;
}
--- request
GET /foz?bla=1999
--- error_code: 412
=== RXWL TEST 2.2: simple half-restrictive regex ($args_var|$url)
--- user_files
>>> foo
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:^bla$|$URL_X:/foo";
}
location /RequestDenied {
return 412;
}
--- request
GET /foo?blaz=1999
--- error_code: 412
=== RXWL TEST 3.0: simple wide regex (url|args|name)
--- user_files
>>> foo
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL_X:/foo|ARGS|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /foo?19991999=foo
--- error_code: 200
=== RXWL TEST 3.1: simple wide regex (url|args|name)
--- user_files
>>> foo
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL_X:/foo|ARGS|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /foo?foo=1999
--- error_code: 412
=== RXWL TEST 4.0: simple restrictive+complex regex ($URL_X|URL)
--- user_files
>>> foo
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS|URL" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$URL_X:^/foo_[0-9]+_$|URL";
}
location /RequestDenied {
return 412;
}
--- request
GET /foo_1999_?x=x
--- error_code: 404
=== RXWL TEST 4.1: simple restrictive+complex regex ($ARGS_VAR_X|NAME)
--- user_files
>>> foo
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS|URL" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:^foo_[0-9]+_$|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foo_1999_inject=x
--- error_code: 412
=== RXWL TEST 5.0: file ext ($URL|NAME) XXX
--- user_files
>>> foo
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS|URL" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:^foo_[0-9]+_$|NAME";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foo_1999_inject=x
--- error_code: 412
=== RXWL TEST 6.0: case sensitiveness
--- user_files
>>> foo
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:abcd" "msg:foobar test pattern #1" "mz:ARGS|URL" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR_X:^foo_[0-9]+_$";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foo_1999_=ABCD
--- error_code: 200

99
naxsi-0.55.3/t/17case.t vendored Normal file
View File

@@ -0,0 +1,99 @@
#vi:filetype=perl
# A AJOUTER :
# TEST CASE AVEC UNE REGLE SUR UN HEADER GENERIQUE
# La même sur des arguments :)
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== WL TEST X.0: URL case sensitive wl
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999,1000 "mz:$URL:/foobar/tableDropdown|URL";
}
location /RequestDenied {
return 412;
}
--- request
GET /foobar/tableDropdown
--- error_code: 404
=== WL TEST X.1: URL case sensitive wl
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:foobar" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1000 "mz:$URL:/wp-content/plugins/ultimate-tinymce/tableDropdown/editor_plugin.js|URL";
}
location /RequestDenied {
return 412;
}
--- request
GET /wp-content/plugins/ultimate-tinymce/tableDropdown/editor_plugin.js
--- error_code: 404
=== WL TEST 6.3: Whitelists trying to provoke collisions
--- user_files
>>> buixor
eh yo
>>> bla
eh yo
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1999" "msg:foobar test pattern #2" "mz:ARGS" "s:$SQL:42" id:1999;
--- config
location / {
#LearningMode;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:$ARGS_VAR:/bla";
BasicRule wl:1998 "mz:$URL:/bla|ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /bla?/bla=1999&bu=1998
--- error_code: 200

354
naxsi-0.55.3/t/18ids.t vendored Normal file
View File

@@ -0,0 +1,354 @@
use lib 'lib';
use Test::Nginx::Socket;
plan tests => repeat_each(2) * blocks();
no_root_location();
no_long_string();
$ENV{TEST_NGINX_SERVROOT} = server_root();
run_tests();
__DATA__
=== ID TEST 1.0: Disabled IDs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999;
}
location /RequestDenied {
return 412;
}
--- request
GET /?bla=1999
--- error_code: 200
=== ID TEST 1.1: Disabled IDs (fail)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999;
}
location /RequestDenied {
return 412;
}
--- request
GET /?bla=1998
--- error_code: 412
=== ID TEST 1.2: Disabled negative IDs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:-1999;
}
location /RequestDenied {
return 412;
}
--- request
GET /?bla=1998
--- error_code: 200
=== ID TEST 1.3: Disabled negative IDs (fail)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:-1999;
}
location /RequestDenied {
return 412;
}
--- request
GET /?bla=1999
--- error_code: 412
=== ID TEST 1.4: Multiple Disabled negative IDs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1997" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1997;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:-1999,-1998;
}
location /RequestDenied {
return 412;
}
--- request
GET /?bla=1997
--- error_code: 200
=== ID TEST 1.5: Multiple Disabled negative IDs
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1997" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1997;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:-1999,-1998;
}
location /RequestDenied {
return 412;
}
--- request
GET /?bla=1999
--- error_code: 412
=== ID TEST 2.0: BasicRule negative id test
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1997" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1997;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:-1999 "mz:$URL:/|$ARGS_VAR:foo";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foo=1999
--- error_code: 412
=== ID TEST 2.1: BasicRule negative id test (fail)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1997" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1997;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:-1999 "mz:$URL:/|$ARGS_VAR:foo";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foo=1998
--- error_code: 200
=== ID TEST 2.2: BasicRule negative id test (fail on internal ID)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1999;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
MainRule "str:1997" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1997;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:-1999 "mz:$URL:/|$ARGS_VAR:foo";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foo=a%00a
--- error_code: 412
=== ID TEST 3.0: Partial disabled whitelist
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS|URL" "s:$SQL:42" id:1999;
# MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
# MainRule "str:1997" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1997;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /?foo=a1999a
--- error_code: 200
=== ID TEST 3.1: Partial disabled whitelist (fail zone)
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1999" "msg:foobar test pattern #1" "mz:ARGS|URL" "s:$SQL:42" id:1999;
# MainRule "str:1998" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1998;
# MainRule "str:1997" "msg:foobar test pattern #1" "mz:ARGS" "s:$SQL:42" id:1997;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1999 "mz:ARGS";
}
location /RequestDenied {
return 412;
}
--- request
GET /1999?foo=aa
--- error_code: 412
=== ID TEST 4.0: header disabled rule
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:HEADERS|ARGS" "s:$SQL:42" id:1998;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
}
location /RequestDenied {
return 412;
}
--- more_headers
foo: 1998
--- request
GET /
--- error_code: 412
=== ID TEST 4.1: header disabled rule wl
--- main_config
load_module /tmp/naxsi_ut/modules/ngx_http_naxsi_module.so;
--- http_config
include /tmp/naxsi_ut/naxsi_core.rules;
MainRule "str:1998" "msg:foobar test pattern #1" "mz:HEADERS|ARGS" "s:$SQL:42" id:1998;
--- config
location / {
SecRulesEnabled;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
root $TEST_NGINX_SERVROOT/html/;
index index.html index.htm;
BasicRule wl:1998 "mz:HEADERS";
}
location /RequestDenied {
return 412;
}
--- more_headers
foo: 1998
--- request
GET /
--- error_code: 200

Some files were not shown because too many files have changed in this diff Show More