Check-in [0be54823ba]
Not logged in
Overview

SHA1 Hash:0be54823ba2b5afe0b058e77376feaa1ff47751a
Date: 2008-10-18 12:55:44
User: drh
Comment:Add defenses against cross-site request forgery attacks.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/admin.c from [8499f27819] to [bf62e8d4f5].

@@ -76,15 +76,17 @@
   style_header("Admin SQL");
   @ <h2>SQL:</h2>
   @ You can enter only SELECT statements here, and some SQL-side functions
   @ are also restricted.<br/>
   @ <form action='' method='post'>
+  login_insert_csrf_secret();
   @ <textarea style='border:2px solid black' name='sql'
   @  cols='80' rows='5'>%h(zSql)</textarea>
   @ <br/><input type='submit' name='sql_submit'/> <input type='reset'/>
   @ </form>
   if( zSql[0] ){
+    login_verify_csrf_secret();
     sqlite3_set_authorizer(g.db, selectOnly, 0);
     db_generic_query_view(zSql, 0);
     sqlite3_set_authorizer(g.db, 0, 0);
   }
   style_footer();

Modified src/info.c from [26d96b828e] to [e9965b6667].

@@ -1022,10 +1022,11 @@
   if( P("apply") ){
     Blob ctrl;
     char *zDate;
     int nChng = 0;
 
+    login_verify_csrf_secret();
     blob_zero(&ctrl);
     zDate = db_text(0, "SELECT datetime('now')");
     zDate[10] = 'T';
     blob_appendf(&ctrl, "D %s\n", zDate);
     if( strcmp(zComment,zNewComment)!=0 ){
@@ -1055,10 +1056,11 @@
   style_header("Edit Baseline [%s]", zUuid);
   @ <p>Make changes to the User and Comment for baseline
   @ [<a href="vinfo?name=%d(rid)">%s(zUuid)</a>] then press the
   @ "Apply Changes" button.</p>
   @ <form action="%s(g.zBaseURL)/vedit" method="POST">
+  login_insert_csrf_secret();
   @ <input type="hidden" name="r" value="%d(rid)">
   @ <p>
   @ <b>User:</b> <input type="text" name="u" size="20" value="%h(zNewUser)">
   @ </p>
   @ <p><b>Comment:</b></b><br />

Modified src/login.c from [d0f9596e5f] to [debcf9a389].

@@ -234,16 +234,16 @@
 ** and is valid.  If the login cookie checks out, it then sets
 ** g.zUserUuid appropriately.
 **
 */
 void login_check_credentials(void){
-  int uid = 0;
-  const char *zCookie;
-  const char *zRemoteAddr;
-  const char *zCap = 0;
-  const char *zNcap;
-  const char *zAcap;
+  int uid = 0;                  /* User id */
+  const char *zCookie;          /* Text of the login cookie */
+  const char *zRemoteAddr;      /* IP address of the requestor */
+  const char *zCap = 0;         /* Capability string */
+  const char *zNcap;            /* Capabilities of user "nobody" */
+  const char *zAcap;            /* Capabllities of user "anonymous" */
 
   /* Only run this check once.  */
   if( g.userUid!=0 ) return;
 
 
@@ -255,10 +255,11 @@
   if( strcmp(zRemoteAddr, "127.0.0.1")==0 && db_get_int("localauth",0)==0 ){
     uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
     g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
     zCap = "s";
     g.noPswd = 1;
+    strcpy(g.zCsrfToken, "localhost");
   }
 
   /* Check the login cookie to see if it matches a known valid user.
   */
   if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
@@ -272,18 +273,20 @@
             atoi(zCookie), zCookie, zRemoteAddr
          );
     }else if( zCookie[0]=='a' ){
       uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'");
     }
+    snprintf(g.zCsrfToken, sizeof(g.zCsrfToken), "%.10s", zCookie);
   }
 
   if( uid==0 ){
     uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
     if( uid==0 ){
       uid = -1;
       zCap = "";
     }
+    strcpy(g.zCsrfToken, "none");
   }
   if( zCap==0 ){
     if( uid ){
       Stmt s;
       db_prepare(&s, "SELECT login, cap FROM user WHERE uid=%d", uid);
@@ -431,6 +434,30 @@
     const char *zUrl = PD("REQUEST_URI", "index");
     @ <p>Many <font color="red">hyperlinks are disabled.</font><br />
     @ Use <a href="%s(g.zTop)/login?anon=1&g=%T(zUrl)">anonymous login</a>
     @ to enable hyperlinks.</p>
   }
+}
+
+/*
+** While rendering a form, call this routine to add the Anti-CSRF token
+** as a hidden element of the form.
+*/
+void login_insert_csrf_secret(void){
+  @ <input type="hidden" name="csrf" value="%s(g.zCsrfToken)">
+}
+
+/*
+** Before using the results of a form, first call this routine to verify
+** that ths Anti-CSRF token is present and is valid.  If the Anti-CSRF token
+** is missing or is incorrect, then this emits and error message and never
+** returns.
+*/
+void login_verify_csrf_secret(void){
+  const char *zCsrf;            /* The CSRF secret */
+  if( g.okCsrf ) return;
+  if( (zCsrf = P("csrf"))!=0 && strcmp(zCsrf, g.zCsrfToken)==0 ){
+    g.okCsrf = 1;
+    return;
+  }
+  fossil_fatal("Cross-site request forgery attempt");
 }

Modified src/main.c from [e3e3bcd32f] to [bc031c08b3].

@@ -118,10 +118,14 @@
   int okApndTkt;          /* c: append to tickets via the web */
   int okWrTkt;            /* w: make changes to tickets via web */
   int okTktFmt;           /* t: create new ticket report formats */
   int okRdAddr;           /* e: read email addresses or other private data */
   int okZip;              /* z: download zipped artifact via /zip URL */
+
+  /* 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 */
 
   FILE *fDebug;           /* Write debug information here, if the file exists */
 
   /* Storage for the aux() and/or option() SQL function arguments */
   int nAux;                    /* Number of distinct aux() or option() values */

Modified src/report.c from [fee58e3fd7] to [e35bb33ea9].

@@ -319,10 +319,11 @@
   zOwner = PD("w",g.zLogin);
   z = P("s");
   zSQL = z ? trim_string(z) : 0;
   zClrKey = trim_string(PD("k",""));
   if( rn>0 && P("del2") ){
+    login_verify_csrf_secret();
     db_multi_exec("DELETE FROM reportfmt WHERE rn=%d", rn);
     cgi_redirect("reportlist");
     return;
   }else if( rn>0 && P("del1") ){
     zTitle = db_text(0, "SELECT title FROM reportfmt "
@@ -335,10 +336,11 @@
     @ <strong>%h(zTitle)</strong> from
     @ the database.  This is an irreversible operation.  All records
     @ related to this report will be removed and cannot be recovered.</p>
     @
     @ <input type="hidden" name="rn" value="%d(rn)">
+    login_insert_csrf_secret();
     @ <input type="submit" name="del2" value="Delete The Report">
     @ <input type="submit" name="can" value="Cancel">
     @ </form>
     style_footer();
     return;
@@ -354,10 +356,11 @@
       zErr = "Please supply a title";
     }else{
       zErr = verify_sql_statement(zSQL);
     }
     if( zErr==0 ){
+      login_verify_csrf_secret();
       if( rn>0 ){
         db_multi_exec("UPDATE reportfmt SET title=%Q, sqlcode=%Q,"
                       " owner=%Q, cols=%Q WHERE rn=%d",
            zTitle, zSQL, zOwner, zClrKey, rn);
       }else{
@@ -404,10 +407,11 @@
   @ <p>Report Title:<br>
   @ <input type="text" name="t" value="%h(zTitle)" size="60"></p>
   @ <p>Enter a complete SQL query statement against the "TICKET" table:<br>
   @ <textarea name="s" rows="20" cols="80">%h(zSQL)</textarea>
   @ </p>
+  login_insert_csrf_secret();
   if( g.okAdmin ){
     @ <p>Report owner:
     @ <input type="text" name="w" size="20" value="%h(zOwner)">
     @ </p>
   } else {

Modified src/setup.c from [1f3b727596] to [5836f2e874].

@@ -286,10 +286,11 @@
       @
       @ <p><a href="setup_uedit?id=%d(uid))>[Bummer]</a></p>
       style_footer();
       return;
     }
+    login_verify_csrf_secret();
     db_multi_exec(
        "REPLACE INTO user(uid,login,info,pw,cap) "
        "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s')",
       uid, P("login"), P("info"), zPw, zCap
     );
@@ -340,10 +341,11 @@
   }else{
     style_header("Add A New User");
   }
   @ <table align="left" hspace="20" vspace="10"><tr><td>
   @ <form action="%s(g.zPath)" method="POST">
+  login_insert_csrf_secret();
   @ <table>
   @ <tr>
   @   <td align="right"><nobr>User ID:</nobr></td>
   if( uid ){
     @   <td>%d(uid) <input type="hidden" name="id" value="%d(uid)"></td>
@@ -543,10 +545,11 @@
     zQ = "off";
   }
   if( zQ ){
     int iQ = strcmp(zQ,"on")==0 || atoi(zQ);
     if( iQ!=iVal ){
+      login_verify_csrf_secret();
       db_set(zVar, iQ ? "1" : "0", 0);
       iVal = iQ;
     }
   }
   if( iVal ){
@@ -567,10 +570,11 @@
   char *zDflt     /* Default value if VAR table entry does not exist */
 ){
   const char *zVal = db_get(zVar, zDflt);
   const char *zQ = P(zQParm);
   if( zQ && strcmp(zQ,zVal)!=0 ){
+    login_verify_csrf_secret();
     db_set(zVar, zQ, 0);
     zVal = zQ;
   }
   @ <input type="text" name="%s(zQParm)" value="%h(zVal)" size="%d(width)">
   @ <b>%s(zLabel)</b>
@@ -588,10 +592,11 @@
   const char *zDflt     /* Default value if VAR table entry does not exist */
 ){
   const char *z = db_get(zVar, (char*)zDflt);
   const char *zQ = P(zQP);
   if( zQ && strcmp(zQ,z)!=0 ){
+    login_verify_csrf_secret();
     db_set(zVar, zQ, 0);
     z = zQ;
   }
   if( rows>0 && cols>0 ){
     @ <textarea name="%s(zQP)" rows="%d(rows)" cols="%d(cols)">%h(z)</textarea>
@@ -610,11 +615,11 @@
   }
 
   style_header("Access Control Settings");
   db_begin_transaction();
   @ <form action="%s(g.zBaseURL)/setup_access" method="POST">
-
+  login_insert_csrf_secret();
   @ <hr>
   onoff_attribute("Require password for local access",
      "localauth", "localauth", 1);
   @ <p>When enabled, the password sign-in is required for
   @ web access coming from 127.0.0.1.  When disabled, web access
@@ -661,10 +666,11 @@
   }
 
   style_header("Timeline Display Preferences");
   db_begin_transaction();
   @ <form action="%s(g.zBaseURL)/setup_timeline" method="POST">
+  login_insert_csrf_secret();
 
   @ <hr>
   onoff_attribute("Block markup in timeline",
                   "timeline-block-markup", "tbm", 0);
   @ <p>In timeline displays, check-in comments can be displayed with or
@@ -693,10 +699,11 @@
   }
 
   style_header("WWW Configuration");
   db_begin_transaction();
   @ <form action="%s(g.zBaseURL)/setup_config" method="POST">
+  login_insert_csrf_secret();
   @ <hr />
   entry_attribute("Project Name", 60, "project-name", "pn", "");
   @ <p>Give your project a name so visitors know what this site is about.
   @ The project name will also be used as the RSS feed title.</p>
   @ <hr />
@@ -735,10 +742,11 @@
   if( !g.okSetup ){
     login_needed();
   }
   style_header("Edit CSS");
   @ <form action="%s(g.zBaseURL)/setup_editcss" method="POST">
+  login_insert_csrf_secret();
   @ Edit the CSS:<br />
   textarea_attribute("", 40, 80, "css", "css", zDefaultCSS);
   @ <br />
   @ <input type="submit" name="submit" value="Apply Changes">
   @ </form>
@@ -765,10 +773,11 @@
   }else{
     textarea_attribute(0, 0, 0, "header", "header", zDefaultHeader);
   }
   style_header("Edit Page Header");
   @ <form action="%s(g.zBaseURL)/setup_header" method="POST">
+  login_insert_csrf_secret();
   @ <p>Edit HTML text with embedded TH1 (a TCL dialect) that will be used to
   @ generate the beginning of every page through start of the main
   @ menu.</p>
   textarea_attribute("", 40, 80, "header", "header", zDefaultHeader);
   @ <br />
@@ -799,10 +808,11 @@
   }else{
     textarea_attribute(0, 0, 0, "footer", "footer", zDefaultFooter);
   }
   style_header("Edit Page Footer");
   @ <form action="%s(g.zBaseURL)/setup_footer" method="POST">
+  login_insert_csrf_secret();
   @ <p>Edit HTML text with embedded TH1 (a TCL dialect) that will be used to
   @ generate the end of every page.</p>
   textarea_attribute("", 20, 80, "footer", "footer", zDefaultFooter);
   @ <br />
   @ <input type="submit" name="submit" value="Apply Changes">

Modified src/shun.c from [e38b6e743d] to [5ce55378d6].

@@ -65,10 +65,11 @@
       zUuid = zCanonical;
     }
   }
   style_header("Shunned Artifacts");
   if( zUuid && P("sub") ){
+    login_verify_csrf_secret();
     db_multi_exec("DELETE FROM shun WHERE uuid='%s'", zUuid);
     if( db_exists("SELECT 1 FROM blob WHERE uuid='%s'", zUuid) ){
       @ <p><font color="blue">Artifact
       @ <a href="%s(g.zBaseURL)/artifact/%s(zUuid)">%s(zUuid)</a> is no
       @ longer being shunned.</font></p>
@@ -79,10 +80,11 @@
       @ <b>fossil rebuild</b> command-line before the artifact content
       @ can pulled in from other respositories.</font></p>
     }
   }
   if( zUuid && P("add") ){
+    login_verify_csrf_secret();
     db_multi_exec("INSERT OR IGNORE INTO shun VALUES('%s')", zUuid);
     @ <p><font color="blue">Artifact
     @ <a href="%s(g.zBaseURL)/artifact/%s(zUuid)">%s(zUuid)</a> has been
     @ shunned.  It will no longer be pushed.
     @ It will be removed from the repository the next time the respository
@@ -124,10 +126,11 @@
   @ of the repository.  Do not shun artifacts merely to remove them from
   @ sight - set the "hidden" tag on such artifacts instead.</p>
   @
   @ <blockquote>
   @ <form method="POST" action="%s(g.zBaseURL)/%s(g.zPath)">
+  login_insert_csrf_secret();
   @ <input type="text" name="uuid" value="%h(PD("shun",""))" size="50">
   @ <input type="submit" name="add" value="Shun">
   @ </form>
   @ </blockquote>
   @
@@ -137,10 +140,11 @@
   @ the formerly shunned artifact will be accepted on subsequent sync
   @ operations.</p>
   @
   @ <blockquote>
   @ <form method="POST" action="%s(g.zBaseURL)/%s(g.zPath)">
+  login_insert_csrf_secret();
   @ <input type="text" name="uuid" size="50">
   @ <input type="submit" name="sub" value="Accept">
   @ </form>
   @ </blockquote>
   style_footer();

Modified src/tkt.c from [4222b4dfef] to [e980babe0a].

@@ -384,10 +384,11 @@
   const char *zUuid;
   int i;
   int rid;
   Blob tktchng, cksum;
 
+  login_verify_csrf_secret();
   zUuid = (const char *)pUuid;
   blob_zero(&tktchng);
   zDate = db_text(0, "SELECT datetime('now')");
   zDate[10] = 'T';
   blob_appendf(&tktchng, "D %s\n", zDate);
@@ -467,10 +468,11 @@
   ticket_init();
   getAllTicketFields();
   initializeVariablesFromDb();
   initializeVariablesFromCGI();
   @ <form method="POST" action="%s(g.zBaseURL)/%s(g.zPath)">
+  login_insert_csrf_secret();
   zScript = ticket_newpage_code();
   Th_Store("login", g.zLogin);
   Th_Store("date", db_text(0, "SELECT datetime('now')"));
   Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
                    (void*)&zNewUuid, 0);
@@ -528,10 +530,11 @@
   getAllTicketFields();
   initializeVariablesFromCGI();
   initializeVariablesFromDb();
   @ <form method="POST" action="%s(g.zBaseURL)/%s(g.zPath)">
   @ <input type="hidden" name="name" value="%s(zName)">
+  login_insert_csrf_secret();
   zScript = ticket_editpage_code();
   Th_Store("login", g.zLogin);
   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);

Modified src/tktsetup.c from [cc6def603d] to [c8d31b703f].

@@ -119,24 +119,27 @@
   if( z==0 ){
     z = db_get(zDbField, (char*)zDfltValue);
   }
   style_header("Edit %s", zTitle);
   if( P("clear")!=0 ){
+    login_verify_csrf_secret();
     db_unset(zDbField, 0);
     if( xRebuild ) xRebuild();
     z = zDfltValue;
   }else if( isSubmit ){
     char *zErr = 0;
+    login_verify_csrf_secret();
     if( xText && (zErr = xText(z))!=0 ){
       @ <p><font color="red"><b>ERROR: %h(zErr)</b></font></p>
     }else{
       db_set(zDbField, z, 0);
       if( xRebuild ) xRebuild();
       cgi_redirect("tktsetup");
     }
   }
   @ <form action="%s(g.zBaseURL)/%s(g.zPath)" method="POST">
+  login_insert_csrf_secret();
   @ <p>%s(zDesc)</p>
   @ <textarea name="x" rows="%d(height)" cols="80">%h(z)</textarea>
   @ <blockquote>
   @ <input type="submit" name="submit" value="Apply Changes">
   @ <input type="submit" name="clear" value="Revert To Default">
@@ -634,10 +637,11 @@
     cgi_redirect("tktsetup");
   }
   style_header("Ticket Display On Timelines");
   db_begin_transaction();
   @ <form action="%s(g.zBaseURL)/tktsetup_timeline" method="POST">
+  login_insert_csrf_secret();
 
   @ <hr>
   entry_attribute("Ticket Title", 40, "ticket-title-expr", "t", "title");
   @ <p>An SQL expression in a query against the TICKET table that will
   @ return the title of the ticket for display purposes after hyperlinks to

Modified src/wiki.c from [6fc1aec89b] to [5bb1f01093].

@@ -251,10 +251,11 @@
     blob_zero(&wiki);
     db_begin_transaction();
     if( isSandbox ){
       db_set("sandbox",zBody,0);
     }else{
+      login_verify_csrf_secret();
       zDate = db_text(0, "SELECT datetime('now')");
       zDate[10] = 'T';
       blob_appendf(&wiki, "D %s\n", zDate);
       free(zDate);
       blob_appendf(&wiki, "L %F\n", zPageName);
@@ -300,10 +301,11 @@
     if( z[0]=='\n' ) n++;
   }
   if( n<20 ) n = 20;
   if( n>40 ) n = 40;
   @ <form method="POST" action="%s(g.zBaseURL)/wikiedit">
+  login_insert_csrf_secret();
   @ <input type="hidden" name="name" value="%h(zPageName)">
   @ <textarea name="w" class="wikiedit" cols="80"
   @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
   @ <br>
   @ <input type="submit" name="preview" value="Preview Your Changes">
@@ -384,10 +386,11 @@
     if( isSandbox ){
       blob_appendf(&body, db_get("sandbox",""));
       appendRemark(&body);
       db_set("sandbox", blob_str(&body), 0);
     }else{
+      login_verify_csrf_secret();
       content_get(rid, &content);
       manifest_parse(&m, &content);
       if( m.type==CFTYPE_WIKI ){
         blob_append(&body, m.zWiki, -1);
       }
@@ -435,10 +438,11 @@
     @ <hr>
     blob_reset(&preview);
   }
   zUser = PD("u", g.zLogin);
   @ <form method="POST" action="%s(g.zBaseURL)/wikiappend">
+  login_insert_csrf_secret();
   @ <input type="hidden" name="name" value="%h(zPageName)">
   @ Your Name:
   @ <input type="text" name="u" size="20" value="%h(zUser)"><br>
   @ Comment to append:<br>
   @ <textarea name="r" class="wikiedit" cols="80"