diff options
author | Lukas Reschke <lukas@statuscode.ch> | 2012-09-28 10:54:11 -0700 |
---|---|---|
committer | Lukas Reschke <lukas@statuscode.ch> | 2012-09-28 10:54:11 -0700 |
commit | cfc98398120065d33659f36573f81bcea9a3e97d (patch) | |
tree | dfb19e6ecabf61e14eef01b646825c1685233045 | |
parent | 35357f3afb90dec1d0a9755ab0e0504a916c5e5a (diff) | |
parent | 71454b1bca0accd1ab5d7628169d4714bb682030 (diff) | |
download | nextcloud-server-cfc98398120065d33659f36573f81bcea9a3e97d.tar.gz nextcloud-server-cfc98398120065d33659f36573f81bcea9a3e97d.zip |
Merge pull request #19 from arkascha/master
Reimplementation of CSRF protection strategy
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | core/ajax/requesttoken.php | 41 | ||||
-rw-r--r-- | core/js/eventsource.js | 2 | ||||
-rw-r--r-- | core/js/requesttoken.js | 55 | ||||
-rw-r--r-- | core/templates/layout.user.php | 9 | ||||
-rw-r--r-- | lib/base.php | 2 | ||||
-rw-r--r-- | lib/template.php | 2 | ||||
-rwxr-xr-x | lib/util.php | 36 |
8 files changed, 130 insertions, 18 deletions
@@ -17,6 +17,7 @@ ownCloud is written by: Sam Tuke Simon Birnbach Lukas Reschke + Christian Reiner … With help from many libraries and frameworks including: diff --git a/core/ajax/requesttoken.php b/core/ajax/requesttoken.php new file mode 100644 index 00000000000..96d5402e621 --- /dev/null +++ b/core/ajax/requesttoken.php @@ -0,0 +1,41 @@ +<?php +/** +* ownCloud +* @author Christian Reiner +* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info> +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the license, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. +* If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * @file core/ajax/requesttoken.php + * @brief Ajax method to retrieve a fresh request protection token for ajax calls + * @return json: success/error state indicator including a fresh request token + * @author Christian Reiner + */ +require_once '../../lib/base.php'; + +// don't load apps or filesystem for this task +$RUNTIME_NOAPPS = TRUE; +$RUNTIME_NOSETUPFS = TRUE; + +// Sanity checks +// using OCP\JSON::callCheck() below protects the token refreshing itself. +//OCP\JSON::callCheck ( ); +OCP\JSON::checkLoggedIn ( ); +// hand out a fresh token +OCP\JSON::success ( array ( 'token' => OCP\Util::callRegister() ) ); +?> diff --git a/core/js/eventsource.js b/core/js/eventsource.js index e3ad7e3a671..45c63715a7e 100644 --- a/core/js/eventsource.js +++ b/core/js/eventsource.js @@ -40,7 +40,7 @@ OC.EventSource=function(src,data){ dataStr+=name+'='+encodeURIComponent(data[name])+'&'; } } - dataStr+='requesttoken='+OC.EventSource.requesttoken; + dataStr+='requesttoken='+OC.Request.Token; if(!this.useFallBack && typeof EventSource !='undefined'){ this.source=new EventSource(src+'?'+dataStr); this.source.onmessage=function(e){ diff --git a/core/js/requesttoken.js b/core/js/requesttoken.js new file mode 100644 index 00000000000..0d78cd7e93b --- /dev/null +++ b/core/js/requesttoken.js @@ -0,0 +1,55 @@ +/** + * ownCloud + * + * @file core/js/requesttoken.js + * @brief Routine to refresh the Request protection request token periodically + * @author Christian Reiner (arkascha) + * @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the license, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. + * If not, see <http://www.gnu.org/licenses/>. + * + */ + +OC.Request = { + // the request token + Token: {}, + // the lifespan span (in secs) + Lifespan: {}, + // method to refresh the local request token periodically + Refresh: function(){ + // just a client side console log to preserve efficiency + console.log("refreshing request token (lifebeat)"); + var dfd=new $.Deferred(); + $.ajax({ + type: 'POST', + url: OC.filePath('core','ajax','requesttoken.php'), + cache: false, + data: { }, + dataType: 'json' + }).done(function(response){ + // store refreshed token inside this class + OC.Request.Token=response.token; + dfd.resolve(); + }).fail(dfd.reject); + return dfd; + } +} +// accept requesttoken and lifespan into the OC namespace +OC.Request.Token = oc_requesttoken; +OC.Request.Lifespan = oc_requestlifespan; +// refresh the request token periodically shortly before it becomes invalid on the server side +setInterval(OC.Request.Refresh,Math.floor(1000*OC.Request.Lifespan*0.93)), // 93% of lifespan value, close to when the token expires +// early bind token as additional ajax argument for every single request +$(document).bind('ajaxSend', function(elm, xhr, s){xhr.setRequestHeader('requesttoken', OC.Request.Token);}); diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 679be2657d4..b6d8a7604a2 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -11,6 +11,8 @@ var oc_webroot = '<?php echo OC::$WEBROOT; ?>'; var oc_appswebroots = <?php echo $_['apps_paths'] ?>; var oc_current_user = '<?php echo OC_User::getUser() ?>'; + var oc_requesttoken = '<?php echo $_['requesttoken']; ?>'; + var oc_requestlifespan = '<?php echo $_['requestlifespan']; ?>'; </script> <?php foreach($_['jsfiles'] as $jsfile): ?> <script type="text/javascript" src="<?php echo $jsfile; ?>"></script> @@ -24,13 +26,6 @@ echo '/>'; ?> <?php endforeach; ?> - <script type="text/javascript"> - requesttoken = '<?php echo $_['requesttoken']; ?>'; - OC.EventSource.requesttoken=requesttoken; - $(document).bind('ajaxSend', function(elm, xhr, s) { - xhr.setRequestHeader('requesttoken', requesttoken); - }); - </script> </head> <body id="<?php echo $_['bodyid'];?>"> diff --git a/lib/base.php b/lib/base.php index f6afc8fe2fe..5a2decc6f63 100644 --- a/lib/base.php +++ b/lib/base.php @@ -240,6 +240,8 @@ class OC{ OC_Util::addScript( "jquery-tipsy" ); OC_Util::addScript( "oc-dialogs" ); OC_Util::addScript( "js" ); + // request protection token MUST be defined after the jquery library but before any $('document').ready() + OC_Util::addScript( "requesttoken" ); OC_Util::addScript( "eventsource" ); OC_Util::addScript( "config" ); //OC_Util::addScript( "multiselect" ); diff --git a/lib/template.php b/lib/template.php index 0987d6f0d88..681b3f0b140 100644 --- a/lib/template.php +++ b/lib/template.php @@ -157,6 +157,7 @@ class OC_Template{ $this->vars = array(); if($renderas == 'user') { $this->vars['requesttoken'] = OC_Util::callRegister(); + $this->vars['requestlifespan'] = OC_Util::$callLifespan; } $parts = explode('/', $app); // fix translation when app is something like core/lostpassword $this->l10n = OC_L10N::get($parts[0]); @@ -374,6 +375,7 @@ class OC_Template{ $page = new OC_TemplateLayout($this->renderas); if($this->renderas == 'user') { $page->assign('requesttoken', $this->vars['requesttoken']); + $page->assign('requestlifespan', $this->vars['requestlifespan']); } // Add custom headers diff --git a/lib/util.php b/lib/util.php index c89c4d8c7c1..777cb7a28fc 100755 --- a/lib/util.php +++ b/lib/util.php @@ -416,14 +416,29 @@ class OC_Util { } /** - * @brief Register an get/post call. This is important to prevent CSRF attacks - * Todo: Write howto + * @brief Static lifespan (in seconds) when a request token expires. + * @see OC_Util::callRegister() + * @see OC_Util::isCallRegistered() + * @description + * Also required for the client side to compute the piont in time when to + * request a fresh token. The client will do so when nearly 97% of the + * timespan coded here has expired. + */ + public static $callLifespan = 3600; // 3600 secs = 1 hour + + /** + * @brief Register an get/post call. Important to prevent CSRF attacks. + * @todo Write howto: CSRF protection guide * @return $token Generated token. + * @description + * Creates a 'request token' (random) and stores it inside the session. + * Ever subsequent (ajax) request must use such a valid token to succeed, + * otherwise the request will be denied as a protection against CSRF. + * The tokens expire after a fixed lifespan. + * @see OC_Util::$callLifespan + * @see OC_Util::isCallRegistered() */ public static function callRegister() { - //mamimum time before token exires - $maxtime=(60*60); // 1 hour - // generate a random token. $token=mt_rand(1000,9000).mt_rand(1000,9000).mt_rand(1000,9000); @@ -436,7 +451,8 @@ class OC_Util { foreach($_SESSION as $key=>$value) { // search all tokens in the session if(substr($key,0,12)=='requesttoken') { - if($value+$maxtime<time()) { + // check if static lifespan has expired + if($value+self::$callLifespan<time()) { // remove outdated tokens unset($_SESSION[$key]); } @@ -447,14 +463,13 @@ class OC_Util { return($token); } - /** * @brief Check an ajax get/post call if the request token is valid. * @return boolean False if request token is not set or is invalid. + * @see OC_Util::$callLifespan + * @see OC_Util::calLRegister() */ public static function isCallRegistered() { - //mamimum time before token exires - $maxtime=(60*60); // 1 hour if(isset($_GET['requesttoken'])) { $token=$_GET['requesttoken']; }elseif(isset($_POST['requesttoken'])) { @@ -467,7 +482,8 @@ class OC_Util { } if(isset($_SESSION['requesttoken-'.$token])) { $timestamp=$_SESSION['requesttoken-'.$token]; - if($timestamp+$maxtime<time()) { + // check if static lifespan has expired + if($timestamp+self::$callLifespan<time()) { return false; }else{ //token valid |