Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | New setting "vuln-report" determines what to do when tainted text is misused in a TH1 script. Enhance the /test-warning page to deliberately misuse tainted text in TH1 to verify error handling. Enhance /errorlog to separate out TH1 vulnerability reports as a new category the the error log. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | th1-taint |
| Files: | files | file ages | folders |
| SHA3-256: |
295b814a2722fff73e65c10b5247fbec |
| User & Date: | drh 2025-04-20 16:13:27.386 |
Context
|
2025-04-20
| ||
| 16:54 | Add "taint mode" to TH1. Attempts to output values that are derived from user input as unescaped HTML, or to use such values unescaped in SQL, raises errors. The resolution of these errors depends on the value of the new "vuln-report" setting. ... (check-in: 2116238e80 user: drh tags: trunk) | |
| 16:13 | New setting "vuln-report" determines what to do when tainted text is misused in a TH1 script. Enhance the /test-warning page to deliberately misuse tainted text in TH1 to verify error handling. Enhance /errorlog to separate out TH1 vulnerability reports as a new category the the error log. ... (Closed-Leaf check-in: 295b814a27 user: drh tags: th1-taint) | |
|
2025-04-19
| ||
| 23:32 | Fix more issues that were already fixed but overwritten by text editor errors and didn't get committed last time. ... (check-in: bd45dc72dd user: drh tags: th1-taint) | |
Changes
Changes to src/main.c.
| ︙ | ︙ | |||
3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 |
** case=1 Issue a fossil_warning() while generating the page.
** case=2 Extra db_begin_transaction()
** case=3 Extra db_end_transaction()
** case=4 Error during SQL processing
** case=5 Call the segfault handler
** case=6 Call webpage_assert()
** case=7 Call webpage_error()
*/
void test_warning_page(void){
int iCase = atoi(PD("case","0"));
int i;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_set_current_feature("test");
style_header("Warning Test Page");
style_submenu_element("Error Log","%R/errorlog");
| > > > | > | | < < < | | 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 |
** case=1 Issue a fossil_warning() while generating the page.
** case=2 Extra db_begin_transaction()
** case=3 Extra db_end_transaction()
** case=4 Error during SQL processing
** case=5 Call the segfault handler
** case=6 Call webpage_assert()
** case=7 Call webpage_error()
** case=8 Simulate a timeout
** case=9 Simulate a TH1 XSS vulnerability
** case=10 Simulate a TH1 SQL-injection vulnerability
*/
void test_warning_page(void){
int iCase = atoi(PD("case","0"));
int i;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_set_current_feature("test");
style_header("Warning Test Page");
style_submenu_element("Error Log","%R/errorlog");
@ <p>This page will generate various kinds of errors to test Fossil's
@ reaction. Depending on settings, a message might be written
@ into the <a href="%R/errorlog">error log</a>. Click on
@ one of the following hyperlinks to generate a simulated error:
for(i=1; i<=10; i++){
@ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
}
@ </p>
@ <p><ol>
@ <li value='1'> Call fossil_warning()
if( iCase==1 ){
fossil_warning("Test warning message from /test-warning");
|
| ︙ | ︙ | |||
3767 3768 3769 3770 3771 3772 3773 |
if( iCase==5 ){
sigsegv_handler(0);
}
@ <li value='6'> call webpage_assert(0)
if( iCase==6 ){
webpage_assert( 5==7 );
}
| | | > > > > > > > > > > > > > > > > > > > | 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 |
if( iCase==5 ){
sigsegv_handler(0);
}
@ <li value='6'> call webpage_assert(0)
if( iCase==6 ){
webpage_assert( 5==7 );
}
@ <li value='7'> call webpage_error()
if( iCase==7 ){
cgi_reset_content();
webpage_error("Case 7 from /test-warning");
}
@ <li value='8'> simulated timeout
if( iCase==8 ){
fossil_set_timeout(1);
cgi_reset_content();
sqlite3_sleep(1100);
}
@ <li value='9'> simulated TH1 XSS vulnerability
@ <li value='10'> simulated TH1 SQL-injection vulnerability
if( iCase==9 || iCase==10 ){
const char *zR;
int n, rc;
static const char *zTH1[] = {
/* case 9 */ "html [taint {<b>XSS</b>}]",
/* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
" html \"<b>[htmlize $msg]</b>\"\n"
"}"
};
rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
zR = Th_GetResult(g.interp, &n);
if( rc==TH_OK ){
@ <pre class="th1result">%h(zR)</pre>
}else{
@ <pre class="th1error">%h(zR)</pre>
}
}
@ </ol>
@ <p>End of test</p>
style_finish_page();
}
|
Changes to src/security_audit.c.
| ︙ | ︙ | |||
808 809 810 811 812 813 814 | /* ** WEBPAGE: errorlog ** ** Show the content of the error log. Only the administrator can view ** this page. ** | | | | | | | > | | > | 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 |
/*
** WEBPAGE: errorlog
**
** Show the content of the error log. Only the administrator can view
** this page.
**
** y=0x001 Show only hack attempts
** y=0x002 Show only panics and assertion faults
** y=0x004 Show hung backoffice processes
** y=0x008 Show POST requests from a different origin
** y=0x010 Show SQLITE_AUTH and similar
** y=0x020 Show SMTP error reports
** y=0x040 Show TH1 vulnerability reports
** y=0x800 Show other uncategorized messages
**
** If y is omitted or is zero, a count of the various message types is
** shown.
*/
void errorlog_page(void){
i64 szFile;
FILE *in;
char *zLog;
const char *zType = P("y");
static const int eAllTypes = 0x87f;
long eType = 0;
int bOutput = 0;
int prevWasTime = 0;
int nHack = 0;
int nPanic = 0;
int nOther = 0;
int nHang = 0;
int nXPost = 0;
int nAuth = 0;
int nSmtp = 0;
int nVuln = 0;
char z[10000];
char zTime[10000];
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
|
| ︙ | ︙ | |||
915 916 917 918 919 920 921 922 923 924 925 926 927 928 |
if( eType & 0x10 ){
@ <li>SQLITE_AUTH and similar errors
}
if( eType & 0x20 ){
@ <li>SMTP malfunctions
}
if( eType & 0x40 ){
@ <li>Other uncategorized messages
}
@ </ul>
}
@ <hr>
if( eType ){
@ <pre>
| > > > | 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 |
if( eType & 0x10 ){
@ <li>SQLITE_AUTH and similar errors
}
if( eType & 0x20 ){
@ <li>SMTP malfunctions
}
if( eType & 0x40 ){
@ <li>TH1 vulnerabilities
}
if( eType & 0x800 ){
@ <li>Other uncategorized messages
}
@ </ul>
}
@ <hr>
if( eType ){
@ <pre>
|
| ︙ | ︙ | |||
951 952 953 954 955 956 957 958 |
}else
if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0
|| sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
){
bOutput = (eType & 0x10)!=0;
nAuth++;
}else
{
| > > > > | | 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 |
}else
if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0
|| sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
){
bOutput = (eType & 0x10)!=0;
nAuth++;
}else
if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
bOutput = (eType & 0x40)!=0;
nVuln++;
}else
{
bOutput = (eType & 0x800)!=0;
nOther++;
}
if( bOutput ){
@ %h(zTime)\
}
}
if( strncmp(z, "--------", 8)==0 ){
|
| ︙ | ︙ | |||
976 977 978 979 980 981 982 |
}
}
fclose(in);
if( eType ){
@ </pre>
}
if( eType==0 ){
| | > > > > | 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 |
}
}
fclose(in);
if( eType ){
@ </pre>
}
if( eType==0 ){
int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther;
int nTotal = nNonHack + nHack + nXPost;
@ <p><table border="a" cellspacing="0" cellpadding="5">
if( nPanic>0 ){
@ <tr><td align="right">%d(nPanic)</td>
@ <td><a href="./errorlog?y=2">Panics</a></td>
}
if( nVuln>0 ){
@ <tr><td align="right">%d(nVuln)</td>
@ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
}
if( nHack>0 ){
@ <tr><td align="right">%d(nHack)</td>
@ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
}
if( nHang>0 ){
@ <tr><td align="right">%d(nHang)</td>
@ <td><a href="./errorlog?y=4">Hung Backoffice</a></td>
|
| ︙ | ︙ | |||
1005 1006 1007 1008 1009 1010 1011 |
}
if( nSmtp>0 ){
@ <tr><td align="right">%d(nSmtp)</td>
@ <td><a href="./errorlog?y=32">SMTP faults</a></td>
}
if( nOther>0 ){
@ <tr><td align="right">%d(nOther)</td>
| | | | 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 |
}
if( nSmtp>0 ){
@ <tr><td align="right">%d(nSmtp)</td>
@ <td><a href="./errorlog?y=32">SMTP faults</a></td>
}
if( nOther>0 ){
@ <tr><td align="right">%d(nOther)</td>
@ <td><a href="./errorlog?y=2048">Other</a></td>
}
@ <tr><td align="right">%d(nTotal)</td>
if( nTotal>0 ){
@ <td><a href="./errorlog?y=4095">All Messages</a></td>
}else{
@ <td>All Messages</td>
}
@ </table>
}
style_finish_page();
}
|
Changes to src/th.c.
| ︙ | ︙ | |||
907 908 909 910 911 912 913 | finish: thBufferFree(interp, &strbuf); thBufferFree(interp, &lenbuf); return rc; } | < < < < < < < < < < < < < < < < < < < < < < < < | 907 908 909 910 911 912 913 914 915 916 917 918 919 920 |
finish:
thBufferFree(interp, &strbuf);
thBufferFree(interp, &lenbuf);
return rc;
}
/*
** Evaluate the th1 script contained in the string (zProgram, nProgram)
** in the current stack frame.
*/
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
int rc = TH_OK;
const char *zInput = zProgram;
|
| ︙ | ︙ |
Changes to src/th_main.c.
| ︙ | ︙ | |||
387 388 389 390 391 392 393 |
static void sendText(Blob *pOut, const char *z, int n, int encode){
if(0==pOut && pThOut!=0){
pOut = pThOut;
}
if(TH_INIT_NO_ENCODE & g.th1Flags){
encode = 0;
}
| < < < < < < > > | 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
static void sendText(Blob *pOut, const char *z, int n, int encode){
if(0==pOut && pThOut!=0){
pOut = pThOut;
}
if(TH_INIT_NO_ENCODE & g.th1Flags){
encode = 0;
}
if( enableOutput && n ){
if( n<0 ){
n = strlen(z);
}else{
n = TH1_LEN(n);
}
if( encode ){
z = htmlize(z, n);
n = strlen(z);
}
if(pOut!=0){
blob_append(pOut, z, n);
|
| ︙ | ︙ | |||
532 533 534 535 536 537 538 539 540 541 |
static int putsCmd(
Th_Interp *interp,
void *pConvert,
int argc,
const char **argv,
int *argl
){
if( argc!=2 ){
return Th_WrongNumArgs(interp, "puts STRING");
}
| > > > > > > > > > | | 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 |
static int putsCmd(
Th_Interp *interp,
void *pConvert,
int argc,
const char **argv,
int *argl
){
int encode = *(unsigned int*)pConvert;
int n;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "puts STRING");
}
n = argl[1];
if( encode==0 && n>0 && TH1_TAINTED(n) ){
if( Th_ReportTaint(interp, "output string", argv[1], n) ){
return TH_ERROR;
}
n = TH1_LEN(n);
}
sendText(0,(char*)argv[1], n, encode);
return TH_OK;
}
/*
** TH1 command: redirect URL ?withMethod?
**
** Issues an HTTP redirect to the specified URL and then exits the process.
|
| ︙ | ︙ | |||
3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 |
** Th_SetOutputBlob() has been called. If it has not been called,
** pThOut will be 0, which will redirect the output to CGI/stdout,
** as appropriate. We need to pass on g.th1Flags for the case of
** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
** inadvertently toggled off by a recursive call.
*/;
}
/*
** COMMAND: test-th-render
**
** Usage: %fossil test-th-render FILE
**
** Read the content of the file named "FILE" as if it were a header or
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 |
** Th_SetOutputBlob() has been called. If it has not been called,
** pThOut will be 0, which will redirect the output to CGI/stdout,
** as appropriate. We need to pass on g.th1Flags for the case of
** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
** inadvertently toggled off by a recursive call.
*/;
}
/*
** SETTING: vuln-report width=8 default=log
**
** This setting controls Fossil's behavior when it encounters a potential
** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
** scripts. Choices are:
**
** off Do nothing. Ignore the vulnerability.
**
** log Write a report of the problem into the error log.
**
** block Like "log" but also prevent the offending TH1 command
** from running.
**
** fatal Render an error message page instead of the requested
** page.
*/
/*
** Report misuse of a tainted string in TH1.
**
** The behavior depends on the vuln-report setting. If "off", this routine
** is a no-op. Otherwise, right a message into the error log. If
** vuln-report is "log", that is all that happens. But for any other
** value of vuln-report, a fatal error is raised.
*/
int Th_ReportTaint(
Th_Interp *interp, /* Report error here, if an error is reported */
const char *zWhere, /* Where the tainted string appears */
const char *zStr, /* The tainted string */
int nStr /* Length of the tainted string */
){
char *zDisp; /* Dispensation */
const char *zVulnType; /* Type of vulnerability */
zDisp = db_get("vuln-report","log");
if( is_false(zDisp) ) return 0;
if( strstr(zWhere,"SQL")!=0 ){
zVulnType = "SQL-injection";
}else{
zVulnType = "XSS";
}
nStr = TH1_LEN(nStr);
fossil_errorlog("possible %s vulnerability due to tainted TH1 %s: \"%.*s\"",
zVulnType, zWhere, nStr, zStr);
if( strcmp(zDisp,"log")==0 ){
return 0;
}
if( strcmp(zDisp,"block")==0 ){
char *z = mprintf("tainted %s: \"", zWhere);
Th_ErrorMessage(interp, z, zStr, nStr);
fossil_free(z);
}else{
char *z = mprintf("%#h", nStr, zStr);
cgi_reset_content();
style_submenu_enable(0);
style_set_current_feature("error");
style_header("Configuration Error");
@ <p>Error in a TH1 configuration script:
@ tainted %h(zWhere): "%z(z)"
style_finish_page();
cgi_reply();
fossil_exit(1);
}
return 1;
}
/*
** COMMAND: test-th-render
**
** Usage: %fossil test-th-render FILE
**
** Read the content of the file named "FILE" as if it were a header or
|
| ︙ | ︙ |