Fossil

Check-in [9ceb5ff869]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment: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.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | pentest
Files: files | file ages | folders
SHA3-256: 9ceb5ff8696589e2dd2747753ee4dc8e0a3a976c869196b46fdef3f48ed0fa69
User & Date: drh 2025-03-28 17:15:00.645
Context
2025-03-28
17:15
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. Leaf check-in: 9ceb5ff869 user: drh tags: pentest
16:47
Document parameters 'from' and 'to' for /reports. check-in: a584f75ff8 user: danield tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/cache.c.
93
94
95
96
97
98
99

100
101
102
103
104
105
106
*/
static sqlite3_stmt *cacheStmt(sqlite3 *db, const char *zSql){
  sqlite3_stmt *pStmt = 0;
  int rc;

  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  if( rc ){

    sqlite3_finalize(pStmt);
    pStmt = 0;
  }
  return pStmt;
}

/*







>







93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
*/
static sqlite3_stmt *cacheStmt(sqlite3 *db, const char *zSql){
  sqlite3_stmt *pStmt = 0;
  int rc;

  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  if( rc ){
    db_pentest(zSql);
    sqlite3_finalize(pStmt);
    pStmt = 0;
  }
  return pStmt;
}

/*
Changes to src/cgi.c.
474
475
476
477
478
479
480



481
482
483
484
485
486
487
**
** The reply consists of a response header (an HTTP or CGI response header)
** followed by the concatenation of the content header and content body.
*/
void cgi_reply(void){
  Blob hdr = BLOB_INITIALIZER;
  int total_size;



  if( iReplyStatus<=0 ){
    iReplyStatus = 200;
    zReplyStatus = "OK";
  }

  if( g.fullHttpReply ){
    if( rangeEnd>0







>
>
>







474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
**
** The reply consists of a response header (an HTTP or CGI response header)
** followed by the concatenation of the content header and content body.
*/
void cgi_reply(void){
  Blob hdr = BLOB_INITIALIZER;
  int total_size;
#ifdef FOSSIL_PENTEST
  int bHtml;     /* True if the output is HTML */
#endif
  if( iReplyStatus<=0 ){
    iReplyStatus = 200;
    zReplyStatus = "OK";
  }

  if( g.fullHttpReply ){
    if( rangeEnd>0
569
570
571
572
573
574
575







576
577
578
579
580
581
582
583





584
585
586
587
588
589
590
      total_size = rangeEnd - rangeStart;
    }
    blob_appendf(&hdr, "Content-Length: %d\r\n", total_size);
  }else{
    total_size = 0;
  }
  blob_appendf(&hdr, "\r\n");







  cgi_fwrite(blob_buffer(&hdr), blob_size(&hdr));
  blob_reset(&hdr);
  if( total_size>0
   && iReplyStatus!=304
   && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
  ){
    int i, size;
    for(i=0; i<2; i++){





      size = blob_size(&cgiContent[i]);
      if( size<=rangeStart ){
        rangeStart -= size;
      }else{
        int n = size - rangeStart;
        if( n>total_size ){
          n = total_size;







>
>
>
>
>
>
>








>
>
>
>
>







572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
      total_size = rangeEnd - rangeStart;
    }
    blob_appendf(&hdr, "Content-Length: %d\r\n", total_size);
  }else{
    total_size = 0;
  }
  blob_appendf(&hdr, "\r\n");
#ifdef FOSSIL_PENTEST
  bHtml = strstr(blob_str(&hdr), "text/html")!=0;
  if( strstr(blob_str(&hdr), "<BUG>")!=0 ){
    fossil_panic("Cross-site scripting vulnerability!");
    abort();
  }
#endif
  cgi_fwrite(blob_buffer(&hdr), blob_size(&hdr));
  blob_reset(&hdr);
  if( total_size>0
   && iReplyStatus!=304
   && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
  ){
    int i, size;
    for(i=0; i<2; i++){
#ifdef FOSSIL_PENTEST
      if( bHtml && strstr(blob_str(&cgiContent[i]), "<BUG>")!=0 ){
        fossil_panic("Cross-site scripting vulnerability!\n");
      }
#endif
      size = blob_size(&cgiContent[i]);
      if( size<=rangeStart ){
        rangeStart -= size;
      }else{
        int n = size - rangeStart;
        if( n>total_size ){
          n = total_size;
Changes to src/db.c.
669
670
671
672
673
674
675

















676
677
678
679
680
681
682
/*
** Set the Blob to which DML statement text should be appended.  Set it
** to zero to stop appending DML statement text.
*/
void db_append_dml_to_blob(Blob *pBlob){
  db.pDmlLog = pBlob;
}


















/*
** Pause or unpause the DML log
*/
void db_pause_dml_log(void){    db.pauseDmlLog++; }
void db_unpause_dml_log(void){  db.pauseDmlLog--; }








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
/*
** Set the Blob to which DML statement text should be appended.  Set it
** to zero to stop appending DML statement text.
*/
void db_append_dml_to_blob(Blob *pBlob){
  db.pDmlLog = pBlob;
}

/*
** This routine is a no-op on most builds.  But if Fossil is built using
** the FOSSIL_PENTEST compile-time option, and if the argument to this routine
** contains the text "BUG", then that indicates a potential SQL injection
** vulnerability.  A panic is generated to bring this to the tester's
** attention.
*/
void db_pentest(const char *zSql){
#ifndef FOSSIL_PENTEST
  (void)zSql;
#else
  if( strstr(zSql,"BUG")!=0 ){
    fossil_panic("SQL Injection vulnerability!");
  }
#endif
}

/*
** Pause or unpause the DML log
*/
void db_pause_dml_log(void){    db.pauseDmlLog++; }
void db_unpause_dml_log(void){  db.pauseDmlLog--; }

696
697
698
699
700
701
702

703
704
705
706
707
708
709
  zSql = blob_str(&pStmt->sql);
  db.nPrepare++;
  db_append_dml(zSql);
  if( flags & DB_PREPARE_PERSISTENT ){
    prepFlags = SQLITE_PREPARE_PERSISTENT;
  }
  rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);

  if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
    db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
  }else if( zExtra && !fossil_all_whitespace(zExtra) ){
    db_err("surplus text follows SQL: \"%s\"", zExtra);
  }
  pStmt->pNext = db.pAllStmt;
  pStmt->pPrev = 0;







>







713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
  zSql = blob_str(&pStmt->sql);
  db.nPrepare++;
  db_append_dml(zSql);
  if( flags & DB_PREPARE_PERSISTENT ){
    prepFlags = SQLITE_PREPARE_PERSISTENT;
  }
  rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
  if( rc!=0 ) db_pentest(zSql);
  if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
    db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
  }else if( zExtra && !fossil_all_whitespace(zExtra) ){
    db_err("surplus text follows SQL: \"%s\"", zExtra);
  }
  pStmt->pNext = db.pAllStmt;
  pStmt->pPrev = 0;
758
759
760
761
762
763
764

765
766
767
768
769
770
771
  char *zSql;
  pStmt->sql = *pSql;
  blob_init(pSql, 0, 0);
  zSql = blob_sql_text(&pStmt->sql);
  db.nPrepare++;
  rc = sqlite3_prepare_v3(g.db, zSql, -1, 0, &pStmt->pStmt, 0);
  if( rc!=0 ){

    db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
  }
  pStmt->pNext = pStmt->pPrev = 0;
  pStmt->nStep = 0;
  pStmt->rc = rc;
  return rc;
}







>







776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
  char *zSql;
  pStmt->sql = *pSql;
  blob_init(pSql, 0, 0);
  zSql = blob_sql_text(&pStmt->sql);
  db.nPrepare++;
  rc = sqlite3_prepare_v3(g.db, zSql, -1, 0, &pStmt->pStmt, 0);
  if( rc!=0 ){
    db_pentest(zSql);
    db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
  }
  pStmt->pNext = pStmt->pPrev = 0;
  pStmt->nStep = 0;
  pStmt->rc = rc;
  return rc;
}
1044
1045
1046
1047
1048
1049
1050
1051



1052
1053
1054
1055
1056
1057
1058
  va_start(ap, zSql);
  blob_vappendf(&sql, zSql, ap);
  va_end(ap);
  z = blob_str(&sql);
  while( rc==SQLITE_OK && z[0] ){
    pStmt = 0;
    rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
    if( rc!=SQLITE_OK ) break;



    if( pStmt ){
      int nRow = 0;
      db.nPrepare++;
      while( sqlite3_step(pStmt)==SQLITE_ROW ){
        int i, n;
        if( nRow++ > 0 ) fossil_print("\n");
        n = sqlite3_column_count(pStmt);







|
>
>
>







1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
  va_start(ap, zSql);
  blob_vappendf(&sql, zSql, ap);
  va_end(ap);
  z = blob_str(&sql);
  while( rc==SQLITE_OK && z[0] ){
    pStmt = 0;
    rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
    if( rc!=SQLITE_OK ){
      db_pentest(z);
      break;
    }
    if( pStmt ){
      int nRow = 0;
      db.nPrepare++;
      while( sqlite3_step(pStmt)==SQLITE_ROW ){
        int i, n;
        if( nRow++ > 0 ) fossil_print("\n");
        n = sqlite3_column_count(pStmt);
1078
1079
1080
1081
1082
1083
1084

1085
1086
1087
1088
1089
1090
1091
  int rc = SQLITE_OK;
  sqlite3_stmt *pStmt;
  const char *zEnd;
  while( rc==SQLITE_OK && z[0] ){
    pStmt = 0;
    rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
    if( rc ){

      db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
    }else if( pStmt ){
      db.nPrepare++;
      db_append_dml(sqlite3_sql(pStmt));
      while( sqlite3_step(pStmt)==SQLITE_ROW ){}
      rc = sqlite3_finalize(pStmt);
      if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);







>







1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
  int rc = SQLITE_OK;
  sqlite3_stmt *pStmt;
  const char *zEnd;
  while( rc==SQLITE_OK && z[0] ){
    pStmt = 0;
    rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
    if( rc ){
      db_pentest(z);
      db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
    }else if( pStmt ){
      db.nPrepare++;
      db_append_dml(sqlite3_sql(pStmt));
      while( sqlite3_step(pStmt)==SQLITE_ROW ){}
      rc = sqlite3_finalize(pStmt);
      if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
Changes to src/login.c.
1154
1155
1156
1157
1158
1159
1160

1161
1162
1163
1164
1165
1166
1167
      "   AND octet_length(pw)>0"
      "   AND cexpire>julianday('now')"
      "   AND constant_time_cmp(cookie,%Q)=0",
      zLogin, zHash
    );
    pStmt = 0;
    rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);

    if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
      db_unprotect(PROTECT_USER);
      db_multi_exec(
        "UPDATE user SET cookie=%Q, cexpire=%.17g"
        " WHERE login=%Q",
        zHash,
        sqlite3_column_double(pStmt, 0), zLogin







>







1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
      "   AND octet_length(pw)>0"
      "   AND cexpire>julianday('now')"
      "   AND constant_time_cmp(cookie,%Q)=0",
      zLogin, zHash
    );
    pStmt = 0;
    rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
    if( rc!=SQLITE_OK ) db_pentest(zSQL);
    if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
      db_unprotect(PROTECT_USER);
      db_multi_exec(
        "UPDATE user SET cookie=%Q, cexpire=%.17g"
        " WHERE login=%Q",
        zHash,
        sqlite3_column_double(pStmt, 0), zLogin
Changes to src/report.c.
320
321
322
323
324
325
326

327
328
329
330
331
332
333
    }
  }

  /* Compile the statement and check for illegal accesses or syntax errors. */
  report_restrict_sql(&zErr);
  rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, &zTail);
  if( rc!=SQLITE_OK ){

    zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db));
  }
  if( !sqlite3_stmt_readonly(pStmt) ){
    zErr = mprintf("SQL must not modify the database");
  }
  if( pStmt ){
    sqlite3_finalize(pStmt);







>







320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
    }
  }

  /* Compile the statement and check for illegal accesses or syntax errors. */
  report_restrict_sql(&zErr);
  rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, &zTail);
  if( rc!=SQLITE_OK ){
    db_pentest(zSql);
    zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db));
  }
  if( !sqlite3_stmt_readonly(pStmt) ){
    zErr = mprintf("SQL must not modify the database");
  }
  if( pStmt ){
    sqlite3_finalize(pStmt);
1047
1048
1049
1050
1051
1052
1053

1054
1055
1056
1057
1058
1059
1060
  int i;                      /* Loop counter */
  int nVar;                   /* Number of parameters */

  pStmt = 0;
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
  assert( rc==SQLITE_OK || pStmt==0 );
  if( rc!=SQLITE_OK ){

    return rc;
  }
  if( !pStmt ){
    /* this happens for a comment or white-space */
    return SQLITE_OK;
  }
  if( !sqlite3_stmt_readonly(pStmt) ){







>







1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
  int i;                      /* Loop counter */
  int nVar;                   /* Number of parameters */

  pStmt = 0;
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
  assert( rc==SQLITE_OK || pStmt==0 );
  if( rc!=SQLITE_OK ){
    db_pentest(zSql);
    return rc;
  }
  if( !pStmt ){
    /* this happens for a comment or white-space */
    return SQLITE_OK;
  }
  if( !sqlite3_stmt_readonly(pStmt) ){
Changes to src/th_main.c.
1945
1946
1947
1948
1949
1950
1951

1952
1953
1954
1955
1956
1957
1958
    zErr = 0;
    report_restrict_sql(&zErr);
    g.dbIgnoreErrors++;
    rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
    g.dbIgnoreErrors--;
    report_unrestrict_sql();
    if( rc!=0 || zErr!=0 ){

      if( noComplain ) return TH_OK;
      Th_ErrorMessage(interp, "SQL error: ",
                      zErr ? zErr : sqlite3_errmsg(g.db), -1);
      return TH_ERROR;
    }
    n = (int)(zTail - zSql);
    zSql += n;







>







1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
    zErr = 0;
    report_restrict_sql(&zErr);
    g.dbIgnoreErrors++;
    rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
    g.dbIgnoreErrors--;
    report_unrestrict_sql();
    if( rc!=0 || zErr!=0 ){
      db_pentest(argv[1]);
      if( noComplain ) return TH_OK;
      Th_ErrorMessage(interp, "SQL error: ",
                      zErr ? zErr : sqlite3_errmsg(g.db), -1);
      return TH_ERROR;
    }
    n = (int)(zTail - zSql);
    zSql += n;