* Fixed bug #12309: It was possible to gain access to the Install Tool by only knowin...
authorErnesto Baschny <ernst@cron-it.de>
Thu, 22 Oct 2009 08:59:39 +0000 (08:59 +0000)
committerErnesto Baschny <ernst@cron-it.de>
Thu, 22 Oct 2009 08:59:39 +0000 (08:59 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@6252 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
typo3/sysext/install/mod/class.tx_install.php
typo3/sysext/install/mod/class.tx_install_session.php [new file with mode: 0644]

index b115f7f..c228321 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,7 @@
 2009-10-22  Ernesto Baschny <ernst@cron-it.de>
 
        * Fixed bug #11586: Potential SQL injection in frontend editing (thanks to Oliver Klee)
+       * Fixed bug #12309: It was possible to gain access to the Install Tool by only knowing the md5 hash of the password.
        * Fixed bug #12303: XSS vulnerability due to not proper sanitizing in function t3lib_div::quoteJSvalue (thanks to Oliver Klee)
        * Fixed bug #12304: Frame inclusion in the backend through alt_mod_frameset (thanks to Oliver Klee)
        * Fixed bug #12305: XSS vulnerability in view_help.php / tfID parameter (thanks to Oliver Klee)
index 4943bf1..23b86e0 100755 (executable)
@@ -39,7 +39,7 @@
  *
  *  162: class tx_install extends t3lib_install
  *  234:     function tx_install()
- *  318:     function checkPassword($uKey)
+ *  318:     function checkPassword()
  *  362:     function loginForm()
  *  396:     function init()
  *  574:     function stepOutput()
@@ -151,6 +151,7 @@ require_once(t3lib_extMgm::extPath('install').'updates/class.tx_coreupdates_inst
 require_once(t3lib_extMgm::extPath('install').'updates/class.tx_coreupdates_imagescols.php');
 require_once(t3lib_extMgm::extPath('install').'updates/class.tx_coreupdates_installversioning.php');
 require_once(t3lib_extMgm::extPath('install').'updates/class.tx_coreupdates_installnewsysexts.php');
+require_once(t3lib_extMgm::extPath('install') . 'mod/class.tx_install_session.php');
 
 /**
  * Install Tool module
@@ -206,6 +207,12 @@ class tx_install extends t3lib_install {
                'no_database' => 0
        );
        var $typo3temp_path='';
+       /**
+        * the session handling object
+        *
+        * @var tx_install_session
+        */
+       protected $session = NULL;
 
        var $menuitems = array(
                'config' => 'Basic Configuration',
@@ -219,7 +226,6 @@ class tx_install extends t3lib_install {
                'typo3conf_edit' => 'Edit files in typo3conf/',
                'about' => 'About'
        );
-       var $cookie_name = 'Typo3InstallTool';
        var $JSmessage = '';
 
 
@@ -296,16 +302,17 @@ class tx_install extends t3lib_install {
                        ($this->mode? '&mode=' . $this->mode : '') .
                        ($this->step? '&step=' . $this->step : '');
                $this->typo3temp_path = PATH_site.'typo3temp/';
+               if (!is_dir($this->typo3temp_path) || !is_writeable($this->typo3temp_path)) {
+                       die('Install Tool needs to write to typo3temp/. Make sure this directory is writeable by your webserver: '. $this->typo3temp_path);
+               }
 
+               $this->session = t3lib_div::makeInstance('tx_install_session');
 
-                       // ****************
-                       // Check password
-                       // ****************
-                       // Getting a unique session key, used to encode the session-access cookie later...
-               $uKey = $_COOKIE[$this->cookie_name.'_key'];
-               if (!$uKey)     {
-                       $uKey = md5(uniqid(microtime()));
-                       SetCookie($this->cookie_name.'_key', $uKey, 0, t3lib_div::getIndpEnv('TYPO3_SITE_PATH'));       // Cookie is set
+                       // *******************
+                       // Check authorization
+                       // *******************
+               if (!$this->session->hasSession()) {
+                       $this->session->startSession();
 
                        $this->JSmessage='SECURITY:
 Make sure to protect the Install Tool with another password than "joh316".
@@ -318,13 +325,11 @@ On behalf of PHP we regret this inconvenience.
 
 BTW: This Install Tool will only work if cookies are accepted by your web browser. If this dialog pops up over and over again you didn\'t enable cookies.
 ';
-
                }
-                       // Check if the password from TYPO3_CONF_VARS combined with uKey matches the sKey cookie. If not, ask for password.
-               $sKey = $_COOKIE[$this->cookie_name];
 
-               if (md5($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'].'|'.$uKey) == $sKey || $this->checkPassword($uKey))    {
+               if ($this->session->isAuthorized() || $this->checkPassword())   {
                        $this->passwordOK=1;
+                       $this->session->refreshSession();
 
                        $enableInstallToolFile = PATH_typo3conf . 'ENABLE_INSTALL_TOOL';
                        if (is_file ($enableInstallToolFile)) {
@@ -341,17 +346,18 @@ BTW: This Install Tool will only work if cookies are accepted by your web browse
        }
 
        /**
-        * Returns true if submitted password is ok. Else displays a form in which to enter password.
+        * Returns true if submitted password is ok.
+        *
+        * If password is ok, set session as "authorized".
         *
-        * @param       [type]          $uKey: ...
-        * @return      bool            whether the submitted password is ok
+        * @return boolean true if the submitted password was ok and session was
+        *                 authorized, false otherwise
         */
-       function checkPassword($uKey)   {
+       function checkPassword() {
                $p = t3lib_div::_GP('password');
 
                if ($p && md5($p)==$GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'])    {
-                       $sKey = md5($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'].'|'.$uKey);
-                       SetCookie($this->cookie_name, $sKey, 0, t3lib_div::getIndpEnv('TYPO3_SITE_PATH'));
+                       $this->session->setAuthorized();
 
                                // Sending warning email
                        $wEmail = $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'];
@@ -414,7 +420,10 @@ REMOTE_ADDR was '".t3lib_div::getIndpEnv('REMOTE_ADDR')."' (".t3lib_div::getIndp
                        //-->
                        </script>';
 
-               $this->message('Password', 'Enter the Install Tool Password', $content,3);
+               if (!$this->session->isAuthorized() && $this->session->isExpired()) {
+                       $this->message('Password', 'Your install tool session has expired', '', 3);
+               }
+               $this->message('Password', 'Enter the Install Tool Password', $content, 2);
                $this->output($this->outputWrapper($this->printAll()));
        }
 
diff --git a/typo3/sysext/install/mod/class.tx_install_session.php b/typo3/sysext/install/mod/class.tx_install_session.php
new file mode 100644 (file)
index 0000000..82a7eb5
--- /dev/null
@@ -0,0 +1,363 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Ernesto Baschny <ernst@cron-it.de>
+*  All rights reserved
+*
+*  This script is part of the TYPO3 project. The TYPO3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 2 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*  A copy is found in the textfile GPL.txt and important notices to the license
+*  from the author is found in LICENSE.txt distributed with these scripts.
+*
+*
+*  This script 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 General Public License for more details.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * Secure session handling for the install tool.
+ *
+ * @author     Ernesto Baschny <ernst@cron-it.de>
+ *
+ * @package TYPO3
+ * @subpackage tx_install
+ *
+ * @version $Id$
+ */
+class tx_install_session {
+
+       /**
+        * The path to our typo3temp (where we can write our sessions). Set in the
+        * constructor.
+        *
+        * @var string
+        */
+       private $typo3tempPath;
+
+       /**
+        * Path where to store our session files in typo3temp. %s will be
+        * non-guesseable.
+        *
+        * @var string
+        */
+       private $sessionPath = 'sessions%s';
+
+       /**
+        * the cookie to store the session ID of the install tool
+        *
+        * @var string
+        */
+       private $cookieName = 'Typo3InstallTool';
+
+       /**
+        * time (minutes) to expire an ununsed session
+        *
+        * @var integer
+        */
+       private $expireTimeInMinutes = 60;
+
+       /**
+        * time (minutes) to generate a new session id for our current session
+        *
+        * @var integer
+        */
+       private $regenerateSessionIdTime = 5;
+
+       /**
+        * Constructor. Starts PHP session handling in our own private store
+        *
+        * Side-effect: might set a cookie, so must be called before any other output.
+        */
+       public function __construct() {
+               $this->typo3tempPath = PATH_site . 'typo3temp/';
+
+               // Start our PHP session early so that hasSession() works
+               $sessionSavePath = $this->getSessionSavePath();
+               if (!is_dir($sessionSavePath)) {
+                       if (!t3lib_div::mkdir($sessionSavePath)) {
+                               die('Could not create session folder in typo3temp/. Make sure it is writeable!');
+                       }
+                       t3lib_div::writeFile($sessionSavePath.'/.htaccess', 'Order deny, allow'."\n".'Deny from all'."\n");
+                       $indexContent = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">';
+                       $indexContent .= '<HTML><HEAD<TITLE></TITLE><META http-equiv=Refresh Content="0; Url=../../">';
+                       $indexContent .= '</HEAD></HTML>';
+                       t3lib_div::writeFile($sessionSavePath.'/index.html', $indexContent);
+               }
+               // Register our "save" session handler
+               session_set_save_handler(
+                       array($this, 'open'),
+                       array($this, 'close'),
+                       array($this, 'read'),
+                       array($this, 'write'),
+                       array($this, 'destroy'),
+                       array($this, 'gc')
+               );
+               session_save_path($sessionSavePath);
+               session_name($this->cookieName);
+               ini_set('session.cookie_path', t3lib_div::getIndpEnv('TYPO3_SITE_PATH'));
+               // Always call the garbage collector to clean up stale session files
+               ini_set('session.gc_probability', 100);
+               ini_set('session.gc_divisor', 100);
+               ini_set('session.gc_maxlifetime', $this->expireTimeInMinutes*2*60);
+               if (version_compare(phpversion(), '5.2', '<')) {
+                       ini_set('session.cookie_httponly', TRUE);
+               }
+               session_start();
+       }
+
+       /**
+        * Returns the path where to store our session files
+        */
+       private function getSessionSavePath() {
+               return sprintf(
+                       $this->typo3tempPath . '/' . $this->sessionPath,
+                       md5(
+                               'session:' .
+                                       $GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword']
+                       )
+               );
+       }
+
+       /**
+        * Starts a new session
+        *
+        * @return string The session ID
+        */
+       public function startSession() {
+               $_SESSION['created'] = time();
+               return session_id();
+       }
+
+       /**
+        * Generates a new session ID and sends it to the client.
+        *
+        * Also moves session information from the old session to the new one
+        * (in PHP 5.1 or later)
+        *
+        * @return string the new session ID
+        */
+       private function renewSession() {
+               if (version_compare(phpversion(), '5.1', '<')) {
+                       session_regenerate_id(TRUE);
+               } else {
+                       session_regenerate_id();
+               }
+               return session_id();
+       }
+
+       /**
+        * Checks whether we already have an active session.
+        *
+        * @return boolean true if there is an active session, false otherwise
+        */
+       public function hasSession() {
+               return (isset($_SESSION['created']));
+       }
+
+       /**
+        * Returns the session ID of the running session.
+        *
+        * @return string the session ID
+        */
+       public function getSessionId() {
+               return session_id();
+       }
+
+       /**
+        * Returns a session hash, which can only be calculated by the server.
+        * Used to store our session files without exposing the session ID.
+        *
+        * @param string An alternative session ID. Defaults to our current session ID
+        *
+        * @return string the session hash
+        */
+       private function getSessionHash($sessionId = '') {
+               if (!$sessionId) {
+                       $sessionId = $this->getSessionId();
+               }
+               return md5($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'].'|'.$sessionId);
+       }
+
+       /**
+        * Marks this session as an "authorized" one (login successful).
+        * Should only be called if:
+        * a) we have a valid session running
+        * b) the "password" or some other authorization mechanism really matched
+        *
+        * @return void
+        */
+       public function setAuthorized() {
+               $_SESSION['authorized'] = TRUE;
+               $_SESSION['lastSessionId'] = time();
+               $_SESSION['tstamp'] = time();
+               $_SESSION['expires'] = (time() + ($this->expireTimeInMinutes*60));
+       }
+
+       /**
+        * Check if we have an already authorized session
+        *
+        * @return boolean True if this session has been authorized before (by a correct password)
+        */
+       public function isAuthorized() {
+               if (!$_SESSION['authorized']) {
+                       return FALSE;
+               }
+               if ($_SESSION['expires'] < time()) {
+                       // This session has already expired
+                       return FALSE;
+               }
+               return TRUE;
+       }
+
+       /**
+        * Check if our session is expired.
+        * Useful only right after a false "isAuthorized" to see if this is the
+        * reason for not being authorized anymore.
+        *
+        * @return boolean True if an authorized session exists, but is expired
+        */
+       public function isExpired() {
+               if (!$_SESSION['authorized']) {
+                       // Session never existed, means it is not "expired"
+                       return FALSE;
+               }
+               if ($_SESSION['expires'] < time()) {
+                       // This session was authorized before, but has expired
+                       return TRUE;
+               }
+               return FALSE;
+       }
+
+       /**
+        * Refreshes our session information, rising the expire time.
+        * Also generates a new session ID every 5 minutes to minimize the risk of
+        * session hijacking.
+        *
+        * @return void
+        */
+       public function refreshSession() {
+               $_SESSION['tstamp'] = time();
+               $_SESSION['expires'] = time() + ($this->expireTimeInMinutes*60);
+               if (time() > $_SESSION['lastSessionId']+$this->regenerateSessionIdTime*60) {
+                       // Renew our session ID
+                       $_SESSION['lastSessionId'] = time();
+                       $this->renewSession();
+               }
+       }
+
+
+       /*************************
+        *
+        * PHP session handling with "secure" session files (hashed session id)
+        * see http://www.php.net/manual/en/function.session-set-save-handler.php
+        *
+        *************************/
+
+       /**
+        * Returns the file where to store our session data
+        *
+        * @return string A filename
+        */
+       private function getSessionFile($id) {
+               $sessionSavePath = $this->getSessionSavePath();
+               return $sessionSavePath . '/hash_' . $this->getSessionHash($id);
+       }
+
+       /**
+        * Open function. See @session_set_save_handler
+        *
+        * @param string $savePath
+        * @param string $sessionName
+        * @return boolean
+        */
+       public function open($savePath, $sessionName) {
+               return TRUE;
+       }
+
+       /**
+        * Close function. See @session_set_save_handler
+        *
+        * @return boolean
+        */
+       public function close() {
+               return TRUE;
+       }
+
+       /**
+        * Read session data. See @session_set_save_handler
+        *
+        * @param string The session id
+        *
+        * @return string
+        */
+       public function read($id) {
+               $sessionFile = $this->getSessionFile($id);
+               return (string) @file_get_contents($sessionFile);
+       }
+
+       /**
+        * Write session data. See @session_set_save_handler
+        *
+        * @param string The session id
+        * @param string The data to be stored
+        *
+        * @return boolean
+        */
+       public function write($id, $sessionData) {
+               $sessionFile = $this->getSessionFile($id);
+               if ($fp = @fopen($sessionFile, 'w')) {
+                       $return = fwrite($fp, $sessionData);
+                       fclose($fp);
+                       return $return;
+               } else {
+                       return FALSE;
+               }
+       }
+
+       /**
+        * Destroys one session. See @session_set_save_handler
+        *
+        * @param string The session id
+        *
+        * @return string
+        */
+       public function destroy($id) {
+               $sessionFile = $this->getSessionFile($id);
+               return(@unlink($sessionFile));
+       }
+
+       /**
+        * Garbage collect session info. See @session_set_save_handler
+        *
+        * @param integer The setting of session.gc_maxlifetime
+        *
+        * @return string
+        */
+       public function gc($maxLifeTime) {
+               $sessionSavePath = $this->getSessionSavePath();
+               foreach (glob($sessionSavePath . '/hash_*') as $filename) {
+                       if (filemtime($filename) + ($this->expireTimeInMinutes*60) < time()) {
+                               @unlink($filename);
+                       }
+               }
+               return TRUE;
+       }
+
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/install/mod/class.tx_install_session.php'])   {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/install/mod/class.tx_install_session.php']);
+}
+
+?>
\ No newline at end of file