Index: src/alerts.c ================================================================== --- src/alerts.c +++ src/alerts.c @@ -1545,11 +1545,11 @@ } style_set_current_feature("alerts"); alert_submenu_common(); needCaptcha = !login_is_individual(); if( P("submit") - && cgi_csrf_safe(1) + && cgi_csrf_safe(2) && subscribe_error_check(&eErr,&zErr,needCaptcha) ){ /* A validated request for a new subscription has been received. */ char ssub[20]; const char *zEAddr = P("e"); @@ -1856,11 +1856,11 @@ db_commit_transaction(); cgi_redirect("subscribe"); /*NOTREACHED*/ } alert_submenu_common(); - if( P("submit")!=0 && cgi_csrf_safe(1) ){ + if( P("submit")!=0 && cgi_csrf_safe(2) ){ char newSsub[10]; int nsub = 0; Blob update; sdonotcall = PB("sdonotcall"); @@ -1918,11 +1918,11 @@ "UPDATE subscriber SET lastContact=now()/86400" " WHERE subscriberId=%d", sid ); db_protect_pop(); } - if( P("delete")!=0 && cgi_csrf_safe(1) ){ + if( P("delete")!=0 && cgi_csrf_safe(2) ){ if( !PB("dodelete") ){ eErr = 9; zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to" " unsubscribe"); }else{ @@ -2269,11 +2269,11 @@ style_set_current_feature("alerts"); zEAddr = PD("e",""); dx = atoi(PD("dx","0")); - bSubmit = P("submit")!=0 && P("e")!=0 && cgi_csrf_safe(1); + bSubmit = P("submit")!=0 && P("e")!=0 && cgi_csrf_safe(2); if( bSubmit ){ if( !captcha_is_correct(1) ){ eErr = 2; zErr = mprintf("enter the security code shown below"); bSubmit = 0; @@ -3300,11 +3300,11 @@ } if( P("submit")!=0 && P("subject")!=0 && P("msg")!=0 && P("from")!=0 - && cgi_csrf_safe(1) + && cgi_csrf_safe(2) && captcha_is_correct(0) ){ Blob hdr, body; AlertSender *pSender = alert_sender_new(0,0); blob_init(&hdr, 0, 0); @@ -3477,11 +3477,11 @@ /* Visit the /announce/test1 page to see the CGI variables */ zAction = "announce/test1"; @

cgi_print_all(0, 0, 0); @

- }else if( P("submit")!=0 && cgi_csrf_safe(1) ){ + }else if( P("submit")!=0 && cgi_csrf_safe(2) ){ char *zErr = alert_send_announcement(); style_header("Announcement Sent"); if( zErr ){ @

Internal Error

@

The following error was reported by the system: @@ -3502,10 +3502,11 @@ return; } style_header("Send Announcement"); @

+ login_insert_csrf_secret(); @ if( g.perm.Admin ){ int aa = PB("aa"); int all = PB("all"); int aMod = PB("mods"); Index: src/builtin.c ================================================================== --- src/builtin.c +++ src/builtin.c @@ -666,18 +666,10 @@ CX("name: %!j,", (g.zLogin&&*g.zLogin) ? g.zLogin : "guest"); CX("isAdmin: %s", (g.perm.Admin || g.perm.Setup) ? "true" : "false"); CX("};\n"/*fossil.user*/); CX("if(fossil.config.skin.isDark) " "document.body.classList.add('fossil-dark-style');\n"); -#if 0 - /* Is it safe to emit the CSRF token here? Some pages add it - ** as a hidden form field. */ - if(g.zCsrfToken[0]!=0){ - CX("window.fossil.csrfToken = %!j;\n", - g.zCsrfToken); - } -#endif /* ** fossil.page holds info about the current page. This is also ** where the current page "should" store any of its own ** page-specific state, and it is reserved for that purpose. */ Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -314,17 +314,15 @@ if( g.zBaseURL!=0 && fossil_strncmp(g.zBaseURL, "https:", 6)==0 ){ zSecure = " secure;"; } if( lifetime!=0 ){ blob_appendf(&extraHeader, - "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; " - "%s Version=1\r\n", + "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; %s\r\n", zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure); }else{ blob_appendf(&extraHeader, - "Set-Cookie: %s=%t; Path=%s; HttpOnly; " - "%s Version=1\r\n", + "Set-Cookie: %s=%t; Path=%s; HttpOnly; %s\r\n", zName, zValue, zPath, zSecure); } } @@ -700,42 +698,66 @@ nBase = (int)strlen(g.zBaseURL); if( fossil_strncmp(g.zBaseURL,zRef,nBase)!=0 ) return 0; if( zRef[nBase]!=0 && zRef[nBase]!='/' ) return 0; return 1; } + +/* +** Return true if the current CGI request is a POST request +*/ +static int cgi_is_post_request(void){ + const char *zMethod = P("REQUEST_METHOD"); + if( zMethod==0 ) return 0; + if( strcmp(zMethod,"POST")!=0 ) return 0; + return 1; +} /* ** Return true if the current request appears to be safe from a -** Cross-Site Request Forgery (CSRF) attack. Conditions that must -** be met: +** Cross-Site Request Forgery (CSRF) attack. The level of checking +** is determined by the parameter. The higher the number, the more +** secure we are: +** +** 0: Request must come from the same origin +** 1: Same origin and must be a POST request +** 2: All of the above plus must have a valid CSRF token +** +** Results are cached in the g.okCsrf variable. The g.okCsrf value +** has meaning as follows: ** -** * The HTTP_REFERER must have the same origin -** * The REQUEST_METHOD must be POST - or requirePost==0 +** -1: Not a secure request +** 0: Status unknown +** 1: Request comes from the same origin +** 2: (1) plus it is a POST request +** 3: (2) plus there is a valid "csrf" token in the request */ -int cgi_csrf_safe(int requirePost){ - if( requirePost ){ - const char *zMethod = P("REQUEST_METHOD"); - if( zMethod==0 ) return 0; - if( strcmp(zMethod,"POST")!=0 ) return 0; +int cgi_csrf_safe(int securityLevel){ + if( g.okCsrf<0 ) return 0; + if( g.okCsrf==0 ){ + if( !cgi_same_origin() ){ + g.okCsrf = -1; + }else{ + g.okCsrf = 1; + if( cgi_is_post_request() ){ + g.okCsrf = 2; + if( fossil_strcmp(P("csrf"), g.zCsrfToken)==0 ){ + g.okCsrf = 3; + } + } + } } - return cgi_same_origin(); + return g.okCsrf >= (securityLevel+1); } /* -** If bLoginVerifyCsrf is true, this calls login_verify_csrf() to -** verify that the secret injected by login_insert_csrf_secret() is in -** the CGI environment and valid. If that fails, it does so -** fatally. If that passes and cgi_csrf_safe(1) returns false, this -** fails fatally with a message about a cross-site scripting attempt, -** else it returns without side effects. +** Verify that CSRF defenses are maximal - that the request comes from +** the same origin, that it is a POST request, and that there is a valid +** "csrf" token. If this is not the case, fail immediately. */ -void cgi_csrf_verify(int bLoginVerifyCsrf){ - if( bLoginVerifyCsrf!=0 ){ - login_verify_csrf_secret(); - } - if( 0==cgi_csrf_safe(1) ){ - fossil_fatal("Cross-site request forgery attempt"); +void cgi_csrf_verify(void){ + if( !cgi_csrf_safe(2) ){ + fossil_fatal("Cross-site Request Forgery detected"); } } /* ** Information about all query parameters, post parameter, cookies and Index: src/event.c ================================================================== --- src/event.c +++ src/event.c @@ -468,12 +468,11 @@ rid ); } } zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime); - if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){ - login_verify_csrf_secret(); + if( P("submit")!=0 && (zBody!=0 && zComment!=0) && cgi_csrf_safe(2) ){ if ( !event_commit_common(rid, zId, zBody, zETime, zMimetype, zComment, zTags, zClrFlag[0] ? zClr : 0) ){ style_header("Error"); @ Internal error: Fossil tried to make an invalid artifact for Index: src/forum.c ================================================================== --- src/forum.c +++ src/forum.c @@ -897,10 +897,11 @@ } }else if( bSameUser ){ /* Allow users to delete (reject) their own pending posts. */ @ } + login_insert_csrf_secret(); @ if( bSelect && forumpost_may_close() && iClosed>=0 ){ int iHead = forumpost_head_rid(p->fpid); @ @@ -1423,11 +1424,11 @@ login_check_credentials(); if( forumpost_may_close()==0 ){ login_needed(g.anon.Admin); return; } - cgi_csrf_verify(1); + cgi_csrf_verify(); fpid = symbolic_name_to_rid(zFpid, "f"); if( fpid<=0 ){ webpage_error("Missing or invalid fpid query parameter"); } fClose = sqlite3_strglob("*_close*", g.zPath)==0; @@ -1539,11 +1540,11 @@ login_check_credentials(); if( !g.perm.WrForum ){ login_needed(g.anon.WrForum); return; } - if( P("submit") && cgi_csrf_safe(1) ){ + if( P("submit") && cgi_csrf_safe(2) ){ if( forum_post(zTitle, 0, 0, 0, zMimetype, zContent, forum_post_flags()) ) return; } if( P("preview") && !whitespace_only(zContent) ){ @

Preview:

@@ -1560,10 +1561,11 @@ @ }else{ @ } forum_render_debug_options(); + login_insert_csrf_secret(); @ forum_emit_js(); style_finish_page(); } @@ -1611,11 +1613,11 @@ return; } bPreview = P("preview")!=0; bReply = P("reply")!=0; iClosed = forum_rid_is_closed(fpid, 1); - isCsrfSafe = cgi_csrf_safe(1); + isCsrfSafe = cgi_csrf_safe(2); bPrivate = content_is_private(fpid); bSameUser = login_is_individual() && fossil_strcmp(pPost->zUser, g.zLogin)==0; if( isCsrfSafe && (g.perm.ModForum || (bPrivate && bSameUser)) ){ if( g.perm.ModForum && P("approve") ){ @@ -1679,10 +1681,11 @@ forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki, "forumEdit", 1); @

Change Into:

forum_render(zTitle, zMimetype, zContent,"forumEdit", 1); @ + login_insert_csrf_secret(); @ @ @ @ if( zTitle ){ @@ -1706,10 +1709,11 @@ @

Preview of Edited Post:

forum_render(zTitle, zMimetype, zContent,"forumEdit", 1); } @

Revised Message:

@ + login_insert_csrf_secret(); @ @ forum_from_line(); forum_post_widget(zTitle, zMimetype, zContent); }else{ @@ -1750,10 +1754,11 @@ if( !iClosed || g.perm.Admin ) { @ } } forum_render_debug_options(); + login_insert_csrf_secret(); @ forum_emit_js(); style_finish_page(); } @@ -1842,14 +1847,13 @@ } } @

Settings

@

Configuration settings specific to the forum.

- if( P("submit") && cgi_csrf_safe(1) ){ + if( P("submit") && cgi_csrf_safe(2) ){ int i = 0; const char *zSetting; - login_verify_csrf_secret(); db_begin_transaction(); while( (zSetting = zSettingsBool[i++]) ){ const char *z = P(zSetting); if( !z || !z[0] ) z = "off"; db_set(zSetting/*works-like:"x"*/, z, 0); Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -3213,15 +3213,14 @@ zNewTag = PDT("tagname",""); zNewBrFlag = P("newbr") ? " checked" : ""; zNewBranch = PDT("brname",""); zCloseFlag = P("close") ? " checked" : ""; zHideFlag = P("hide") ? " checked" : ""; - if( P("apply") && cgi_csrf_safe(1) ){ + if( P("apply") && cgi_csrf_safe(2) ){ Blob ctrl; char *zNow; - login_verify_csrf_secret(); blob_zero(&ctrl); zNow = date_in_standard_format(zChngTime ? zChngTime : "now"); blob_appendf(&ctrl, "D %s\n", zNow); init_newtags(); if( zNewColorFlag[0] @@ -3300,11 +3299,10 @@ blob_reset(&suffix); } @

Make changes to attributes of check-in @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)]:

form_begin(0, "%R/ci_edit"); - login_insert_csrf_secret(); @
@
@ @ } @ @ } @ } db_finalize(&q); @@ -836,11 +840,11 @@ zTitle = mprintf("%s for Draft%d", aSkinAttr[ii].zTitle, iSkin); zBasis = PD("basis","current"); zDflt = skin_file_content(zBasis, zFile); zOrig = db_get_mprintf(zDflt, "draft%d-%s",iSkin,zFile); zContent = PD(zFile,zOrig); - if( P("revert")!=0 && cgi_csrf_safe(0) ){ + if( P("revert")!=0 && cgi_csrf_safe(2) ){ zContent = zDflt; isRevert = 1; } db_begin_transaction(); @@ -853,11 +857,13 @@ @
login_insert_csrf_secret(); @ @ @

Edit %s(zTitle):

- if( P("submit") && cgi_csrf_safe(0) && (zOrig==0 || strcmp(zOrig,zContent)!=0) ){ + if( P("submit") && cgi_csrf_safe(2) + && (zOrig==0 || strcmp(zOrig,zContent)!=0) + ){ db_set_mprintf(zContent, 0, "draft%d-%s",iSkin,zFile); } @ @
@@ -1042,10 +1048,11 @@ @ } } @ @

+ @ @ @ @

Step 2: Authenticate

@ if( isSetup ){ Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -259,10 +259,11 @@ }else{ needHrefJs = 1; @
} + login_insert_csrf_secret(); } /* ** Add a new element to the submenu */ @@ -1459,11 +1460,30 @@ @ g.zRepositoryName = %h(g.zRepositoryName)
@ load_average() = %f(load_average())
#ifndef _WIN32 @ RSS = %.2f(fossil_rss()/1000000.0) MB
#endif - @ cgi_csrf_safe(0) = %d(cgi_csrf_safe(0))
+ (void)cgi_csrf_safe(2); + switch( g.okCsrf ){ + case 1: { + @ CSRF safety = Same origin
+ break; + } + case 2: { + @ CSRF safety = Same origin, POST
+ break; + } + case 3: { + @ CSRF safety = Same origin, POST, CSRF token
+ break; + } + default: { + @ CSRF safety = unsafe
+ break; + } + } + @ fossil_exe_id() = %h(fossil_exe_id())
if( g.perm.Admin ){ int k; for(k=0; g.argvOrig[k]; k++){ Blob t; Index: src/th_main.c ================================================================== --- src/th_main.c +++ src/th_main.c @@ -605,11 +605,13 @@ int *argl ){ if( argc!=1 ){ return Th_WrongNumArgs(interp, "verifyCsrf"); } - login_verify_csrf_secret(); + if( !cgi_csrf_safe(2) ){ + fossil_fatal("possible CSRF attack"); + } return TH_OK; } /* ** TH1 command: verifyLogin Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -900,11 +900,14 @@ int i; int nJ = 0, rc = TH_OK; Blob tktchng, cksum; int needMod; - login_verify_csrf_secret(); + if( !cgi_csrf_safe(2) ){ + @

Error: Invalid CSRF token.

+ return TH_OK; + } if( !captcha_is_correct(0) ){ @

Error: Incorrect security code.

return TH_OK; } zUuid = (const char *)pUuid; @@ -1015,11 +1018,10 @@ initializeVariablesFromCGI(); getAllTicketFields(); initializeVariablesFromDb(); if( g.zPath[0]=='d' ) showAllFields(); form_begin(0, "%R/%s", g.zPath); - login_insert_csrf_secret(); if( P("date_override") && g.perm.Setup ){ @ } zScript = ticket_newpage_code(); if( g.zLogin && g.zLogin[0] ){ @@ -1105,11 +1107,10 @@ initializeVariablesFromCGI(); initializeVariablesFromDb(); if( g.zPath[0]=='d' ) showAllFields(); form_begin(0, "%R/%s", g.zPath); @ - login_insert_csrf_secret(); zScript = ticket_editpage_code(); Th_Store("login", login_name()); Th_Store("date", db_text(0, "SELECT datetime('now')")); Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); Index: src/tktsetup.c ================================================================== --- src/tktsetup.c +++ src/tktsetup.c @@ -135,18 +135,16 @@ if( z==0 ){ z = db_get(zDbField, zDfltValue); } style_set_current_feature("tktsetup"); style_header("Edit %s", zTitle); - if( P("clear")!=0 ){ - login_verify_csrf_secret(); + if( P("clear")!=0 && cgi_csrf_safe(2) ){ db_unset(zDbField/*works-like:"x"*/, 0); if( xRebuild ) xRebuild(); cgi_redirect("tktsetup"); - }else if( isSubmit ){ + }else if( isSubmit && cgi_csrf_safe(2) ){ char *zErr = 0; - login_verify_csrf_secret(); if( xText && (zErr = xText(z))!=0 ){ @

ERROR: %h(zErr)

}else{ db_set(zDbField/*works-like:"x"*/, z, 0); if( xRebuild ) xRebuild(); Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -1632,18 +1632,18 @@ zMimetype = wiki_filter_mimetypes(pWiki->zMimetype) /* see https://fossil-scm.org/forum/forumpost/0acfdaac80 */; } if( !isSandbox && P("submit")!=0 && P("r")!=0 && P("u")!=0 && (goodCaptcha = captcha_is_correct(0)) + && cgi_csrf_safe(2) ){ char *zDate; Blob cksum; Blob body; Blob wiki; blob_zero(&body); - login_verify_csrf_secret(); blob_append(&body, pWiki->zWiki, -1); blob_zero(&wiki); db_begin_transaction(); zDate = date_in_standard_format("now"); blob_appendf(&wiki, "D %s\n", zDate); @@ -1695,11 +1695,10 @@ @
blob_reset(&preview); } zUser = PD("u", g.zLogin); form_begin(0, "%R/wikiappend"); - login_insert_csrf_secret(); @ @ @ Your Name: @
zFormat = mimetype_common_name(zMimetype); Index: src/xfersetup.c ================================================================== --- src/xfersetup.c +++ src/xfersetup.c @@ -118,18 +118,16 @@ if( z==0 ){ z = db_get(zDbField, zDfltValue); } style_set_current_feature("xfersetup"); style_header("Edit %s", zTitle); - if( P("clear")!=0 ){ - login_verify_csrf_secret(); + if( P("clear")!=0 && cgi_csrf_safe(2) ){ db_unset(zDbField/*works-like:"x"*/, 0); if( xRebuild ) xRebuild(); z = zDfltValue; - }else if( isSubmit ){ + }else if( isSubmit && cgi_csrf_safe(2) ){ char *zErr = 0; - login_verify_csrf_secret(); if( xText && (zErr = xText(z))!=0 ){ @

ERROR: %h(zErr)

}else{ db_set(zDbField/*works-like:"x"*/, z, 0); if( xRebuild ) xRebuild();
User: Index: src/interwiki.c ================================================================== --- src/interwiki.c +++ src/interwiki.c @@ -314,11 +314,11 @@ login_check_credentials(); if( !g.perm.Read && !g.perm.RdWiki && ~g.perm.RdTkt ){ login_needed(0); return; } - if( g.perm.Setup && P("submit")!=0 && cgi_csrf_safe(1) ){ + if( g.perm.Setup && P("submit")!=0 && cgi_csrf_safe(2) ){ zTag = PT("tag"); zBase = PT("base"); zHash = PT("hash"); zWiki = PT("wiki"); if( zTag==0 || zTag[0]==0 || !interwiki_valid_name(zTag) ){ Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -49,10 +49,25 @@ # define sleep Sleep /* windows does not have sleep, but Sleep */ # endif #endif #include +/* +** Compute an appropriate Anti-CSRF token into g.zCsrfToken[]. +*/ +static void login_create_csrf_secret(const char *zSeed){ + unsigned char zResult[20]; + int i; + + sha1sum_binary(zSeed, zResult); + for(i=0; i } -/* -** Before using the results of a form, first call this routine to verify -** that this Anti-CSRF token is present and is valid. If the Anti-CSRF token -** is missing or is incorrect, that indicates a cross-site scripting attack. -** If the event of an attack is detected, an error message is generated and -** all further processing is aborted. -*/ -void login_verify_csrf_secret(void){ - if( g.okCsrf ) return; - if( fossil_strcmp(P("csrf"), g.zCsrfToken)==0 ){ - g.okCsrf = 1; - return; - } - fossil_fatal("Cross-site request forgery attempt"); -} - /* ** Check to see if the candidate username zUserID is already used. ** Return 1 if it is already in use. Return 0 if the name is ** available for a self-registeration. */ @@ -1978,11 +1982,11 @@ zConfirm = PDT("cp",""); zEAddr = PDT("ea",""); zDName = PDT("dn",""); /* Verify user imputs */ - if( P("new")==0 || !cgi_csrf_safe(1) ){ + if( P("new")==0 || !cgi_csrf_safe(2) ){ /* This is not a valid form submission. Fall through into ** the form display */ }else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){ iErrLine = 6; zErr = "Incorrect CAPTCHA"; @@ -2256,11 +2260,11 @@ return; } zEAddr = PDT("ea",""); /* Verify user imputs */ - if( !cgi_csrf_safe(1) || P("reqpwreset")==0 ){ + if( !cgi_csrf_safe(2) || P("reqpwreset")==0 ){ /* This is the initial display of the form. No processing or error ** checking is to be done. Fall through into the form display */ }else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){ iErrLine = 2; Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -256,12 +256,16 @@ /* all Tcl related context necessary for integration */ struct TclContext tcl; #endif /* For defense against Cross-site Request Forgery attacks */ - char zCsrfToken[12]; /* Value of the anti-CSRF token */ - int okCsrf; /* Anti-CSRF token is present and valid */ + char zCsrfToken[16]; /* Value of the anti-CSRF token */ + int okCsrf; /* -1: unsafe + ** 0: unknown + ** 1: same origin + ** 2: same origin + is POST + ** 3: same origin, POST, valid csrf token */ int parseCnt[10]; /* Counts of artifacts parsed */ FILE *fDebug; /* Write debug information here, if the file exists */ #ifdef FOSSIL_ENABLE_TH1_HOOKS int fNoThHook; /* Disable all TH1 command/webpage hooks */ Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -471,16 +471,15 @@ zClrKey = trim_string(PD("k","")); zDesc = trim_string(PD("d","")); zMimetype = P("m"); zTag = P("x"); report_update_reportfmt_table(); - if( rn>0 && P("del2") ){ - login_verify_csrf_secret(); + if( rn>0 && P("del2") && cgi_csrf_safe(2) ){ db_multi_exec("DELETE FROM reportfmt WHERE rn=%d", rn); cgi_redirect("reportlist"); return; - }else if( rn>0 && P("del1") ){ + }else if( rn>0 && P("del1") && cgi_csrf_safe(2) ){ zTitle = db_text(0, "SELECT title FROM reportfmt " "WHERE rn=%d", rn); if( zTitle==0 ) cgi_redirect("reportlist"); style_header("Are You Sure?"); @@ -514,12 +513,11 @@ && db_exists("SELECT 1 FROM reportfmt WHERE title=%Q and rn<>%d", zTitle, rn) ){ zErr = mprintf("There is already another report named \"%h\"", zTitle); } - if( zErr==0 ){ - login_verify_csrf_secret(); + if( zErr==0 && cgi_csrf_safe(2) ){ if( zTag && zTag[0]==0 ) zTag = 0; if( zDesc && zDesc[0]==0 ){ zDesc = 0; zMimetype = 0; } if( zMimetype && zMimetype[0]==0 ){ zDesc = 0; zMimetype = 0; } if( rn>0 ){ db_multi_exec( Index: src/security_audit.c ================================================================== --- src/security_audit.c +++ src/security_audit.c @@ -795,11 +795,11 @@ @ command. @ style_finish_page(); return; } - if( P("truncate1") && cgi_csrf_safe(1) ){ + if( P("truncate1") && cgi_csrf_safe(2) ){ fclose(fopen(g.zErrlog,"w")); } if( P("download") ){ Blob log; blob_read_from_file(&log, g.zErrlog, ExtFILE); @@ -808,10 +808,11 @@ return; } szFile = file_size(g.zErrlog, ExtFILE); if( P("truncate") ){ @
+ login_insert_csrf_secret(); @

Confirm that you want to truncate the %,lld(szFile)-byte error log: @ @ @

style_finish_page(); Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -202,12 +202,11 @@ if( zQ==0 && !disabled && P("submit") ){ zQ = "off"; } if( zQ ){ int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ); - if( iQ!=iVal ){ - login_verify_csrf_secret(); + if( iQ!=iVal && cgi_csrf_safe(2) ){ db_protect_only(PROTECT_NONE); db_set(zVar/*works-like:"x"*/, iQ ? "1" : "0", 0); db_protect_pop(); setup_incr_cfgcnt(); admin_log("Set option [%q] to [%q].", @@ -237,13 +236,12 @@ const char *zDflt, /* Default value if CONFIG table entry does not exist */ int disabled /* 1 if disabled */ ){ const char *zVal = db_get(zVar, zDflt); const char *zQ = P(zQParm); - if( zQ && fossil_strcmp(zQ,zVal)!=0 ){ + if( zQ && fossil_strcmp(zQ,zVal)!=0 && cgi_csrf_safe(2) ){ const int nZQ = (int)strlen(zQ); - login_verify_csrf_secret(); setup_incr_cfgcnt(); db_protect_only(PROTECT_NONE); db_set(zVar/*works-like:"x"*/, zQ, 0); db_protect_pop(); admin_log("Set entry_attribute %Q to: %.*s%s", @@ -270,13 +268,12 @@ const char *zDflt, /* Default value if CONFIG table entry does not exist */ int disabled /* 1 if the textarea should not be editable */ ){ const char *z = db_get(zVar, zDflt); const char *zQ = P(zQP); - if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){ + if( zQ && !disabled && fossil_strcmp(zQ,z)!=0 && cgi_csrf_safe(2) ){ const int nZQ = (int)strlen(zQ); - login_verify_csrf_secret(); db_protect_only(PROTECT_NONE); db_set(zVar/*works-like:"x"*/, zQ, 0); db_protect_pop(); setup_incr_cfgcnt(); admin_log("Set textarea_attribute %Q to: %.*s%s", @@ -309,13 +306,12 @@ const char *const *azChoice /* Choices in pairs (VAR value, Display) */ ){ const char *z = db_get(zVar, zDflt); const char *zQ = P(zQP); int i; - if( zQ && fossil_strcmp(zQ,z)!=0){ + if( zQ && fossil_strcmp(zQ,z)!=0 && cgi_csrf_safe(2) ){ const int nZQ = (int)strlen(zQ); - login_verify_csrf_secret(); db_unprotect(PROTECT_ALL); db_set(zVar/*works-like:"x"*/, zQ, 0); setup_incr_cfgcnt(); db_protect_pop(); admin_log("Set multiple_choice_attribute %Q to: %.*s%s", @@ -1458,11 +1454,11 @@ if( !g.perm.Admin ){ login_needed(0); return; } db_begin_transaction(); - if( P("clear")!=0 && cgi_csrf_safe(1) ){ + if( P("clear")!=0 && cgi_csrf_safe(2) ){ db_unprotect(PROTECT_CONFIG); db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'"); db_protect_pop(); cgi_replace_parameter("adunit",""); cgi_replace_parameter("adright",""); @@ -1560,11 +1556,11 @@ if( !g.perm.Admin ){ login_needed(0); return; } db_begin_transaction(); - if( !cgi_csrf_safe(1) ){ + if( !cgi_csrf_safe(2) ){ /* Allow no state changes if not safe from CSRF */ }else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){ Blob img; Stmt ins; blob_init(&img, aLogoImg, szLogoImg); @@ -1769,11 +1765,11 @@ if( !g.perm.Setup ){ login_needed(0); return; } add_content_sql_commands(g.db); - zQ = cgi_csrf_safe(1) ? P("q") : 0; + zQ = cgi_csrf_safe(2) ? P("q") : 0; style_set_current_feature("setup"); style_header("Raw SQL Commands"); @

Caution: There are no restrictions on the SQL that can be @ run by this page. You can do serious and irrepairable damage to the @ repository. Proceed with extreme caution.

@@ -1822,19 +1818,18 @@ go = 1; }else if( P("tablelist") ){ zQ = sqlite3_mprintf("SELECT*FROM pragma_table_list ORDER BY schema, name"); go = 1; } - if( go ){ + if( go && cgi_csrf_safe(2) ){ sqlite3_stmt *pStmt; int rc; const char *zTail; int nCol; int nRow = 0; int i; @
- login_verify_csrf_secret(); sqlite3_set_authorizer(g.db, raw_sql_query_authorizer, 0); search_sql_setup(g.db); rc = sqlite3_prepare_v2(g.db, zQ, -1, &pStmt, &zTail); if( rc!=SQLITE_OK ){ @
%h(sqlite3_errmsg(g.db))
@@ -1913,22 +1908,20 @@ style_header("Raw TH1 Commands"); @

Caution: There are no restrictions on the TH1 that can be @ run by this page. If Tcl integration was enabled at compile-time and @ the "tcl" setting is enabled, Tcl commands may be run as well.

@ - @
- login_insert_csrf_secret(); + form_begin(0, "%R/admin_th1"); @ TH1:
@
@ @
- if( go ){ + if( go && cgi_csrf_safe(2) ){ const char *zR; int rc; int n; @
- login_verify_csrf_secret(); rc = Th_Eval(g.interp, 0, zQ, -1); zR = Th_GetResult(g.interp, &n); if( rc==TH_OK ){ @
%h(zR)
}else{ @@ -2129,11 +2122,11 @@ Blob *pSql, const char *zOldName, const char *zNewName, const char *zValue ){ - if( !cgi_csrf_safe(1) ) return; + if( !cgi_csrf_safe(2) ) return; if( zNewName[0]==0 || zValue[0]==0 ){ if( zOldName[0] ){ blob_append_sql(pSql, "DELETE FROM config WHERE name='walias:%q';\n", zOldName); @@ -2173,17 +2166,16 @@ login_needed(0); return; } style_set_current_feature("setup"); style_header("URL Alias Configuration"); - if( P("submit")!=0 ){ + if( P("submit")!=0 && cgi_csrf_safe(2) ){ Blob token; Blob sql; const char *zNewName; const char *zValue; char zCnt[10]; - login_verify_csrf_secret(); blob_init(&namelist, PD("namelist",""), -1); blob_init(&sql, 0, 0); while( blob_token(&namelist, &token) ){ const char *zOldName = blob_str(&token); sqlite3_snprintf(sizeof(zCnt), zCnt, "n%d", cnt); Index: src/setupuser.c ================================================================== --- src/setupuser.c +++ src/setupuser.c @@ -342,11 +342,11 @@ cgi_redirect(cgi_referer("setup_ulist")); return; } /* Check for requests to delete the user */ - if( P("delete") && cgi_csrf_safe(1) ){ + if( P("delete") && cgi_csrf_safe(2) ){ int n; if( P("verifydelete") ){ /* Verified delete user request */ db_unprotect(PROTECT_USER); if( alert_tables_exist() ){ @@ -386,11 +386,11 @@ ** more are missing, no-op */ }else if( higherUser ){ /* An Admin (a) user cannot edit a Superuser (s) */ }else if( zDeleteVerify!=0 ){ /* Need to verify a delete request */ - }else if( !cgi_csrf_safe(1) ){ + }else if( !cgi_csrf_safe(2) ){ /* This might be a cross-site request forgery, so ignore it */ }else{ /* We have all the information we need to make the change to the user */ char c; char zCap[70], zNm[4]; @@ -440,11 +440,11 @@ @

@ [Bummer]

style_finish_page(); return; } - login_verify_csrf_secret(); + cgi_csrf_verify(); db_unprotect(PROTECT_USER); db_multi_exec( "REPLACE INTO user(uid,login,info,pw,cap,mtime) " "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())", uid, zLogin, P("info"), zPw, zCap Index: src/sha1.c ================================================================== --- src/sha1.c +++ src/sha1.c @@ -392,10 +392,23 @@ blob_resize(pCksum, 40); SHA1Final(zResult, &ctx); DigestToBase16(zResult, blob_buffer(pCksum)); return 0; } + +/* +** Compute a binary SHA1 checksum of a zero-terminated string. The +** result is stored in zOut, which is a buffer that must be at least +** 20 bytes in size. +*/ +void sha1sum_binary(const char *zIn, unsigned char *zOut){ + SHA1Context ctx; + + SHA1Init(&ctx); + SHA1Update(&ctx, (unsigned const char*)zIn, strlen(zIn)); + SHA1Final(zOut, &ctx); +} /* ** Compute the SHA1 checksum of a zero-terminated string. The ** result is held in memory obtained from mprintf(). */ Index: src/shun.c ================================================================== --- src/shun.c +++ src/shun.c @@ -98,14 +98,13 @@ } } zUuid = zCanonical; } style_header("Shunned Artifacts"); - if( zUuid && P("sub") ){ + if( zUuid && P("sub") && cgi_csrf_safe(2) ){ const char *p = zUuid; int allExist = 1; - login_verify_csrf_secret(); while( *p ){ db_multi_exec("DELETE FROM shun WHERE uuid=%Q", p); if( !db_exists("SELECT 1 FROM blob WHERE uuid=%Q", p) ){ allExist = 0; } @@ -127,14 +126,13 @@ @ It may be necessary to rebuild the repository using the @ fossil rebuild command-line before the artifact content @ can pulled in from other repositories.

} } - if( zUuid && P("add") ){ + if( zUuid && P("add") && cgi_csrf_safe(2) ){ const char *p = zUuid; int rid, tagid; - login_verify_csrf_secret(); while( *p ){ db_multi_exec( "INSERT OR IGNORE INTO shun(uuid,mtime)" " VALUES(%Q, now())", p); db_multi_exec("DELETE FROM attachment WHERE src=%Q", p); Index: src/skins.c ================================================================== --- src/skins.c +++ src/skins.c @@ -531,11 +531,11 @@ aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel); } style_set_current_feature("skins"); - if( cgi_csrf_safe(1) ){ + if( cgi_csrf_safe(2) ){ /* Process requests to delete a user-defined skin */ if( P("del1") && (zName = skinVarName(P("sn"), 1))!=0 ){ style_header("Confirm Custom Skin Delete"); @
@

Deletion of a custom skin is a permanent action that cannot @@ -628,10 +628,11 @@ seenCurrent = 1; }else{ @ @ @ + login_insert_csrf_secret(); if( pAltSkin==&aBuiltinSkin[i] ){ @ (Current override) } @

} @@ -652,10 +653,11 @@ @

Skins saved as "skin:*' entries \ @ in the CONFIG table:

%d(i).%h(zN)   @
+ login_insert_csrf_secret(); if( fossil_strcmp(zV, zCurrent)==0 ){ @ (Currently In Use) seenCurrent = 1; }else{ @ @@ -671,10 +673,11 @@ @

Current skin in css/header/footer/details entries \ @ in the CONFIG table:

%d(i).Current   @ @ + login_insert_csrf_secret(); @ } db_prepare(&q, "SELECT DISTINCT substr(name, 1, 6) FROM config" " WHERE name GLOB 'draft[1-9]-*'" @@ -689,10 +692,11 @@ @

Draft skins stored as "draft[1-9]-*' entries \ @ in the CONFIG table:

%d(i).%h(zN)   @
+ login_insert_csrf_secret(); @ @ @