mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2025-09-29 19:24:29 +03:00
Update to libinjection 3.0.0-pre8
This commit is contained in:
@@ -37,7 +37,7 @@ extern "C" {
|
|||||||
* See python's normalized version
|
* See python's normalized version
|
||||||
* http://www.python.org/dev/peps/pep-0386/#normalizedversion
|
* http://www.python.org/dev/peps/pep-0386/#normalizedversion
|
||||||
*/
|
*/
|
||||||
#define LIBINJECTION_VERSION "3.0.0-pre2"
|
#define LIBINJECTION_VERSION "3.0.0-pre8"
|
||||||
|
|
||||||
#define ST_MAX_SIZE 32
|
#define ST_MAX_SIZE 32
|
||||||
#define MAX_TOKENS 5
|
#define MAX_TOKENS 5
|
||||||
@@ -46,7 +46,13 @@ extern "C" {
|
|||||||
#define CHAR_SINGLE '\''
|
#define CHAR_SINGLE '\''
|
||||||
#define CHAR_DOUBLE '"'
|
#define CHAR_DOUBLE '"'
|
||||||
|
|
||||||
|
#define COMMENTS_ANSI 0
|
||||||
|
#define COMMENTS_MYSQL 1
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
#ifdef SWIG
|
||||||
|
%immutable;
|
||||||
|
#endif
|
||||||
char type;
|
char type;
|
||||||
char str_open;
|
char str_open;
|
||||||
char str_close;
|
char str_close;
|
||||||
@@ -55,6 +61,10 @@ typedef struct {
|
|||||||
} stoken_t;
|
} stoken_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
#ifdef SWIG
|
||||||
|
%immutable;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* input */
|
/* input */
|
||||||
const char *s;
|
const char *s;
|
||||||
size_t slen;
|
size_t slen;
|
||||||
@@ -73,13 +83,40 @@ typedef struct {
|
|||||||
/* +1 for ending null */
|
/* +1 for ending null */
|
||||||
char pat[MAX_TOKENS + 1];
|
char pat[MAX_TOKENS + 1];
|
||||||
char delim;
|
char delim;
|
||||||
|
char comment_style;
|
||||||
|
|
||||||
int reason;
|
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;
|
||||||
|
|
||||||
|
int stats_comment_c;
|
||||||
|
|
||||||
|
int stats_folds;
|
||||||
|
|
||||||
} sfilter;
|
} sfilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pointer to function, takes cstr input, returns 1 for true, 0 for false
|
* Pointer to function, takes cstr input, returns 1 for true, 0 for false
|
||||||
*/
|
*/
|
||||||
typedef int (*ptr_fingerprints_fn)(const char*, void* callbackarg);
|
typedef int (*ptr_fingerprints_fn)(sfilter*, void* callbackarg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main API: tests for SQLi in three possible contexts, no quotes,
|
* Main API: tests for SQLi in three possible contexts, no quotes,
|
||||||
@@ -112,28 +149,51 @@ int libinjection_is_sqli(sfilter * sql_state,
|
|||||||
* CHAR_SINGLE ('), single quote context
|
* CHAR_SINGLE ('), single quote context
|
||||||
* CHAR_DOUBLE ("), double quote context
|
* CHAR_DOUBLE ("), double quote context
|
||||||
* Other values will likely be ignored.
|
* Other values will likely be ignored.
|
||||||
* \param ptr_fingerprints_fn is a pointer to a function
|
|
||||||
* that determines if a fingerprint is a match or not.
|
|
||||||
* \param callbackarg passed to function above
|
|
||||||
*
|
*
|
||||||
*
|
* \return pointer to sfilter.pat as convience.
|
||||||
* \return 1 (true) if SQLi or 0 (false) if not SQLi **in this context**
|
* do not free!
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
int libinjection_is_string_sqli(sfilter * sql_state,
|
const char* libinjection_sqli_fingerprint(sfilter * sql_state,
|
||||||
const char *s, size_t slen,
|
const char *s, size_t slen,
|
||||||
const char delim,
|
char delim,
|
||||||
ptr_fingerprints_fn fn, void* callbackarg);
|
char comment_style);
|
||||||
|
|
||||||
/* FOR H@CKERS ONLY
|
/* FOR H@CKERS ONLY
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void libinjection_sqli_init(sfilter* sql_state, const char* str,
|
void libinjection_sqli_init(sfilter* sql_state,
|
||||||
size_t slen, char delim);
|
const char* s, size_t slen,
|
||||||
|
char delim, char comment_style);
|
||||||
|
|
||||||
int libinjection_sqli_tokenize(sfilter * sql_state, stoken_t *ouput);
|
int libinjection_sqli_tokenize(sfilter * sql_state, stoken_t *ouput);
|
||||||
|
|
||||||
|
/** The built-in default function to match fingerprints
|
||||||
|
* and do false negative/positive analysis. This calls the following
|
||||||
|
* two functions. With this, you other-ride one part or the other.
|
||||||
|
*
|
||||||
|
* return libinjection_sqli_blacklist(sql_state, callbackarg) &&
|
||||||
|
* libinject_sqli_not_whitelist(sql_state, callbackarg);
|
||||||
|
*
|
||||||
|
* \param sql_state should be filled out after libinjection_sqli_fingerprint is called
|
||||||
|
* \param callbackarg is unused but here to be used with API.
|
||||||
|
*/
|
||||||
|
int libinjection_sqli_check_fingerprint(sfilter *sql_state, void* callbackarg);
|
||||||
|
|
||||||
|
/* Given a pattern determine if it's a SQLi pattern.
|
||||||
|
*
|
||||||
|
* \return TRUE if sqli, false otherwise
|
||||||
|
*/
|
||||||
|
int libinjection_sqli_blacklist(sfilter* 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(sfilter* sql_state);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -47,16 +47,13 @@ memchr2(const char *haystack, size_t haystack_len, char c0, char c1)
|
|||||||
if (haystack_len < 2) {
|
if (haystack_len < 2) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (c0 == c1) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (cur < last) {
|
while (cur < last) {
|
||||||
if (cur[0] == c0) {
|
if (cur[0] == c0) {
|
||||||
if (cur[1] == c1) {
|
if (cur[1] == c1) {
|
||||||
return cur;
|
return cur;
|
||||||
} else {
|
} else {
|
||||||
cur += 2;
|
cur += 2; //(c0 == c1) ? 1 : 2;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cur += 1;
|
cur += 1;
|
||||||
@@ -66,6 +63,24 @@ memchr2(const char *haystack, size_t haystack_len, char c0, char c1)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
static const char *
|
||||||
|
my_memmem(const char* haystack, size_t hlen, const char* needle, size_t nlen)
|
||||||
|
{
|
||||||
|
assert(haystack);
|
||||||
|
assert(needle);
|
||||||
|
assert(nlen > 1);
|
||||||
|
const char* cur;
|
||||||
|
const char* last = haystack + hlen - nlen;
|
||||||
|
for (cur = haystack; cur <= last; ++cur) {
|
||||||
|
if (cur[0] == needle[0] && memcmp(cur, needle, nlen) == 0) {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/** Find largest string containing certain characters.
|
/** Find largest string containing certain characters.
|
||||||
*
|
*
|
||||||
* C Standard library 'strspn' only works for 'c-strings' (null terminated)
|
* C Standard library 'strspn' only works for 'c-strings' (null terminated)
|
||||||
@@ -107,6 +122,17 @@ strlencspn(const char *s, size_t len, const char *accept)
|
|||||||
}
|
}
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
static int char_is_white(char ch) {
|
||||||
|
/* ' ' space is 0x32
|
||||||
|
'\t 0x09 \011 horizontal tab
|
||||||
|
'\n' 0x0a \012 new line
|
||||||
|
'\v' 0x0b \013 verical tab
|
||||||
|
'\f' 0x0c \014 new page
|
||||||
|
'\r' 0x0d \015 carriage return
|
||||||
|
0xa0 \240 is latin1
|
||||||
|
*/
|
||||||
|
return strchr(" \t\n\v\f\r\240", ch) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ASCII half-case-insenstive compare!
|
* ASCII half-case-insenstive compare!
|
||||||
@@ -203,17 +229,6 @@ static int bsearch_cstrcase(const char *key, const char *base[], size_t nmemb)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#define UNUSED(x) (void)(x)
|
|
||||||
|
|
||||||
static int is_sqli_pattern(const char* key, void* callbackarg)
|
|
||||||
{
|
|
||||||
UNUSED(callbackarg);
|
|
||||||
return bsearch_cstr(key, sql_fingerprints, sqli_fingerprints_sz);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
@@ -262,10 +277,7 @@ static char is_keyword(const char* key)
|
|||||||
|
|
||||||
static void st_clear(stoken_t * st)
|
static void st_clear(stoken_t * st)
|
||||||
{
|
{
|
||||||
st->type = CHAR_NULL;
|
memset(st, 0, sizeof(stoken_t));
|
||||||
st->str_open = CHAR_NULL;
|
|
||||||
st->str_close = CHAR_NULL;
|
|
||||||
st->val[0] = CHAR_NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void st_assign_char(stoken_t * st, const char stype, const char value)
|
static void st_assign_char(stoken_t * st, const char stype, const char value)
|
||||||
@@ -275,8 +287,8 @@ static void st_assign_char(stoken_t * st, const char stype, const char value)
|
|||||||
st->val[1] = CHAR_NULL;
|
st->val[1] = CHAR_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void st_assign(stoken_t * st, const char stype, const char *value,
|
static void st_assign(stoken_t * st, const char stype,
|
||||||
size_t len)
|
const char *value, size_t len)
|
||||||
{
|
{
|
||||||
size_t last = len < ST_MAX_SIZE ? len : (ST_MAX_SIZE - 1);
|
size_t last = len < ST_MAX_SIZE ? len : (ST_MAX_SIZE - 1);
|
||||||
st->type = stype;
|
st->type = stype;
|
||||||
@@ -367,13 +379,28 @@ static size_t parse_dash(sfilter * sf)
|
|||||||
const size_t slen = sf->slen;
|
const size_t slen = sf->slen;
|
||||||
size_t pos = sf->pos;
|
size_t pos = sf->pos;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* five cases
|
||||||
|
* 1) --[white] this is always a SQL comment
|
||||||
|
* 2) --[EOF] this is a comment
|
||||||
|
* 3) --[notwhite] in MySQL this is NOT a comment but two unary operators
|
||||||
|
* 4) --[notwhite] everyone else thinks this is a comment
|
||||||
|
* 5) -[not dash] '-' is a unary operator
|
||||||
|
*/
|
||||||
|
|
||||||
size_t pos1 = pos + 1;
|
if (pos + 2 < slen && cs[pos + 1] == '-' && char_is_white(cs[pos+2]) ) {
|
||||||
if (pos1 < slen && cs[pos1] == '-') {
|
return parse_eol_comment(sf);
|
||||||
|
} else if (pos +2 == slen && cs[pos + 1] == '-') {
|
||||||
|
return parse_eol_comment(sf);
|
||||||
|
} else if (pos + 1 < slen && cs[pos + 1] == '-' && sf->comment_style == COMMENTS_ANSI) {
|
||||||
|
/* --[not-white] not-white case:
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
sf->stats_comment_ddx += 1;
|
||||||
return parse_eol_comment(sf);
|
return parse_eol_comment(sf);
|
||||||
} else {
|
} else {
|
||||||
st_assign_char(sf->current, 'o', '-');
|
st_assign_char(sf->current, 'o', '-');
|
||||||
return pos1;
|
return pos + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,7 +609,7 @@ static size_t parse_string_core(const char *cs, const size_t len, size_t pos,
|
|||||||
st_assign(st, 's', cs + pos + offset, len - pos - offset);
|
st_assign(st, 's', cs + pos + offset, len - pos - offset);
|
||||||
st->str_close = CHAR_NULL;
|
st->str_close = CHAR_NULL;
|
||||||
return len;
|
return len;
|
||||||
} else if (*(qpos - 1) != '\\') {
|
} else if (qpos == cs || *(qpos - 1) != '\\') {
|
||||||
/*
|
/*
|
||||||
* ending quote is not escaped.. copy and end
|
* ending quote is not escaped.. copy and end
|
||||||
*/
|
*/
|
||||||
@@ -673,42 +700,156 @@ static size_t parse_string_tick(sfilter *sf)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t parse_word(sfilter * sf)
|
/** MySQL ad-hoc character encoding
|
||||||
|
*
|
||||||
|
* if something starts with a underscore
|
||||||
|
* check to see if it's in this form
|
||||||
|
* _[a-z0-9] and if it's a character encoding
|
||||||
|
* If not, let the normal 'word parser'
|
||||||
|
* handle it.
|
||||||
|
*/
|
||||||
|
static size_t parse_underscore(sfilter *sf)
|
||||||
{
|
{
|
||||||
const char *cs = sf->s;
|
const char *cs = sf->s;
|
||||||
|
size_t slen = sf->slen;
|
||||||
size_t pos = sf->pos;
|
size_t pos = sf->pos;
|
||||||
char *dot;
|
|
||||||
char ch;
|
char ch;
|
||||||
size_t slen =
|
|
||||||
strlencspn(cs + pos, sf->slen - pos,
|
|
||||||
" .`<>:\\?=@!#~+-*/&|^%(),';\r\n\t\"\013\014");
|
|
||||||
|
|
||||||
st_assign(sf->current, 'n', cs + pos, slen);
|
size_t xlen = strlenspn(cs + pos + 1, slen - pos - 1,
|
||||||
|
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||||
|
if (xlen == 0) {
|
||||||
|
return parse_word(sf);
|
||||||
|
}
|
||||||
|
st_assign(sf->current, 'n', cs + pos, xlen);
|
||||||
|
ch = is_keyword(sf->current->val);
|
||||||
|
if (ch == 't') {
|
||||||
|
sf->current->type = 't';
|
||||||
|
return xlen + 1;
|
||||||
|
}
|
||||||
|
return parse_word(sf);
|
||||||
|
}
|
||||||
|
|
||||||
dot = strchr(sf->current->val, '.');
|
static size_t parse_ustring(sfilter * sf)
|
||||||
if (dot != NULL) {
|
{
|
||||||
*dot = '\0';
|
const char *cs = sf->s;
|
||||||
|
size_t slen = sf->slen;
|
||||||
|
size_t pos = sf->pos;
|
||||||
|
|
||||||
ch = is_keyword(sf->current->val);
|
if (pos + 2 < slen && cs[pos+1] == '&' && cs[pos+2] == '\'') {
|
||||||
|
sf->pos += 2;
|
||||||
|
pos = parse_string(sf);
|
||||||
|
sf->current->str_open = 'u';
|
||||||
|
if (sf->current->str_close == '\'') {
|
||||||
|
sf->current->str_close = 'u';
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
} else {
|
||||||
|
return parse_word(sf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ch == 'k' || ch == 'o' || ch == 'E') {
|
static size_t parse_qstring_core(sfilter * sf, int offset)
|
||||||
/*
|
{
|
||||||
* we got something like "SELECT.1"
|
char ch;
|
||||||
*/
|
const char *strend;
|
||||||
sf->current->type = ch;
|
const char *cs = sf->s;
|
||||||
return pos + strlen(sf->current->val);
|
size_t slen = sf->slen;
|
||||||
} else {
|
size_t pos = sf->pos + offset;
|
||||||
/*
|
|
||||||
* something else, put back dot
|
/* if we are already at end of string..
|
||||||
*/
|
if current char is not q or Q
|
||||||
*dot = '.';
|
if we don't have 2 more chars
|
||||||
|
if char2 != a single quote
|
||||||
|
then, just treat as word
|
||||||
|
*/
|
||||||
|
if (pos >= slen ||
|
||||||
|
(cs[pos] != 'q' && cs[pos] != 'Q') ||
|
||||||
|
pos + 2 >= slen ||
|
||||||
|
cs[pos + 1] != '\'') {
|
||||||
|
return parse_word(sf);
|
||||||
|
}
|
||||||
|
|
||||||
|
ch = cs[pos + 2];
|
||||||
|
if (ch < 33 && ch > 127) {
|
||||||
|
return parse_word(sf);
|
||||||
|
}
|
||||||
|
switch (ch) {
|
||||||
|
case '(' : ch = ')'; break;
|
||||||
|
case '[' : ch = ']'; break;
|
||||||
|
case '{' : ch = '}'; break;
|
||||||
|
case '<' : ch = '>'; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
strend = memchr2(cs + pos + 3, slen - pos - 3, ch, '\'');
|
||||||
|
if (strend == NULL) {
|
||||||
|
st_assign(sf->current, 's', cs + pos + 3, slen - pos - 3);
|
||||||
|
sf->current->str_open = 'q';
|
||||||
|
sf->current->str_close = CHAR_NULL;
|
||||||
|
return slen;
|
||||||
|
} else {
|
||||||
|
st_assign(sf->current, 's', cs + pos + 3, strend - cs - pos - 3);
|
||||||
|
sf->current->str_open = 'q';
|
||||||
|
sf->current->str_close = 'q';
|
||||||
|
return (strend - cs) + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Oracle's q string
|
||||||
|
*/
|
||||||
|
static size_t parse_qstring(sfilter * sf)
|
||||||
|
{
|
||||||
|
return parse_qstring_core(sf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Oracle's nq string
|
||||||
|
*/
|
||||||
|
static size_t parse_nqstring(sfilter * sf)
|
||||||
|
{
|
||||||
|
return parse_qstring_core(sf, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t parse_word(sfilter * sf)
|
||||||
|
{
|
||||||
|
char ch;
|
||||||
|
char delim;
|
||||||
|
size_t i;
|
||||||
|
const char *cs = sf->s;
|
||||||
|
size_t pos = sf->pos;
|
||||||
|
size_t wlen = strlencspn(cs + pos, sf->slen - pos,
|
||||||
|
" <>:\\?=@!#~+-*/&|^%(),';\t\n\v\f\r\"");
|
||||||
|
|
||||||
|
st_assign(sf->current, 'n', cs + pos, wlen);
|
||||||
|
|
||||||
|
/* now we need to look inside what we good for "." and "`"
|
||||||
|
* and see if what is before is a keyword or not
|
||||||
|
*/
|
||||||
|
for (i =0; i < strlen(sf->current->val); ++i) {
|
||||||
|
delim = sf->current->val[i];
|
||||||
|
if (delim == '.' || delim == '`') {
|
||||||
|
sf->current->val[i] = CHAR_NULL;
|
||||||
|
ch = is_keyword(sf->current->val);
|
||||||
|
if (ch == 'k' || ch == 'o' || ch == 'E') {
|
||||||
|
/* needed for swig */
|
||||||
|
st_clear(sf->current);
|
||||||
|
/*
|
||||||
|
* we got something like "SELECT.1"
|
||||||
|
* or SELECT`column`
|
||||||
|
*/
|
||||||
|
st_assign(sf->current, ch, cs + pos, i);
|
||||||
|
return pos + i;
|
||||||
|
} else {
|
||||||
|
/* restore character */
|
||||||
|
sf->current->val[i] = delim;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* do normal lookup with word including '.'
|
* do normal lookup with word including '.'
|
||||||
*/
|
*/
|
||||||
if (slen < ST_MAX_SIZE) {
|
if (wlen < ST_MAX_SIZE) {
|
||||||
|
|
||||||
ch = is_keyword(sf->current->val);
|
ch = is_keyword(sf->current->val);
|
||||||
|
|
||||||
@@ -717,7 +858,7 @@ static size_t parse_word(sfilter * sf)
|
|||||||
}
|
}
|
||||||
sf->current->type = ch;
|
sf->current->type = ch;
|
||||||
}
|
}
|
||||||
return pos + slen;
|
return pos + wlen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MySQL backticks are a cross between string and
|
/* MySQL backticks are a cross between string and
|
||||||
@@ -794,8 +935,7 @@ static size_t parse_var(sfilter * sf)
|
|||||||
|
|
||||||
|
|
||||||
xlen = strlencspn(cs + pos, slen - pos,
|
xlen = strlencspn(cs + pos, slen - pos,
|
||||||
" <>:\\?=@!#~+-*/&|^%(),';\r\n\t\"\013\014");
|
" <>:\\?=@!#~+-*/&|^%(),';\t\n\v\f\r'`\"");
|
||||||
// "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.$");
|
|
||||||
if (xlen == 0) {
|
if (xlen == 0) {
|
||||||
st_assign(sf->current, 'v', cs + pos, 0);
|
st_assign(sf->current, 'v', cs + pos, 0);
|
||||||
return pos;
|
return pos;
|
||||||
@@ -807,21 +947,73 @@ static size_t parse_var(sfilter * sf)
|
|||||||
|
|
||||||
static size_t parse_money(sfilter *sf)
|
static size_t parse_money(sfilter *sf)
|
||||||
{
|
{
|
||||||
|
const char* strend;
|
||||||
const char *cs = sf->s;
|
const char *cs = sf->s;
|
||||||
const size_t slen = sf->slen;
|
const size_t slen = sf->slen;
|
||||||
size_t pos = sf->pos;
|
size_t pos = sf->pos;
|
||||||
size_t xlen;
|
size_t xlen;
|
||||||
|
|
||||||
|
if (pos + 1 == slen) {
|
||||||
|
/* end of line */
|
||||||
|
st_assign_char(sf->current, 'n', '$');
|
||||||
|
return slen;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* $1,000.00 or $1.000,00 ok!
|
* $1,000.00 or $1.000,00 ok!
|
||||||
* This also parses $....,,,111 but that's ok
|
* This also parses $....,,,111 but that's ok
|
||||||
*/
|
*/
|
||||||
|
|
||||||
xlen = strlenspn(cs + pos + 1, slen - pos - 1, "0123456789.,");
|
xlen = strlenspn(cs + pos + 1, slen - pos - 1, "0123456789.,");
|
||||||
if (xlen == 0) {
|
if (xlen == 0) {
|
||||||
/*
|
if (cs[pos + 1] == '$') {
|
||||||
* just ignore '$'
|
/* we have $$ .. find ending $$ and make string */
|
||||||
*/
|
strend = memchr2(cs + pos + 2, slen - pos -2, '$', '$');
|
||||||
return pos + 1;
|
if (strend == NULL) {
|
||||||
|
/* fell off edge */
|
||||||
|
st_assign(sf->current, 's', cs + pos + 2, slen - (pos + 2));
|
||||||
|
sf->current->str_open = '$';
|
||||||
|
sf->current->str_close = CHAR_NULL;
|
||||||
|
return slen;
|
||||||
|
} else {
|
||||||
|
st_assign(sf->current, 's', cs + pos + 2, strend - (cs + pos + 2));
|
||||||
|
sf->current->str_open = '$';
|
||||||
|
sf->current->str_close = '$';
|
||||||
|
return strend - cs + 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* ok it's not a number or '$$', but maybe it's pgsql "$ quoted strings" */
|
||||||
|
xlen = strlenspn(cs + pos + 1, slen - pos - 1, "abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||||
|
if (xlen == 0) {
|
||||||
|
/* hmm it's "$" _something_ .. just add $ and keep going*/
|
||||||
|
st_assign_char(sf->current, 'n', '$');
|
||||||
|
return pos + 1;
|
||||||
|
}
|
||||||
|
/* we have $foobar????? */
|
||||||
|
/* is it $foobar$ */
|
||||||
|
if (pos + xlen + 1 == slen || cs[pos+xlen+1] != '$') {
|
||||||
|
/* not $foobar$, or fell off edge */
|
||||||
|
st_assign_char(sf->current, 'n', '$');
|
||||||
|
return pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we have $foobar$ ... find it again */
|
||||||
|
strend = my_memmem(cs+xlen+2, slen - (pos+xlen+2), cs + pos, xlen+2);
|
||||||
|
|
||||||
|
if (strend == NULL) {
|
||||||
|
/* fell off edge */
|
||||||
|
st_assign(sf->current, 's', cs+pos+xlen+2, slen - pos - xlen - 2);
|
||||||
|
sf->current->str_open = '$';
|
||||||
|
sf->current->str_close = CHAR_NULL;
|
||||||
|
return slen;
|
||||||
|
} else {
|
||||||
|
/* got one */
|
||||||
|
st_assign(sf->current, 's', cs+pos+xlen+2, strend - (cs + pos + xlen + 2));
|
||||||
|
sf->current->str_open = '$';
|
||||||
|
sf->current->str_close = '$';
|
||||||
|
return (strend + xlen + 2) - cs;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
st_assign(sf->current, '1', cs + pos, 1 + xlen);
|
st_assign(sf->current, '1', cs + pos, 1 + xlen);
|
||||||
return pos + 1 + xlen;
|
return pos + 1 + xlen;
|
||||||
@@ -950,12 +1142,13 @@ int libinjection_sqli_tokenize(sfilter * sf, stoken_t *current)
|
|||||||
* Initializes parsing state
|
* Initializes parsing state
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void libinjection_sqli_init(sfilter * sf, const char *s, size_t len, char delim)
|
void libinjection_sqli_init(sfilter * sf, const char *s, size_t len, char delim, char comment_style)
|
||||||
{
|
{
|
||||||
memset(sf, 0, sizeof(sfilter));
|
memset(sf, 0, sizeof(sfilter));
|
||||||
sf->s = s;
|
sf->s = s;
|
||||||
sf->slen = len;
|
sf->slen = len;
|
||||||
sf->delim = delim;
|
sf->delim = delim;
|
||||||
|
sf->comment_style = comment_style;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** See if two tokens can be merged since they are compound SQL phrases.
|
/** See if two tokens can be merged since they are compound SQL phrases.
|
||||||
@@ -984,7 +1177,7 @@ static int syntax_merge_words(stoken_t * a, stoken_t * b)
|
|||||||
|
|
||||||
if (!
|
if (!
|
||||||
(a->type == 'k' || a->type == 'n' || a->type == 'o'
|
(a->type == 'k' || a->type == 'n' || a->type == 'o'
|
||||||
|| a->type == 'U' || a->type == 'E')) {
|
|| a->type == 'U' || a->type == 'E' || a->type == 't')) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1083,15 +1276,18 @@ int filter_fold(sfilter * sf)
|
|||||||
*/
|
*/
|
||||||
if (sf->tokenvec[left].type == 's' && sf->tokenvec[left+1].type == 's') {
|
if (sf->tokenvec[left].type == 's' && sf->tokenvec[left+1].type == 's') {
|
||||||
pos -= 1;
|
pos -= 1;
|
||||||
|
sf->stats_folds += 1;
|
||||||
continue;
|
continue;
|
||||||
} else if (sf->tokenvec[left].type =='o' && st_is_unary_op(&sf->tokenvec[left+1])) {
|
} else if (sf->tokenvec[left].type =='o' && st_is_unary_op(&sf->tokenvec[left+1])) {
|
||||||
pos -= 1;
|
pos -= 1;
|
||||||
|
sf->stats_folds += 1;
|
||||||
if (left > 0) {
|
if (left > 0) {
|
||||||
left -= 1;
|
left -= 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (sf->tokenvec[left].type =='(' && st_is_unary_op(&sf->tokenvec[left+1])) {
|
} else if (sf->tokenvec[left].type =='(' && st_is_unary_op(&sf->tokenvec[left+1])) {
|
||||||
pos -= 1;
|
pos -= 1;
|
||||||
|
sf->stats_folds += 1;
|
||||||
if (left > 0) {
|
if (left > 0) {
|
||||||
left -= 1;
|
left -= 1;
|
||||||
}
|
}
|
||||||
@@ -1121,8 +1317,16 @@ int filter_fold(sfilter * sf)
|
|||||||
sf->tokenvec[left].type = 'f';
|
sf->tokenvec[left].type = 'f';
|
||||||
continue;
|
continue;
|
||||||
#endif
|
#endif
|
||||||
|
} else if (sf->tokenvec[left].type == 't' &&
|
||||||
|
(sf->tokenvec[left+1].type == 'n' || sf->tokenvec[left+1].type == '1' ||
|
||||||
|
sf->tokenvec[left+1].type == 'v' || sf->tokenvec[left+1].type == 's')) {
|
||||||
|
st_copy(&sf->tokenvec[left], &sf->tokenvec[left+1]);
|
||||||
|
pos -= 1;
|
||||||
|
sf->stats_folds += 1;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* all cases of handing 2 tokens is done
|
/* all cases of handing 2 tokens is done
|
||||||
and nothing matched. Get one more token
|
and nothing matched. Get one more token
|
||||||
*/
|
*/
|
||||||
@@ -1171,13 +1375,18 @@ int filter_fold(sfilter * sf)
|
|||||||
(sf->tokenvec[left+2].type == '1' || sf->tokenvec[left+2].type == 'n')) {
|
(sf->tokenvec[left+2].type == '1' || sf->tokenvec[left+2].type == 'n')) {
|
||||||
pos -= 2;
|
pos -= 2;
|
||||||
continue;
|
continue;
|
||||||
#if 0
|
} else if ((sf->tokenvec[left].type == 'n' || sf->tokenvec[left].type == '1' ||
|
||||||
} else if ((sf->tokenvec[left].type == 'n' || sf->tokenvec[left].type == '1') &&
|
sf->tokenvec[left].type == 'v' || sf->tokenvec[left].type == 's') &&
|
||||||
|
sf->tokenvec[left+1].type == 'o' &&
|
||||||
|
sf->tokenvec[left+2].type == 't') {
|
||||||
|
pos -= 2;
|
||||||
|
sf->stats_folds += 2;
|
||||||
|
continue;
|
||||||
|
} else if ((sf->tokenvec[left].type == 'n' || sf->tokenvec[left].type == '1' || sf->tokenvec[left].type == 's') &&
|
||||||
sf->tokenvec[left+1].type == ',' &&
|
sf->tokenvec[left+1].type == ',' &&
|
||||||
(sf->tokenvec[left+2].type == '1' || sf->tokenvec[left+2].type == 'n')) {
|
(sf->tokenvec[left+2].type == '1' || sf->tokenvec[left+2].type == 'n' || sf->tokenvec[left+2].type == 's')) {
|
||||||
pos -= 2;
|
pos -= 2;
|
||||||
continue;
|
continue;
|
||||||
#endif
|
|
||||||
} else if ((sf->tokenvec[left].type == 'k' || sf->tokenvec[left].type == 'E') &&
|
} else if ((sf->tokenvec[left].type == 'k' || sf->tokenvec[left].type == 'E') &&
|
||||||
st_is_unary_op(&sf->tokenvec[left+1]) &&
|
st_is_unary_op(&sf->tokenvec[left+1]) &&
|
||||||
(sf->tokenvec[left+2].type == '1' || sf->tokenvec[left+2].type == 'n' || sf->tokenvec[left+2].type == 'v' || sf->tokenvec[left+2].type == 's' || sf->tokenvec[left+2].type == 'f' )) {
|
(sf->tokenvec[left+2].type == '1' || sf->tokenvec[left+2].type == 'n' || sf->tokenvec[left+2].type == 'v' || sf->tokenvec[left+2].type == 's' || sf->tokenvec[left+2].type == 'f' )) {
|
||||||
@@ -1234,17 +1443,15 @@ int filter_fold(sfilter * sf)
|
|||||||
* double quote.
|
* double quote.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
int libinjection_is_string_sqli(sfilter * sql_state,
|
const char*
|
||||||
const char *s, size_t slen,
|
libinjection_sqli_fingerprint(sfilter * sql_state,
|
||||||
const char delim,
|
const char *s, size_t slen,
|
||||||
ptr_fingerprints_fn fn, void* callbackarg)
|
char delim, char comment_style)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
int tlen = 0;
|
int tlen = 0;
|
||||||
char ch;
|
|
||||||
int patmatch;
|
|
||||||
|
|
||||||
libinjection_sqli_init(sql_state, s, slen, delim);
|
libinjection_sqli_init(sql_state, s, slen, delim, comment_style);
|
||||||
|
|
||||||
tlen = filter_fold(sql_state);
|
tlen = filter_fold(sql_state);
|
||||||
for (i = 0; i < tlen; ++i) {
|
for (i = 0; i < tlen; ++i) {
|
||||||
@@ -1257,17 +1464,45 @@ int libinjection_is_string_sqli(sfilter * sql_state,
|
|||||||
sql_state->pat[tlen] = CHAR_NULL;
|
sql_state->pat[tlen] = CHAR_NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* check for 'X' in pattern
|
* check for 'X' in pattern, and then
|
||||||
|
* clear out all tokens
|
||||||
|
*
|
||||||
* this means parsing could not be done
|
* this means parsing could not be done
|
||||||
* accurately due to pgsql's double comments
|
* accurately due to pgsql's double comments
|
||||||
* or other syntax that isn't consistent
|
* or other syntax that isn't consistent.
|
||||||
* should be very rare false positive
|
* Should be very rare false positive
|
||||||
*/
|
*/
|
||||||
if (strchr(sql_state->pat, 'X')) {
|
if (strchr(sql_state->pat, 'X')) {
|
||||||
return TRUE;
|
/* needed for SWIG */
|
||||||
|
memset((void*)sql_state->pat, 0, MAX_TOKENS + 1);
|
||||||
|
sql_state->pat[0] = 'X';
|
||||||
|
|
||||||
|
sql_state->tokenvec[0].type = 'X';
|
||||||
|
sql_state->tokenvec[0].val[0] = 'X';
|
||||||
|
sql_state->tokenvec[0].val[1] = '\0';
|
||||||
|
sql_state->tokenvec[1].type = CHAR_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
patmatch = fn(sql_state->pat, callbackarg);
|
return sql_state->pat;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define UNUSED(x) (void)(x)
|
||||||
|
|
||||||
|
int libinjection_sqli_check_fingerprint(sfilter* sql_state, void* callbackarg)
|
||||||
|
{
|
||||||
|
UNUSED(callbackarg);
|
||||||
|
|
||||||
|
return libinjection_sqli_blacklist(sql_state) &&
|
||||||
|
libinjection_sqli_not_whitelist(sql_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
int libinjection_sqli_blacklist(sfilter* sql_state)
|
||||||
|
{
|
||||||
|
int patmatch = bsearch_cstr(sql_state->pat, sql_fingerprints, sqli_fingerprints_sz);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* No match.
|
* No match.
|
||||||
@@ -1280,11 +1515,22 @@ int libinjection_is_string_sqli(sfilter * sql_state,
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* return TRUE if sqli, false is benign
|
||||||
|
*/
|
||||||
|
int libinjection_sqli_not_whitelist(sfilter* sql_state)
|
||||||
|
{
|
||||||
/*
|
/*
|
||||||
* We got a SQLi match
|
* We assume we got a SQLi match
|
||||||
* This next part just helps reduce false positives.
|
* This next part just helps reduce false positives.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
char ch;
|
||||||
|
size_t tlen = strlen(sql_state->pat);
|
||||||
|
|
||||||
switch (tlen) {
|
switch (tlen) {
|
||||||
case 2:{
|
case 2:{
|
||||||
/*
|
/*
|
||||||
@@ -1431,7 +1677,6 @@ int libinjection_is_string_sqli(sfilter * sql_state,
|
|||||||
int libinjection_is_sqli(sfilter * sql_state, const char *s, size_t slen,
|
int libinjection_is_sqli(sfilter * sql_state, const char *s, size_t slen,
|
||||||
ptr_fingerprints_fn fn, void* callbackarg)
|
ptr_fingerprints_fn fn, void* callbackarg)
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* no input? not sqli
|
* no input? not sqli
|
||||||
*/
|
*/
|
||||||
@@ -1440,17 +1685,21 @@ int libinjection_is_sqli(sfilter * sql_state, const char *s, size_t slen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fn == NULL) {
|
if (fn == NULL) {
|
||||||
fn = is_sqli_pattern;
|
fn = libinjection_sqli_check_fingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* test input "as-is"
|
* test input "as-is"
|
||||||
*/
|
*/
|
||||||
if (libinjection_is_string_sqli(sql_state, s, slen, CHAR_NULL,
|
libinjection_sqli_fingerprint(sql_state, s, slen, CHAR_NULL, COMMENTS_ANSI);
|
||||||
fn, callbackarg)) {
|
if (fn(sql_state, callbackarg)) {
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
} else if (sql_state->stats_comment_ddx) {
|
||||||
|
libinjection_sqli_fingerprint(sql_state, s, slen, CHAR_NULL, COMMENTS_MYSQL);
|
||||||
|
if (fn(sql_state, callbackarg)) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* if input has a single_quote, then
|
* if input has a single_quote, then
|
||||||
* test as if input was actually '
|
* test as if input was actually '
|
||||||
@@ -1460,19 +1709,26 @@ int libinjection_is_sqli(sfilter * sql_state, const char *s, size_t slen,
|
|||||||
* is_string_sqli(sql_state, "'" + s, slen+1, NULL, fn, arg)
|
* is_string_sqli(sql_state, "'" + s, slen+1, NULL, fn, arg)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
if (memchr(s, CHAR_SINGLE, slen)
|
if (memchr(s, CHAR_SINGLE, slen)) {
|
||||||
&& libinjection_is_string_sqli(sql_state, s, slen, CHAR_SINGLE,
|
libinjection_sqli_fingerprint(sql_state, s, slen, CHAR_SINGLE, COMMENTS_ANSI);
|
||||||
fn, callbackarg)) {
|
if (fn(sql_state, callbackarg)) {
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
} else if (sql_state->stats_comment_ddx) {
|
||||||
|
libinjection_sqli_fingerprint(sql_state, s, slen, CHAR_SINGLE, COMMENTS_MYSQL);
|
||||||
|
if (fn(sql_state, callbackarg)) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* same as above but with a double-quote "
|
* same as above but with a double-quote "
|
||||||
*/
|
*/
|
||||||
if (memchr(s, CHAR_DOUBLE, slen)
|
if (memchr(s, CHAR_DOUBLE, slen)) {
|
||||||
&& libinjection_is_string_sqli(sql_state, s, slen, CHAR_DOUBLE,
|
libinjection_sqli_fingerprint(sql_state, s, slen, CHAR_DOUBLE, COMMENTS_MYSQL);
|
||||||
fn, callbackarg)) {
|
if (fn(sql_state, callbackarg)) {
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user