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

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;
}
}
}