[pLog-svn] r7010 - in plugins/branches/lifetype-1.2: . csrf csrf/class csrf/class/security csrf/locale

jondaley at devel.lifetype.net jondaley at devel.lifetype.net
Sat Jul 31 01:46:51 EDT 2010


Author: jondaley
Date: 2010-07-31 01:46:51 -0400 (Sat, 31 Jul 2010)
New Revision: 7010

Added:
   plugins/branches/lifetype-1.2/csrf/
   plugins/branches/lifetype-1.2/csrf/class/
   plugins/branches/lifetype-1.2/csrf/class/security/
   plugins/branches/lifetype-1.2/csrf/class/security/csrffilter.class.php
   plugins/branches/lifetype-1.2/csrf/locale/
   plugins/branches/lifetype-1.2/csrf/locale/locale_en_UK.php
   plugins/branches/lifetype-1.2/csrf/plugincsrf.class.php
Log:
my attempt at a CSRF protector.  It works on all POST and GET requests.  The only error I saw was in the purge/doCleanup stuff, where there isn't an a href, but a javascript window.location=...  I didn't check the ajax draft saving, or anywhere else where there is AJAX.  There are probably a couple more places to check, but it works pretty well, I think.  And doesn't affect users (I dislike sites that don't let you have multiple tabs open, which most CSRF protectors don't allow) too much, other than cluttering up the GET URLs, but until we stop LT from using REQUEST all over the place and counting on things being done in GET, that's what we have to deal with.  Anyone up for testing this?  I'll install it on some blogs that I manage, and see how it goes for a bit.

Added: plugins/branches/lifetype-1.2/csrf/class/security/csrffilter.class.php
===================================================================
--- plugins/branches/lifetype-1.2/csrf/class/security/csrffilter.class.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/csrf/class/security/csrffilter.class.php	2010-07-31 05:46:51 UTC (rev 7010)
@@ -0,0 +1,149 @@
+<?php
+lt_include( PLOG_CLASS_PATH."class/security/pipelinefilter.class.php" );
+
+class CsrfFilter extends PipelineFilter 
+{
+    function CsrfFilter($pipelineRequest){
+        $this->PipelineFilter($pipelineRequest);
+    }
+    
+    function filter(){
+        $request  = $this->_pipelineRequest->getHttpRequest();
+        $token = $request->getValue(CSRF_TOKEN_NAME);
+        $op = $request->getValue("op");
+        
+            // Allow logouts to go through without CSRF protection
+            /* if(($op == "Logout") ||
+             ($op == "blogSelect" && $request->getValue("action") == "Logout"))
+             {
+             return new PipelineResult();
+             }*/
+
+            // Explicitly block those operations we care about and allow
+            // all others through.
+        switch($op){
+          case "previewPost":
+          case "addPost":
+          case "addArticleCategory":
+          case "addArticleCategoryAjax":
+          case "updateBlogSettings":
+          case "deletePost":
+          case "deletePosts":
+          case "changePostsStatus":
+          case "changePostsCategory":
+          case "addLinkCategory":
+          case "addLink":
+          case "updatePost":
+          case "deleteArticleCategory":
+          case "deleteArticleCategories":
+          case "editArticleCategory":
+          case "updateArticleCategory":
+          case "deleteLink":
+          case "deleteLinks":
+          case "changeLinksCategory":
+          case "deleteLinkCategory":
+          case "deleteLinkCategories":
+          case "updateLink":
+          case "updateLinkCategory":
+          case "deleteComment":
+          case "deleteComments":
+          case "changeCommentsStatus":
+          case "updateUserSettings":
+          case "sendTrackbacks":
+          case "deleteUsers":
+          case "deleteUser":
+          case "updateGlobalSettings":
+          case "updateUserProfile":
+          case "addUser":
+          case "addBlog":
+          case "updateEditBlog":
+          case "updateBlogUsers":
+          case "addBlogUser":
+          case "deleteBlogUserPermissions":
+          case "deleteBlogUsersPermissions":
+          case "deleteLocales":
+          case "deleteLocale":
+          case "uploadLocale":
+          case "scanLocales":
+          case "deleteTemplates":
+          case "deleteTemplate":
+          case "addTemplateUpload":
+          case "scanTemplates":
+          case "addBlogTemplate":
+          case "scanBlogTemplates":
+          case "deleteBlogTemplate":
+          case "deleteBlogTemplates":
+          case "deleteBlogs":
+          case "deleteBlog":
+          case "purgePosts":
+          case "addResourceAlbum":
+          case "addResource":
+          case "updateResource":
+          case "deleteResource":
+          case "updateResourceAlbum":
+          case "deleteResourceAlbum":
+          case "deleteResourceItems":
+          case "changeGalleryItemsAlbum":
+          case "markComment":
+          case "markTrackback":
+          case "purgeSpamComments":
+          case "regeneratePreview":
+          case "addCustomField":
+          case "deleteCustomFields":
+          case "deleteCustomField":
+          case "updateCustomField":
+          case "saveDraftArticleAjax":
+          case "deleteTrackback":
+          case "deleteTrackbacks":
+          case "changeTrackbacksStatus":
+          case "deleteReferrer":
+          case "deleteReferrers":
+          case "deleteArticleReferrer":
+          case "deleteArticleReferrers":
+          case "doCleanUp":
+          case "purgeUsers":
+          case "purgeBlogs":
+          case "finishRegisterBlog":
+          case "addBlogCategory":
+          case "deleteBlogCategory":
+          case "deleteBlogCategories":
+          case "addGlobalArticleCategory":
+          case "deleteGlobalArticleCategory":
+          case "deleteGlobalArticleCategories":
+          case "updateGlobalArticleCategory":
+          case "resendConfirmation":
+          case "adminBlogSelect":
+          case "updateBlogCategory":
+          case "deletePermission":
+          case "deletePermissions":
+          case "updatePermission":
+          case "updatePermission":
+          case "addPermission":
+          case "updateBlogUser":
+          case "updatePluginSettings":
+          case "changeBlogStatus":
+          case "changeUserStatus":
+                  // Interesting operations, whether by GET or POST
+              break;
+          default:
+                // don't care about the rest
+            return new PipelineResult();
+        }
+
+            // Get our token from the session
+        $session = HttpVars::getSession();
+        $sessioninfo = $session["SessionInfo"];
+        $saved_token = $sessioninfo->getValue(CSRF_TOKEN_NAME);
+            
+        if(!empty($saved_token) && $token == $saved_token){
+                // it's not empty and it matches, yay.
+            return new PipelineResult();
+        }
+        else{
+                // too bad. a hacker, or some bad coding/output filtering...
+            $blogInfo = $this->_pipelineRequest->getBlogInfo();
+            $locale = $blogInfo->getLocale();
+            return new PipelineResult(false, 0, $locale->tr("error_csrf_token_incorrect"));
+        }
+    }
+}

Added: plugins/branches/lifetype-1.2/csrf/locale/locale_en_UK.php
===================================================================
--- plugins/branches/lifetype-1.2/csrf/locale/locale_en_UK.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/csrf/locale/locale_en_UK.php	2010-07-31 05:46:51 UTC (rev 7010)
@@ -0,0 +1,2 @@
+<?php
+$messages["error_csrf_token_incorrect"] = "The Cross Site Request Forgery token was incorrect.  This token is designed to prevent hackers using your browser to modify your blog without your consent.  You will need to <a href='?op=Logout'>login again</a> to continue.";

Added: plugins/branches/lifetype-1.2/csrf/plugincsrf.class.php
===================================================================
--- plugins/branches/lifetype-1.2/csrf/plugincsrf.class.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/csrf/plugincsrf.class.php	2010-07-31 05:46:51 UTC (rev 7010)
@@ -0,0 +1,57 @@
+<?php
+
+lt_include( PLOG_CLASS_PATH."class/plugin/pluginbase.class.php" );
+
+define("CSRF_TOKEN_NAME", "CsrfToken");
+
+class PluginCSRF extends PluginBase
+{
+    function PluginCSRF($source){
+        $this->PluginBase($source);
+        
+        $this->id = "csrf";
+        $this->author = "Jon Daley";
+        $this->version = "20100731";
+        $this->desc = "Protects the administrators and blog editors from CSRF attacks.";
+
+            // this plugin only cares about the administration side
+        if($source == "admin"){
+            $this->registerNotification(EVENT_LOGIN_SUCCESS);
+            $this->registerNotification(EVENT_PROCESS_BLOG_ADMIN_TEMPLATE_OUTPUT);
+            lt_include(PLOG_CLASS_PATH."plugins/csrf/class/security/csrffilter.class.php");
+			$this->registerFilter( "CsrfFilter" );
+        }
+    }
+	
+    function process($eventType, $params){
+        $session = HttpVars::getSession();
+        $sessioninfo = $session["SessionInfo"];
+
+        switch($eventType){
+          case EVENT_PROCESS_BLOG_ADMIN_TEMPLATE_OUTPUT:
+                // Handle all GET/links
+                // TODO: don't modify any links that are going outside the domain
+                // TODO: only modify links that we explicitly care about?
+            $params['content'] = preg_replace('/(<a[^>]+op=[a-zA-Z]+)/i', '$1' .
+                                              '&'.CSRF_TOKEN_NAME.'='.
+                                              $sessioninfo->getValue(CSRF_TOKEN_NAME),
+                                              $params['content']);
+
+                // Handle all POST/forms
+            $element = '<input type="hidden" name="'.CSRF_TOKEN_NAME.
+                '" value="'.$sessioninfo->getValue(CSRF_TOKEN_NAME).'" />';
+            $params['content'] = preg_replace('/<form[^>]*>/i', '$0' . $element, $params['content']);
+
+            break;
+            
+          case EVENT_LOGIN_SUCCESS:
+            $sessioninfo->setValue(CSRF_TOKEN_NAME, md5(rand().time().filectime(__FILE__)));
+//            print "Token: ".$sessioninfo->getValue(CSRF_TOKEN_NAME)."<br/>\n";
+            break;
+
+          default:
+            return false;
+        }
+        return true;
+    }
+}



More information about the pLog-svn mailing list