Fossil SCM
Test command for the SQL-injection detection routine. Possible performance improvements as well.
Commit
d3cb62f76759c6fa58399dc040944c8da13ad4cc6443bff099ba21a82da4ea0f
Parent
05fa1c3c24de29a…
2 files changed
+8
-7
+99
-10
+8
-7
| --- src/cgi.c | ||
| +++ src/cgi.c | ||
| @@ -1515,29 +1515,30 @@ | ||
| 1515 | 1515 | static void cgi_begone_spider(void){ |
| 1516 | 1516 | Blob content = empty_blob; |
| 1517 | 1517 | |
| 1518 | 1518 | cgi_set_content(&content); |
| 1519 | 1519 | style_set_current_feature("test"); |
| 1520 | - style_header("Spider Detected"); | |
| 1521 | - @ <h2>Begone, Spider!</h2> | |
| 1520 | + style_header("Malicious Query Detected"); | |
| 1521 | + @ <h2>Begone, Hacker!</h2> | |
| 1522 | 1522 | @ <p>This page was generated because Fossil believes it has |
| 1523 | - @ detected a spider-based attack. If you believe you are seeing | |
| 1524 | - @ this in error, please contact us on the forum: https://fossil-scm.org/forum | |
| 1523 | + @ detected an SQL injection attack. If you believe you are seeing | |
| 1524 | + @ this in error, contact the developers on the Fossil-SCM Forum. Type | |
| 1525 | + @ "fossil-scm forum" into any search engine to locate the Fossil-SCM Forum. | |
| 1525 | 1526 | style_finish_page(); |
| 1526 | - cgi_set_status(404,"Spider Detected"); | |
| 1527 | + cgi_set_status(404,"Robot Attack Detected"); | |
| 1527 | 1528 | cgi_reply(); |
| 1528 | 1529 | exit(0); |
| 1529 | 1530 | } |
| 1530 | 1531 | |
| 1531 | 1532 | /* |
| 1532 | -** If might_be_sql() returns true for the given string, calls | |
| 1533 | +** If looks_like_sql_injection() returns true for the given string, calls | |
| 1533 | 1534 | ** cgi_begin_spider() and does not return, else this function has no |
| 1534 | 1535 | ** side effects. The range of checks performed by this function may |
| 1535 | 1536 | ** be extended in the future. |
| 1536 | 1537 | */ |
| 1537 | 1538 | void cgi_value_spider_check(const char *zTxt){ |
| 1538 | - if( might_be_sql(zTxt) ){ | |
| 1539 | + if( looks_like_sql_injection(zTxt) ){ | |
| 1539 | 1540 | cgi_begone_spider(); |
| 1540 | 1541 | } |
| 1541 | 1542 | } |
| 1542 | 1543 | |
| 1543 | 1544 | /* |
| 1544 | 1545 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -1515,29 +1515,30 @@ | |
| 1515 | static void cgi_begone_spider(void){ |
| 1516 | Blob content = empty_blob; |
| 1517 | |
| 1518 | cgi_set_content(&content); |
| 1519 | style_set_current_feature("test"); |
| 1520 | style_header("Spider Detected"); |
| 1521 | @ <h2>Begone, Spider!</h2> |
| 1522 | @ <p>This page was generated because Fossil believes it has |
| 1523 | @ detected a spider-based attack. If you believe you are seeing |
| 1524 | @ this in error, please contact us on the forum: https://fossil-scm.org/forum |
| 1525 | style_finish_page(); |
| 1526 | cgi_set_status(404,"Spider Detected"); |
| 1527 | cgi_reply(); |
| 1528 | exit(0); |
| 1529 | } |
| 1530 | |
| 1531 | /* |
| 1532 | ** If might_be_sql() returns true for the given string, calls |
| 1533 | ** cgi_begin_spider() and does not return, else this function has no |
| 1534 | ** side effects. The range of checks performed by this function may |
| 1535 | ** be extended in the future. |
| 1536 | */ |
| 1537 | void cgi_value_spider_check(const char *zTxt){ |
| 1538 | if( might_be_sql(zTxt) ){ |
| 1539 | cgi_begone_spider(); |
| 1540 | } |
| 1541 | } |
| 1542 | |
| 1543 | /* |
| 1544 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -1515,29 +1515,30 @@ | |
| 1515 | static void cgi_begone_spider(void){ |
| 1516 | Blob content = empty_blob; |
| 1517 | |
| 1518 | cgi_set_content(&content); |
| 1519 | style_set_current_feature("test"); |
| 1520 | style_header("Malicious Query Detected"); |
| 1521 | @ <h2>Begone, Hacker!</h2> |
| 1522 | @ <p>This page was generated because Fossil believes it has |
| 1523 | @ detected an SQL injection attack. If you believe you are seeing |
| 1524 | @ this in error, contact the developers on the Fossil-SCM Forum. Type |
| 1525 | @ "fossil-scm forum" into any search engine to locate the Fossil-SCM Forum. |
| 1526 | style_finish_page(); |
| 1527 | cgi_set_status(404,"Robot Attack Detected"); |
| 1528 | cgi_reply(); |
| 1529 | exit(0); |
| 1530 | } |
| 1531 | |
| 1532 | /* |
| 1533 | ** If looks_like_sql_injection() returns true for the given string, calls |
| 1534 | ** cgi_begin_spider() and does not return, else this function has no |
| 1535 | ** side effects. The range of checks performed by this function may |
| 1536 | ** be extended in the future. |
| 1537 | */ |
| 1538 | void cgi_value_spider_check(const char *zTxt){ |
| 1539 | if( looks_like_sql_injection(zTxt) ){ |
| 1540 | cgi_begone_spider(); |
| 1541 | } |
| 1542 | } |
| 1543 | |
| 1544 | /* |
| 1545 |
+99
-10
| --- src/lookslike.c | ||
| +++ src/lookslike.c | ||
| @@ -460,23 +460,112 @@ | ||
| 460 | 460 | (lookFlags&LOOK_INVALID)?"yes":"no"); |
| 461 | 461 | fossil_print("Has flag LOOK_ODD: %s\n",(lookFlags&LOOK_ODD)?"yes":"no"); |
| 462 | 462 | fossil_print("Has flag LOOK_SHORT: %s\n",(lookFlags&LOOK_SHORT)?"yes":"no"); |
| 463 | 463 | blob_reset(&blob); |
| 464 | 464 | } |
| 465 | + | |
| 466 | +/* | |
| 467 | +** Return true if z[i] is the whole word given by zWord | |
| 468 | +*/ | |
| 469 | +static int isWholeWord(const char *z, unsigned int i, const char *zWord, int n){ | |
| 470 | + if( i>0 && fossil_isalnum(z[i-1]) ) return 0; | |
| 471 | + if( sqlite3_strnicmp(z+i, zWord, n)!=0 ) return 0; | |
| 472 | + if( z[i+n]!=0&& fossil_isalnum(z[i+n]) ) return 0; | |
| 473 | + return 1; | |
| 474 | +} | |
| 465 | 475 | |
| 466 | 476 | /* |
| 467 | 477 | ** Returns true if the given text contains certain keywords or |
| 468 | 478 | ** punctuation which indicate that it might be SQL. This is only a |
| 469 | 479 | ** high-level check, not intended to be used for any application-level |
| 470 | 480 | ** logic other than in defense against spiders in limited contexts. |
| 471 | 481 | */ |
| 472 | -int might_be_sql(const char *zTxt){ | |
| 473 | - if( zTxt==0 || zTxt[0]==0 ) return 0; | |
| 474 | -#define L(GLOB) 0==sqlite3_strlike("%" GLOB "%",zTxt, '%') | |
| 475 | - return L(";") || L("'") | |
| 476 | - || L("select") || L("order") || L("drop") | |
| 477 | - || L(" and ") || L(" or ") | |
| 478 | - /* ^^^^^ noting that \n and \t should also be checked */ | |
| 479 | - || L("null") || L("delete") || L("update") | |
| 480 | - || L("waitfor"); | |
| 481 | -#undef L | |
| 482 | +int looks_like_sql_injection(const char *zTxt){ | |
| 483 | + unsigned int i; | |
| 484 | + if( zTxt==0 ) return 0; | |
| 485 | + for(i=0; zTxt[i]; i++){ | |
| 486 | + switch( zTxt[i] ){ | |
| 487 | + case ';': | |
| 488 | + case '\'': | |
| 489 | + return 1; | |
| 490 | + case 'a': | |
| 491 | + case 'A': | |
| 492 | + if( isWholeWord(zTxt, i, "and", 3) ) return 1; | |
| 493 | + break; | |
| 494 | + case 'n': | |
| 495 | + case 'N': | |
| 496 | + if( isWholeWord(zTxt, i, "null", 4) ) return 1; | |
| 497 | + break; | |
| 498 | + case 'o': | |
| 499 | + case 'O': | |
| 500 | + if( isWholeWord(zTxt, i, "order", 5) ) return 1; | |
| 501 | + if( isWholeWord(zTxt, i, "or", 2) ) return 1; | |
| 502 | + break; | |
| 503 | + case 's': | |
| 504 | + case 'S': | |
| 505 | + if( isWholeWord(zTxt, i, "select", 6) ) return 1; | |
| 506 | + break; | |
| 507 | + case 'w': | |
| 508 | + case 'W': | |
| 509 | + if( isWholeWord(zTxt, i, "waitfor", 7) ) return 1; | |
| 510 | + break; | |
| 511 | + } | |
| 512 | + } | |
| 513 | + return 0; | |
| 514 | +} | |
| 515 | + | |
| 516 | +/* | |
| 517 | +** This is a utility routine associated with the test-looks-like-sql-injection | |
| 518 | +** command. | |
| 519 | +** | |
| 520 | +** Read input from zInFile and print only those lines that look like they | |
| 521 | +** might be SQL injection. | |
| 522 | +** | |
| 523 | +** Or if bInvert is true, then show the opposite - those lines that do NOT | |
| 524 | +** look like SQL injection. | |
| 525 | +*/ | |
| 526 | +static void show_sql_injection_lines( | |
| 527 | + const char *zInFile, /* Name of input file */ | |
| 528 | + int bInvert, /* Invert the sense of the output (-v) */ | |
| 529 | + int bDeHttpize /* De-httpize the inputs. (-d) */ | |
| 530 | +){ | |
| 531 | + FILE *in; | |
| 532 | + char zLine[10000]; | |
| 533 | + if( zInFile==0 || strcmp(zInFile,"-")==0 ){ | |
| 534 | + in = stdin; | |
| 535 | + }else{ | |
| 536 | + in = fopen(zInFile, "rb"); | |
| 537 | + if( in==0 ){ | |
| 538 | + fossil_fatal("cannot open \"%s\" for reading\n", zInFile); | |
| 539 | + } | |
| 540 | + } | |
| 541 | + while( fgets(zLine, sizeof(zLine), in) ){ | |
| 542 | + dehttpize(zLine); | |
| 543 | + if( (looks_like_sql_injection(zLine)!=0) ^ bInvert ){ | |
| 544 | + fossil_print("%s", zLine); | |
| 545 | + } | |
| 546 | + } | |
| 547 | + if( in!=stdin ) fclose(in); | |
| 548 | +} | |
| 549 | + | |
| 550 | +/* | |
| 551 | +** COMMAND: test-looks-like-sql-injection | |
| 552 | +** | |
| 553 | +** Read lines of input from files named as arguments (or from standard | |
| 554 | +** input if no arguments are provided) and print those that look like they | |
| 555 | +** might be part of an SQL injection attack. | |
| 556 | +** | |
| 557 | +** Used to test the looks_lide_sql_injection() utility subroutine, possibly | |
| 558 | +** by piping in actual server log data. | |
| 559 | +*/ | |
| 560 | +void test_looks_like_sql_injection(void){ | |
| 561 | + int i; | |
| 562 | + int bInvert = find_option("invert","v",0)!=0; | |
| 563 | + int bDeHttpize = find_option("dehttpize","d",0)!=0; | |
| 564 | + verify_all_options(); | |
| 565 | + if( g.argc==2 ){ | |
| 566 | + show_sql_injection_lines(0, bInvert, bDeHttpize); | |
| 567 | + } | |
| 568 | + for(i=2; i<g.argc; i++){ | |
| 569 | + show_sql_injection_lines(g.argv[i], bInvert, bDeHttpize); | |
| 570 | + } | |
| 482 | 571 | } |
| 483 | 572 |
| --- src/lookslike.c | |
| +++ src/lookslike.c | |
| @@ -460,23 +460,112 @@ | |
| 460 | (lookFlags&LOOK_INVALID)?"yes":"no"); |
| 461 | fossil_print("Has flag LOOK_ODD: %s\n",(lookFlags&LOOK_ODD)?"yes":"no"); |
| 462 | fossil_print("Has flag LOOK_SHORT: %s\n",(lookFlags&LOOK_SHORT)?"yes":"no"); |
| 463 | blob_reset(&blob); |
| 464 | } |
| 465 | |
| 466 | /* |
| 467 | ** Returns true if the given text contains certain keywords or |
| 468 | ** punctuation which indicate that it might be SQL. This is only a |
| 469 | ** high-level check, not intended to be used for any application-level |
| 470 | ** logic other than in defense against spiders in limited contexts. |
| 471 | */ |
| 472 | int might_be_sql(const char *zTxt){ |
| 473 | if( zTxt==0 || zTxt[0]==0 ) return 0; |
| 474 | #define L(GLOB) 0==sqlite3_strlike("%" GLOB "%",zTxt, '%') |
| 475 | return L(";") || L("'") |
| 476 | || L("select") || L("order") || L("drop") |
| 477 | || L(" and ") || L(" or ") |
| 478 | /* ^^^^^ noting that \n and \t should also be checked */ |
| 479 | || L("null") || L("delete") || L("update") |
| 480 | || L("waitfor"); |
| 481 | #undef L |
| 482 | } |
| 483 |
| --- src/lookslike.c | |
| +++ src/lookslike.c | |
| @@ -460,23 +460,112 @@ | |
| 460 | (lookFlags&LOOK_INVALID)?"yes":"no"); |
| 461 | fossil_print("Has flag LOOK_ODD: %s\n",(lookFlags&LOOK_ODD)?"yes":"no"); |
| 462 | fossil_print("Has flag LOOK_SHORT: %s\n",(lookFlags&LOOK_SHORT)?"yes":"no"); |
| 463 | blob_reset(&blob); |
| 464 | } |
| 465 | |
| 466 | /* |
| 467 | ** Return true if z[i] is the whole word given by zWord |
| 468 | */ |
| 469 | static int isWholeWord(const char *z, unsigned int i, const char *zWord, int n){ |
| 470 | if( i>0 && fossil_isalnum(z[i-1]) ) return 0; |
| 471 | if( sqlite3_strnicmp(z+i, zWord, n)!=0 ) return 0; |
| 472 | if( z[i+n]!=0&& fossil_isalnum(z[i+n]) ) return 0; |
| 473 | return 1; |
| 474 | } |
| 475 | |
| 476 | /* |
| 477 | ** Returns true if the given text contains certain keywords or |
| 478 | ** punctuation which indicate that it might be SQL. This is only a |
| 479 | ** high-level check, not intended to be used for any application-level |
| 480 | ** logic other than in defense against spiders in limited contexts. |
| 481 | */ |
| 482 | int looks_like_sql_injection(const char *zTxt){ |
| 483 | unsigned int i; |
| 484 | if( zTxt==0 ) return 0; |
| 485 | for(i=0; zTxt[i]; i++){ |
| 486 | switch( zTxt[i] ){ |
| 487 | case ';': |
| 488 | case '\'': |
| 489 | return 1; |
| 490 | case 'a': |
| 491 | case 'A': |
| 492 | if( isWholeWord(zTxt, i, "and", 3) ) return 1; |
| 493 | break; |
| 494 | case 'n': |
| 495 | case 'N': |
| 496 | if( isWholeWord(zTxt, i, "null", 4) ) return 1; |
| 497 | break; |
| 498 | case 'o': |
| 499 | case 'O': |
| 500 | if( isWholeWord(zTxt, i, "order", 5) ) return 1; |
| 501 | if( isWholeWord(zTxt, i, "or", 2) ) return 1; |
| 502 | break; |
| 503 | case 's': |
| 504 | case 'S': |
| 505 | if( isWholeWord(zTxt, i, "select", 6) ) return 1; |
| 506 | break; |
| 507 | case 'w': |
| 508 | case 'W': |
| 509 | if( isWholeWord(zTxt, i, "waitfor", 7) ) return 1; |
| 510 | break; |
| 511 | } |
| 512 | } |
| 513 | return 0; |
| 514 | } |
| 515 | |
| 516 | /* |
| 517 | ** This is a utility routine associated with the test-looks-like-sql-injection |
| 518 | ** command. |
| 519 | ** |
| 520 | ** Read input from zInFile and print only those lines that look like they |
| 521 | ** might be SQL injection. |
| 522 | ** |
| 523 | ** Or if bInvert is true, then show the opposite - those lines that do NOT |
| 524 | ** look like SQL injection. |
| 525 | */ |
| 526 | static void show_sql_injection_lines( |
| 527 | const char *zInFile, /* Name of input file */ |
| 528 | int bInvert, /* Invert the sense of the output (-v) */ |
| 529 | int bDeHttpize /* De-httpize the inputs. (-d) */ |
| 530 | ){ |
| 531 | FILE *in; |
| 532 | char zLine[10000]; |
| 533 | if( zInFile==0 || strcmp(zInFile,"-")==0 ){ |
| 534 | in = stdin; |
| 535 | }else{ |
| 536 | in = fopen(zInFile, "rb"); |
| 537 | if( in==0 ){ |
| 538 | fossil_fatal("cannot open \"%s\" for reading\n", zInFile); |
| 539 | } |
| 540 | } |
| 541 | while( fgets(zLine, sizeof(zLine), in) ){ |
| 542 | dehttpize(zLine); |
| 543 | if( (looks_like_sql_injection(zLine)!=0) ^ bInvert ){ |
| 544 | fossil_print("%s", zLine); |
| 545 | } |
| 546 | } |
| 547 | if( in!=stdin ) fclose(in); |
| 548 | } |
| 549 | |
| 550 | /* |
| 551 | ** COMMAND: test-looks-like-sql-injection |
| 552 | ** |
| 553 | ** Read lines of input from files named as arguments (or from standard |
| 554 | ** input if no arguments are provided) and print those that look like they |
| 555 | ** might be part of an SQL injection attack. |
| 556 | ** |
| 557 | ** Used to test the looks_lide_sql_injection() utility subroutine, possibly |
| 558 | ** by piping in actual server log data. |
| 559 | */ |
| 560 | void test_looks_like_sql_injection(void){ |
| 561 | int i; |
| 562 | int bInvert = find_option("invert","v",0)!=0; |
| 563 | int bDeHttpize = find_option("dehttpize","d",0)!=0; |
| 564 | verify_all_options(); |
| 565 | if( g.argc==2 ){ |
| 566 | show_sql_injection_lines(0, bInvert, bDeHttpize); |
| 567 | } |
| 568 | for(i=2; i<g.argc; i++){ |
| 569 | show_sql_injection_lines(g.argv[i], bInvert, bDeHttpize); |
| 570 | } |
| 571 | } |
| 572 |