diff --git a/appfw/src/sqlfw.c b/appfw/src/sqlfw.c index 44918509daeb4d1686c8bc8d77204e07e9cd393d..b449d5133dc14b50f27b4d20bfc689cd993311af 100644 --- a/appfw/src/sqlfw.c +++ b/appfw/src/sqlfw.c @@ -86,7 +86,8 @@ int sqlfw_verify_s(const char *zSql, char *p_critical) bzero(structure, len); // get all the critical keywords - int result_flag = sqlfw_get_structure(zSql, markings, structure); + int is_tautology = 0; + int result_flag = sqlfw_get_structure(zSql, markings, structure, &is_tautology); strcpy(p_critical, markings); if(verbose) @@ -169,13 +170,28 @@ int sqlfw_verify(const char *zSql, char **errMsg) return 0; } - // get all the critical keywords - sqlfw_get_structure(zSql, tainted, structure); - int success = appfw_establish_taint_fast2(zSql, tainted, FALSE); - if (!success && verbose) - appfw_display_taint("SQL Injection detected", zSql, tainted); + int is_tautology = 0; + int success = 1; + + // get all the critical keywords / detect tautologies + sqlfw_get_structure(zSql, tainted, structure, &is_tautology); + if (is_tautology && verbose) + { + appfw_display_taint("SQL Injection detected (tautology)", zSql, tainted); + success = 0; + } + + if (!is_tautology) + { + success = appfw_establish_taint_fast2(zSql, tainted, FALSE); + + if (!success && verbose) + appfw_display_taint("SQL Injection detected", zSql, tainted); + } free(tainted); + free(structure); + return success; } @@ -195,6 +211,7 @@ static char *CRITICAL_FUNCTIONS[] = { "COLLATION", "CONCAT", "CONVERT", + "COS", "CRC32", "CURRENT_USER", "DATABASE", @@ -232,6 +249,7 @@ static char *CRITICAL_FUNCTIONS[] = { "POSITION", "POW", "QUARTER", + "RAND", "REVERSE", "RIGHT", "ROUND", @@ -239,6 +257,7 @@ static char *CRITICAL_FUNCTIONS[] = { "SCHEMA", "SESSION_USER", "SHA", + "SIN", "SPACE", "STRCMP", "SUBSTR", @@ -310,13 +329,125 @@ char get_violation_marking(char p_current_marking) return APPFW_SECURITY_VIOLATION; } +#define TT_NUM_TOKENS 4 +#define MAX_TOKEN_SIZE 256 +struct tt_sql_tokens { + int type; + char data[MAX_TOKEN_SIZE]; +}; + +void tt_clear(struct tt_sql_tokens *tokens, int tt_num_tokens) +{ + memset(tokens, 0, sizeof(struct tt_sql_tokens) * tt_num_tokens); +} + +void tt_save_token(struct tt_sql_tokens *tokens, int *tt_num_tokens, int tokenType, const char *zSql, int beg, int end) +{ + int tokenid = *tt_num_tokens; + if (tokenid >= TT_NUM_TOKENS) + { + fprintf(stderr, "tt_save_token(): fatal error in tautology detector\n"); + return; + } + + tokens[tokenid].type = tokenType; + int len = end - beg + 1; + strncpy(tokens[tokenid].data, &zSql[beg], len); + tokens[tokenid].data[len] = 0; + + *tt_num_tokens = tokenid + 1; +} + +// detect tautology +// OR <string> <OP> <string> +// OR <numeric> <OP> <numeric> +// e.g.: +// OR 1 = 1 +// OR 1.23 >= 1.2 +// OR 'a' = 'a' +// OR 'a' < 'b' +// +int tt_detect(struct tt_sql_tokens *tokens, int tt_num_tokens) +{ + if (tt_num_tokens != 4 || + tokens[0].type != TK_OR || + (tokens[2].type != TK_EQ && + tokens[2].type != TK_NE && + tokens[2].type != TK_GT && + tokens[2].type != TK_LT && + tokens[2].type != TK_GE && + tokens[2].type != TK_LE)) + return 0; + + double val1, val3; + if (tokens[1].type != TK_STRING) + { + if ((tokens[1].type != TK_INTEGER && tokens[1].type != TK_FLOAT) || + (tokens[3].type != TK_INTEGER && tokens[3].type != TK_FLOAT)) + return 0; + if (sscanf(tokens[1].data, "%lf", &val1) != 1) + return 0; + if (sscanf(tokens[3].data, "%lf", &val3) != 1) + return 0; + } + + if (tokens[2].type == TK_EQ) + { + if (tokens[1].type == TK_STRING) + return strcmp(tokens[1].data, tokens[3].data) == 0; + else + return val1 == val3; + } + else if (tokens[2].type == TK_NE) + { + if (tokens[1].type == TK_STRING) + return strcmp(tokens[1].data, tokens[3].data) != 0; + else + return val1 != val3; + } + else if (tokens[2].type == TK_GT) + { + if (tokens[1].type == TK_STRING) + return strcmp(tokens[1].data, tokens[3].data) > 0; + else + return val1 > val3; + } + else if (tokens[2].type == TK_LT) + { + if (tokens[1].type == TK_STRING) + return strcmp(tokens[1].data, tokens[3].data) < 0; + else + return val1 < val3; + } + else if (tokens[2].type == TK_GE) + { + if (tokens[1].type == TK_STRING) + return strcmp(tokens[1].data, tokens[3].data) > 0 || + strcmp(tokens[1].data, tokens[3].data) == 0; + else + return val1 >= val3; + } + else if (tokens[2].type == TK_LE) + { + if (tokens[1].type == TK_STRING) + return strcmp(tokens[1].data, tokens[3].data) < 0 || + strcmp(tokens[1].data, tokens[3].data) == 0; + else + return val1 <= val3; + } + + return 0; // by default, return false +} + /* ** Run the original sqlite parser on the given SQL string. -** Identify critical tokens in query +** out: +** Identify critical tokens in query +** Simple tautology detector for OR clause ** ** original code: SQLITE_PRIVATE int sqlite3_sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ */ -int sqlfw_get_structure(const char *zSql, char *p_annot, char *p_structure) +int sqlfw_get_structure(const char *zSql, char *p_annot, char *p_structure, int *is_tautology) { Parse *pParse; int nErr = 0; /* Number of errors encountered */ @@ -333,6 +464,15 @@ int sqlfw_get_structure(const char *zSql, char *p_annot, char *p_structure) int comment_2_started = 0; int result_flag = S3_SQL_SAFE; int verbose = getenv("APPFW_VERBOSE") ? TRUE : FALSE; + int very_verbose = getenv("APPFW_VERY_VERBOSE") ? TRUE : FALSE; + + // for tautology detection + struct tt_sql_tokens tt_tokens[TT_NUM_TOKENS]; + int tt_num_tokens = 0; + int tt_do_save_tokens = 0; + *is_tautology = 0; + + tt_clear(tt_tokens, TT_NUM_TOKENS); // by default mark critial tokens as security violations // in a subsequent stage, we will attempt to bless them via dna shotgun sequencing @@ -359,7 +499,6 @@ int sqlfw_get_structure(const char *zSql, char *p_annot, char *p_structure) return S3_SQL_PARSE_ERROR; } - while( zSql[i]!=0 ) { pParse->sLastToken.z = &zSql[i]; pParse->sLastToken.n = appfw_sqlite3GetToken((unsigned char*)&zSql[i],&tokenType); @@ -397,7 +536,7 @@ int sqlfw_get_structure(const char *zSql, char *p_annot, char *p_structure) // here we have a SQL terminator; we need to parse the next statement // so we recursively call ourself if (end+1 < strlen(zSql)) - return result_flag | sqlfw_get_structure(&zSql[end+1], &p_annot[end+1], p_structure); + return result_flag | sqlfw_get_structure(&zSql[end+1], &p_annot[end+1], p_structure, is_tautology); else { return result_flag; // semicolon was the last character in the entire statement return @@ -407,13 +546,13 @@ int sqlfw_get_structure(const char *zSql, char *p_annot, char *p_structure) // show token info -/* - fprintf(stderr, "\n----------------------\n"); - fprintf(stderr, "token: ["); - for (k = beg; k <= end; ++k) - fprintf(stderr,"%c (%d)", zSql[k], p_annot[k]); - fprintf(stderr, "] type: %d [%d..%d]\n", tokenType, beg, end); -*/ + if (very_verbose) { + fprintf(stderr, "\n----------------------\n"); + fprintf(stderr, "token: ["); + for (k = beg; k <= end; ++k) + fprintf(stderr,"%c (%d)", zSql[k], p_annot[k]); + fprintf(stderr, "] type: %d [%d..%d]\n", tokenType, beg, end); + } appfw_sqlite3Parser(pEngine, tokenType, pParse->sLastToken, pParse); @@ -423,12 +562,55 @@ int sqlfw_get_structure(const char *zSql, char *p_annot, char *p_structure) continue; } + if (tokenType == TK_OR && tt_num_tokens == 0 && (*is_tautology == 0)) + tt_do_save_tokens = 1; + + if (tt_do_save_tokens) + { + switch(tt_num_tokens) { + case 0: // this must be the OR + tt_save_token(tt_tokens, &tt_num_tokens, tokenType, zSql, beg, end); + break; + case 1: // this must be a string, an integer or a float + if (tokenType == TK_STRING || tokenType == TK_INTEGER || tokenType == TK_FLOAT) + { + tt_save_token(tt_tokens, &tt_num_tokens, tokenType, zSql, beg, end); + } + else + { + tt_clear(tt_tokens, TT_NUM_TOKENS); + tt_do_save_tokens = 0; + } + break; + case 2: // this must be an operator: =, >, <, >=, <= + if (tokenType == TK_EQ || tokenType == TK_NE || tokenType == TK_GT || tokenType == TK_LT || tokenType == TK_GE || tokenType == TK_LE) + { + tt_save_token(tt_tokens, &tt_num_tokens, tokenType, zSql, beg, end); + } + else + { + tt_clear(tt_tokens, TT_NUM_TOKENS); + tt_do_save_tokens = 0; + } + break; + case 3: // this must be a string, an integer or a float + if (tokenType == TK_STRING || tokenType == TK_INTEGER || tokenType == TK_FLOAT) + { + tt_save_token(tt_tokens, &tt_num_tokens, tokenType, zSql, beg, end); + *is_tautology = tt_detect(tt_tokens, tt_num_tokens); // detect tautology here + } + tt_clear(tt_tokens, TT_NUM_TOKENS); + tt_do_save_tokens = 0; + break; + } + } + + char temp_identifier[4096]; switch (tokenType) { // so here we would need to add all the token types that should not be p_annot // this would be any SQL keywords - case TK_STRING: strcat(p_structure, "d "); break; @@ -446,16 +628,15 @@ int sqlfw_get_structure(const char *zSql, char *p_annot, char *p_structure) // if it's one of the identifier we care about, then fallthrough // list is not exhaustive, need to track all relevant ones - case TK_EXPLAIN: case TK_ANALYZE: - case TK_OR: case TK_AND: case TK_IS: case TK_BETWEEN: case TK_IN: case TK_ISNULL: case TK_NOTNULL: + case TK_OR: case TK_NE: case TK_EQ: case TK_GT: @@ -509,10 +690,10 @@ int sqlfw_get_structure(const char *zSql, char *p_annot, char *p_structure) strcat(p_structure, " "); } break; - } + } // end switch (tokenType) for critical identifiers break; } - } + } // end switch (tokenType) // handle comments if (end + 1 < strlen(zSql)) @@ -589,3 +770,5 @@ void sqlfw_save_query_structure_cache(const char *p_file) { saveQueryStructureCache(p_file); } + + diff --git a/appfw/src/sqlfw.h b/appfw/src/sqlfw.h index e75b1605d304f28d0b9a704bab61d311808ce03d..1210b8bc0870f67e7083aa9df053df0cdfb7aa26 100644 --- a/appfw/src/sqlfw.h +++ b/appfw/src/sqlfw.h @@ -17,7 +17,7 @@ extern int sqlfw_isInitialized(); // extern int sqlfw_verify(const char *zSql, char **pzErrMsg); extern int sqlfw_verify(const char *zSql, char **); extern int sqlfw_verify_s(const char *zSql, char *p_annot); -extern int sqlfw_get_structure(const char *zSql, char *p_taint, char *p_structure); +extern int sqlfw_get_structure(const char *zSql, char *p_taint, char *p_structure, int *is_tautology); extern int sqlfw_is_safe(int); extern int sqlfw_is_error(int);