Fossil SCM

Test command for the SQL-injection detection routine. Possible performance improvements as well.

drh 2023-02-08 16:32 trunk
Commit d3cb62f76759c6fa58399dc040944c8da13ad4cc6443bff099ba21a82da4ea0f
2 files changed +8 -7 +99 -10
+8 -7
--- src/cgi.c
+++ src/cgi.c
@@ -1515,29 +1515,30 @@
15151515
static void cgi_begone_spider(void){
15161516
Blob content = empty_blob;
15171517
15181518
cgi_set_content(&content);
15191519
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>
15221522
@ <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.
15251526
style_finish_page();
1526
- cgi_set_status(404,"Spider Detected");
1527
+ cgi_set_status(404,"Robot Attack Detected");
15271528
cgi_reply();
15281529
exit(0);
15291530
}
15301531
15311532
/*
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
15331534
** cgi_begin_spider() and does not return, else this function has no
15341535
** side effects. The range of checks performed by this function may
15351536
** be extended in the future.
15361537
*/
15371538
void cgi_value_spider_check(const char *zTxt){
1538
- if( might_be_sql(zTxt) ){
1539
+ if( looks_like_sql_injection(zTxt) ){
15391540
cgi_begone_spider();
15401541
}
15411542
}
15421543
15431544
/*
15441545
--- 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 @@
460460
(lookFlags&LOOK_INVALID)?"yes":"no");
461461
fossil_print("Has flag LOOK_ODD: %s\n",(lookFlags&LOOK_ODD)?"yes":"no");
462462
fossil_print("Has flag LOOK_SHORT: %s\n",(lookFlags&LOOK_SHORT)?"yes":"no");
463463
blob_reset(&blob);
464464
}
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
+}
465475
466476
/*
467477
** Returns true if the given text contains certain keywords or
468478
** punctuation which indicate that it might be SQL. This is only a
469479
** high-level check, not intended to be used for any application-level
470480
** logic other than in defense against spiders in limited contexts.
471481
*/
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
+ }
482571
}
483572
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button