Fossil SCM

If compiled with -DFOSSIL_PENTEST and if "<BUG>" appears anywhere in HTML output, or if "BUG" appears anywhere in SQL, then a panic is generated.

drh 2025-03-28 17:15 trunk
Commit 9ceb5ff8696589e2dd2747753ee4dc8e0a3a976c869196b46fdef3f48ed0fa69
--- src/cache.c
+++ src/cache.c
@@ -95,10 +95,11 @@
9595
sqlite3_stmt *pStmt = 0;
9696
int rc;
9797
9898
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
9999
if( rc ){
100
+ db_pentest(zSql);
100101
sqlite3_finalize(pStmt);
101102
pStmt = 0;
102103
}
103104
return pStmt;
104105
}
105106
--- src/cache.c
+++ src/cache.c
@@ -95,10 +95,11 @@
95 sqlite3_stmt *pStmt = 0;
96 int rc;
97
98 rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
99 if( rc ){
 
100 sqlite3_finalize(pStmt);
101 pStmt = 0;
102 }
103 return pStmt;
104 }
105
--- src/cache.c
+++ src/cache.c
@@ -95,10 +95,11 @@
95 sqlite3_stmt *pStmt = 0;
96 int rc;
97
98 rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
99 if( rc ){
100 db_pentest(zSql);
101 sqlite3_finalize(pStmt);
102 pStmt = 0;
103 }
104 return pStmt;
105 }
106
+15
--- src/cgi.c
+++ src/cgi.c
@@ -476,10 +476,13 @@
476476
** followed by the concatenation of the content header and content body.
477477
*/
478478
void cgi_reply(void){
479479
Blob hdr = BLOB_INITIALIZER;
480480
int total_size;
481
+#ifdef FOSSIL_PENTEST
482
+ int bHtml; /* True if the output is HTML */
483
+#endif
481484
if( iReplyStatus<=0 ){
482485
iReplyStatus = 200;
483486
zReplyStatus = "OK";
484487
}
485488
@@ -571,18 +574,30 @@
571574
blob_appendf(&hdr, "Content-Length: %d\r\n", total_size);
572575
}else{
573576
total_size = 0;
574577
}
575578
blob_appendf(&hdr, "\r\n");
579
+#ifdef FOSSIL_PENTEST
580
+ bHtml = strstr(blob_str(&hdr), "text/html")!=0;
581
+ if( strstr(blob_str(&hdr), "<BUG>")!=0 ){
582
+ fossil_panic("Cross-site scripting vulnerability!");
583
+ abort();
584
+ }
585
+#endif
576586
cgi_fwrite(blob_buffer(&hdr), blob_size(&hdr));
577587
blob_reset(&hdr);
578588
if( total_size>0
579589
&& iReplyStatus!=304
580590
&& fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
581591
){
582592
int i, size;
583593
for(i=0; i<2; i++){
594
+#ifdef FOSSIL_PENTEST
595
+ if( bHtml && strstr(blob_str(&cgiContent[i]), "<BUG>")!=0 ){
596
+ fossil_panic("Cross-site scripting vulnerability!\n");
597
+ }
598
+#endif
584599
size = blob_size(&cgiContent[i]);
585600
if( size<=rangeStart ){
586601
rangeStart -= size;
587602
}else{
588603
int n = size - rangeStart;
589604
--- src/cgi.c
+++ src/cgi.c
@@ -476,10 +476,13 @@
476 ** followed by the concatenation of the content header and content body.
477 */
478 void cgi_reply(void){
479 Blob hdr = BLOB_INITIALIZER;
480 int total_size;
 
 
 
481 if( iReplyStatus<=0 ){
482 iReplyStatus = 200;
483 zReplyStatus = "OK";
484 }
485
@@ -571,18 +574,30 @@
571 blob_appendf(&hdr, "Content-Length: %d\r\n", total_size);
572 }else{
573 total_size = 0;
574 }
575 blob_appendf(&hdr, "\r\n");
 
 
 
 
 
 
 
576 cgi_fwrite(blob_buffer(&hdr), blob_size(&hdr));
577 blob_reset(&hdr);
578 if( total_size>0
579 && iReplyStatus!=304
580 && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
581 ){
582 int i, size;
583 for(i=0; i<2; i++){
 
 
 
 
 
584 size = blob_size(&cgiContent[i]);
585 if( size<=rangeStart ){
586 rangeStart -= size;
587 }else{
588 int n = size - rangeStart;
589
--- src/cgi.c
+++ src/cgi.c
@@ -476,10 +476,13 @@
476 ** followed by the concatenation of the content header and content body.
477 */
478 void cgi_reply(void){
479 Blob hdr = BLOB_INITIALIZER;
480 int total_size;
481 #ifdef FOSSIL_PENTEST
482 int bHtml; /* True if the output is HTML */
483 #endif
484 if( iReplyStatus<=0 ){
485 iReplyStatus = 200;
486 zReplyStatus = "OK";
487 }
488
@@ -571,18 +574,30 @@
574 blob_appendf(&hdr, "Content-Length: %d\r\n", total_size);
575 }else{
576 total_size = 0;
577 }
578 blob_appendf(&hdr, "\r\n");
579 #ifdef FOSSIL_PENTEST
580 bHtml = strstr(blob_str(&hdr), "text/html")!=0;
581 if( strstr(blob_str(&hdr), "<BUG>")!=0 ){
582 fossil_panic("Cross-site scripting vulnerability!");
583 abort();
584 }
585 #endif
586 cgi_fwrite(blob_buffer(&hdr), blob_size(&hdr));
587 blob_reset(&hdr);
588 if( total_size>0
589 && iReplyStatus!=304
590 && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
591 ){
592 int i, size;
593 for(i=0; i<2; i++){
594 #ifdef FOSSIL_PENTEST
595 if( bHtml && strstr(blob_str(&cgiContent[i]), "<BUG>")!=0 ){
596 fossil_panic("Cross-site scripting vulnerability!\n");
597 }
598 #endif
599 size = blob_size(&cgiContent[i]);
600 if( size<=rangeStart ){
601 rangeStart -= size;
602 }else{
603 int n = size - rangeStart;
604
+24 -1
--- src/db.c
+++ src/db.c
@@ -671,10 +671,27 @@
671671
** to zero to stop appending DML statement text.
672672
*/
673673
void db_append_dml_to_blob(Blob *pBlob){
674674
db.pDmlLog = pBlob;
675675
}
676
+
677
+/*
678
+** This routine is a no-op on most builds. But if Fossil is built using
679
+** the FOSSIL_PENTEST compile-time option, and if the argument to this routine
680
+** contains the text "BUG", then that indicates a potential SQL injection
681
+** vulnerability. A panic is generated to bring this to the tester's
682
+** attention.
683
+*/
684
+void db_pentest(const char *zSql){
685
+#ifndef FOSSIL_PENTEST
686
+ (void)zSql;
687
+#else
688
+ if( strstr(zSql,"BUG")!=0 ){
689
+ fossil_panic("SQL Injection vulnerability!");
690
+ }
691
+#endif
692
+}
676693
677694
/*
678695
** Pause or unpause the DML log
679696
*/
680697
void db_pause_dml_log(void){ db.pauseDmlLog++; }
@@ -698,10 +715,11 @@
698715
db_append_dml(zSql);
699716
if( flags & DB_PREPARE_PERSISTENT ){
700717
prepFlags = SQLITE_PREPARE_PERSISTENT;
701718
}
702719
rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
720
+ if( rc!=0 ) db_pentest(zSql);
703721
if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
704722
db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
705723
}else if( zExtra && !fossil_all_whitespace(zExtra) ){
706724
db_err("surplus text follows SQL: \"%s\"", zExtra);
707725
}
@@ -760,10 +778,11 @@
760778
blob_init(pSql, 0, 0);
761779
zSql = blob_sql_text(&pStmt->sql);
762780
db.nPrepare++;
763781
rc = sqlite3_prepare_v3(g.db, zSql, -1, 0, &pStmt->pStmt, 0);
764782
if( rc!=0 ){
783
+ db_pentest(zSql);
765784
db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
766785
}
767786
pStmt->pNext = pStmt->pPrev = 0;
768787
pStmt->nStep = 0;
769788
pStmt->rc = rc;
@@ -1046,11 +1065,14 @@
10461065
va_end(ap);
10471066
z = blob_str(&sql);
10481067
while( rc==SQLITE_OK && z[0] ){
10491068
pStmt = 0;
10501069
rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
1051
- if( rc!=SQLITE_OK ) break;
1070
+ if( rc!=SQLITE_OK ){
1071
+ db_pentest(z);
1072
+ break;
1073
+ }
10521074
if( pStmt ){
10531075
int nRow = 0;
10541076
db.nPrepare++;
10551077
while( sqlite3_step(pStmt)==SQLITE_ROW ){
10561078
int i, n;
@@ -1080,10 +1102,11 @@
10801102
const char *zEnd;
10811103
while( rc==SQLITE_OK && z[0] ){
10821104
pStmt = 0;
10831105
rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
10841106
if( rc ){
1107
+ db_pentest(z);
10851108
db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
10861109
}else if( pStmt ){
10871110
db.nPrepare++;
10881111
db_append_dml(sqlite3_sql(pStmt));
10891112
while( sqlite3_step(pStmt)==SQLITE_ROW ){}
10901113
--- src/db.c
+++ src/db.c
@@ -671,10 +671,27 @@
671 ** to zero to stop appending DML statement text.
672 */
673 void db_append_dml_to_blob(Blob *pBlob){
674 db.pDmlLog = pBlob;
675 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
676
677 /*
678 ** Pause or unpause the DML log
679 */
680 void db_pause_dml_log(void){ db.pauseDmlLog++; }
@@ -698,10 +715,11 @@
698 db_append_dml(zSql);
699 if( flags & DB_PREPARE_PERSISTENT ){
700 prepFlags = SQLITE_PREPARE_PERSISTENT;
701 }
702 rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
 
703 if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
704 db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
705 }else if( zExtra && !fossil_all_whitespace(zExtra) ){
706 db_err("surplus text follows SQL: \"%s\"", zExtra);
707 }
@@ -760,10 +778,11 @@
760 blob_init(pSql, 0, 0);
761 zSql = blob_sql_text(&pStmt->sql);
762 db.nPrepare++;
763 rc = sqlite3_prepare_v3(g.db, zSql, -1, 0, &pStmt->pStmt, 0);
764 if( rc!=0 ){
 
765 db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
766 }
767 pStmt->pNext = pStmt->pPrev = 0;
768 pStmt->nStep = 0;
769 pStmt->rc = rc;
@@ -1046,11 +1065,14 @@
1046 va_end(ap);
1047 z = blob_str(&sql);
1048 while( rc==SQLITE_OK && z[0] ){
1049 pStmt = 0;
1050 rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
1051 if( rc!=SQLITE_OK ) break;
 
 
 
1052 if( pStmt ){
1053 int nRow = 0;
1054 db.nPrepare++;
1055 while( sqlite3_step(pStmt)==SQLITE_ROW ){
1056 int i, n;
@@ -1080,10 +1102,11 @@
1080 const char *zEnd;
1081 while( rc==SQLITE_OK && z[0] ){
1082 pStmt = 0;
1083 rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
1084 if( rc ){
 
1085 db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
1086 }else if( pStmt ){
1087 db.nPrepare++;
1088 db_append_dml(sqlite3_sql(pStmt));
1089 while( sqlite3_step(pStmt)==SQLITE_ROW ){}
1090
--- src/db.c
+++ src/db.c
@@ -671,10 +671,27 @@
671 ** to zero to stop appending DML statement text.
672 */
673 void db_append_dml_to_blob(Blob *pBlob){
674 db.pDmlLog = pBlob;
675 }
676
677 /*
678 ** This routine is a no-op on most builds. But if Fossil is built using
679 ** the FOSSIL_PENTEST compile-time option, and if the argument to this routine
680 ** contains the text "BUG", then that indicates a potential SQL injection
681 ** vulnerability. A panic is generated to bring this to the tester's
682 ** attention.
683 */
684 void db_pentest(const char *zSql){
685 #ifndef FOSSIL_PENTEST
686 (void)zSql;
687 #else
688 if( strstr(zSql,"BUG")!=0 ){
689 fossil_panic("SQL Injection vulnerability!");
690 }
691 #endif
692 }
693
694 /*
695 ** Pause or unpause the DML log
696 */
697 void db_pause_dml_log(void){ db.pauseDmlLog++; }
@@ -698,10 +715,11 @@
715 db_append_dml(zSql);
716 if( flags & DB_PREPARE_PERSISTENT ){
717 prepFlags = SQLITE_PREPARE_PERSISTENT;
718 }
719 rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
720 if( rc!=0 ) db_pentest(zSql);
721 if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
722 db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
723 }else if( zExtra && !fossil_all_whitespace(zExtra) ){
724 db_err("surplus text follows SQL: \"%s\"", zExtra);
725 }
@@ -760,10 +778,11 @@
778 blob_init(pSql, 0, 0);
779 zSql = blob_sql_text(&pStmt->sql);
780 db.nPrepare++;
781 rc = sqlite3_prepare_v3(g.db, zSql, -1, 0, &pStmt->pStmt, 0);
782 if( rc!=0 ){
783 db_pentest(zSql);
784 db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
785 }
786 pStmt->pNext = pStmt->pPrev = 0;
787 pStmt->nStep = 0;
788 pStmt->rc = rc;
@@ -1046,11 +1065,14 @@
1065 va_end(ap);
1066 z = blob_str(&sql);
1067 while( rc==SQLITE_OK && z[0] ){
1068 pStmt = 0;
1069 rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
1070 if( rc!=SQLITE_OK ){
1071 db_pentest(z);
1072 break;
1073 }
1074 if( pStmt ){
1075 int nRow = 0;
1076 db.nPrepare++;
1077 while( sqlite3_step(pStmt)==SQLITE_ROW ){
1078 int i, n;
@@ -1080,10 +1102,11 @@
1102 const char *zEnd;
1103 while( rc==SQLITE_OK && z[0] ){
1104 pStmt = 0;
1105 rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
1106 if( rc ){
1107 db_pentest(z);
1108 db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
1109 }else if( pStmt ){
1110 db.nPrepare++;
1111 db_append_dml(sqlite3_sql(pStmt));
1112 while( sqlite3_step(pStmt)==SQLITE_ROW ){}
1113
--- src/login.c
+++ src/login.c
@@ -1156,10 +1156,11 @@
11561156
" AND constant_time_cmp(cookie,%Q)=0",
11571157
zLogin, zHash
11581158
);
11591159
pStmt = 0;
11601160
rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
1161
+ if( rc!=SQLITE_OK ) db_pentest(zSQL);
11611162
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
11621163
db_unprotect(PROTECT_USER);
11631164
db_multi_exec(
11641165
"UPDATE user SET cookie=%Q, cexpire=%.17g"
11651166
" WHERE login=%Q",
11661167
--- src/login.c
+++ src/login.c
@@ -1156,10 +1156,11 @@
1156 " AND constant_time_cmp(cookie,%Q)=0",
1157 zLogin, zHash
1158 );
1159 pStmt = 0;
1160 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
 
1161 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
1162 db_unprotect(PROTECT_USER);
1163 db_multi_exec(
1164 "UPDATE user SET cookie=%Q, cexpire=%.17g"
1165 " WHERE login=%Q",
1166
--- src/login.c
+++ src/login.c
@@ -1156,10 +1156,11 @@
1156 " AND constant_time_cmp(cookie,%Q)=0",
1157 zLogin, zHash
1158 );
1159 pStmt = 0;
1160 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
1161 if( rc!=SQLITE_OK ) db_pentest(zSQL);
1162 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
1163 db_unprotect(PROTECT_USER);
1164 db_multi_exec(
1165 "UPDATE user SET cookie=%Q, cexpire=%.17g"
1166 " WHERE login=%Q",
1167
--- src/report.c
+++ src/report.c
@@ -322,10 +322,11 @@
322322
323323
/* Compile the statement and check for illegal accesses or syntax errors. */
324324
report_restrict_sql(&zErr);
325325
rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, &zTail);
326326
if( rc!=SQLITE_OK ){
327
+ db_pentest(zSql);
327328
zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db));
328329
}
329330
if( !sqlite3_stmt_readonly(pStmt) ){
330331
zErr = mprintf("SQL must not modify the database");
331332
}
@@ -1049,10 +1050,11 @@
10491050
10501051
pStmt = 0;
10511052
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
10521053
assert( rc==SQLITE_OK || pStmt==0 );
10531054
if( rc!=SQLITE_OK ){
1055
+ db_pentest(zSql);
10541056
return rc;
10551057
}
10561058
if( !pStmt ){
10571059
/* this happens for a comment or white-space */
10581060
return SQLITE_OK;
10591061
--- src/report.c
+++ src/report.c
@@ -322,10 +322,11 @@
322
323 /* Compile the statement and check for illegal accesses or syntax errors. */
324 report_restrict_sql(&zErr);
325 rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, &zTail);
326 if( rc!=SQLITE_OK ){
 
327 zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db));
328 }
329 if( !sqlite3_stmt_readonly(pStmt) ){
330 zErr = mprintf("SQL must not modify the database");
331 }
@@ -1049,10 +1050,11 @@
1049
1050 pStmt = 0;
1051 rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
1052 assert( rc==SQLITE_OK || pStmt==0 );
1053 if( rc!=SQLITE_OK ){
 
1054 return rc;
1055 }
1056 if( !pStmt ){
1057 /* this happens for a comment or white-space */
1058 return SQLITE_OK;
1059
--- src/report.c
+++ src/report.c
@@ -322,10 +322,11 @@
322
323 /* Compile the statement and check for illegal accesses or syntax errors. */
324 report_restrict_sql(&zErr);
325 rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, &zTail);
326 if( rc!=SQLITE_OK ){
327 db_pentest(zSql);
328 zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db));
329 }
330 if( !sqlite3_stmt_readonly(pStmt) ){
331 zErr = mprintf("SQL must not modify the database");
332 }
@@ -1049,10 +1050,11 @@
1050
1051 pStmt = 0;
1052 rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
1053 assert( rc==SQLITE_OK || pStmt==0 );
1054 if( rc!=SQLITE_OK ){
1055 db_pentest(zSql);
1056 return rc;
1057 }
1058 if( !pStmt ){
1059 /* this happens for a comment or white-space */
1060 return SQLITE_OK;
1061
--- src/th_main.c
+++ src/th_main.c
@@ -1947,10 +1947,11 @@
19471947
g.dbIgnoreErrors++;
19481948
rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
19491949
g.dbIgnoreErrors--;
19501950
report_unrestrict_sql();
19511951
if( rc!=0 || zErr!=0 ){
1952
+ db_pentest(argv[1]);
19521953
if( noComplain ) return TH_OK;
19531954
Th_ErrorMessage(interp, "SQL error: ",
19541955
zErr ? zErr : sqlite3_errmsg(g.db), -1);
19551956
return TH_ERROR;
19561957
}
19571958
--- src/th_main.c
+++ src/th_main.c
@@ -1947,10 +1947,11 @@
1947 g.dbIgnoreErrors++;
1948 rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
1949 g.dbIgnoreErrors--;
1950 report_unrestrict_sql();
1951 if( rc!=0 || zErr!=0 ){
 
1952 if( noComplain ) return TH_OK;
1953 Th_ErrorMessage(interp, "SQL error: ",
1954 zErr ? zErr : sqlite3_errmsg(g.db), -1);
1955 return TH_ERROR;
1956 }
1957
--- src/th_main.c
+++ src/th_main.c
@@ -1947,10 +1947,11 @@
1947 g.dbIgnoreErrors++;
1948 rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
1949 g.dbIgnoreErrors--;
1950 report_unrestrict_sql();
1951 if( rc!=0 || zErr!=0 ){
1952 db_pentest(argv[1]);
1953 if( noComplain ) return TH_OK;
1954 Th_ErrorMessage(interp, "SQL error: ",
1955 zErr ? zErr : sqlite3_errmsg(g.db), -1);
1956 return TH_ERROR;
1957 }
1958

Keyboard Shortcuts

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