From 037af6890fe6fdb84a08d3c86083e847c90ec0ad Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 22 Oct 2013 08:17:26 -0400
Subject: [PATCH] Fix vulnerability in handling _session argument of utils/save-prefs (#1489382)

---
 program/lib/Roundcube/rcube_session.php |   72 +++++++++++++++++++++++++++++++++---
 1 files changed, 66 insertions(+), 6 deletions(-)

diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php
index 1aa5d58..ee4db6e 100644
--- a/program/lib/Roundcube/rcube_session.php
+++ b/program/lib/Roundcube/rcube_session.php
@@ -32,6 +32,7 @@
     private $ip;
     private $start;
     private $changed;
+    private $reloaded = false;
     private $unsets = array();
     private $gc_handlers = array();
     private $cookiename = 'roundcube_sessauth';
@@ -200,8 +201,18 @@
         if ($oldvars !== null) {
             $a_oldvars = $this->unserialize($oldvars);
             if (is_array($a_oldvars)) {
-                foreach ((array)$this->unsets as $k)
-                    unset($a_oldvars[$k]);
+                // remove unset keys on oldvars
+                foreach ((array)$this->unsets as $var) {
+                    if (isset($a_oldvars[$var])) {
+                        unset($a_oldvars[$var]);
+                    }
+                    else {
+                        $path = explode('.', $var);
+                        $k = array_pop($path);
+                        $node = &$this->get_node($path, $a_oldvars);
+                        unset($node[$k]);
+                    }
+                }
 
                 $newvars = $this->serialize(array_merge(
                     (array)$a_oldvars, (array)$this->unserialize($vars)));
@@ -299,9 +310,9 @@
 
         $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars;
 
-        if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2) {
+        if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 3) {
             return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)),
-                MEMCACHE_COMPRESSED, $this->lifetime);
+                MEMCACHE_COMPRESSED, $this->lifetime + 60);
         }
 
         return true;
@@ -371,9 +382,32 @@
 
 
     /**
+     * Append the given value to the certain node in the session data array
+     *
+     * @param string Path denoting the session variable where to append the value
+     * @param string Key name under which to append the new value (use null for appending to an indexed list)
+     * @param mixed  Value to append to the session data array
+     */
+    public function append($path, $key, $value)
+    {
+        // re-read session data from DB because it might be outdated
+        if (!$this->reloaded && microtime(true) - $this->start > 0.5) {
+            $this->reload();
+            $this->reloaded = true;
+            $this->start = microtime(true);
+        }
+
+        $node = &$this->get_node(explode('.', $path), $_SESSION);
+
+        if ($key !== null) $node[$key] = $value;
+        else               $node[] = $value;
+    }
+
+
+    /**
      * Unset a session variable
      *
-     * @param string Varibale name
+     * @param string Variable name (can be a path denoting a certain node in the session array, e.g. compose.attachments.5)
      * @return boolean True on success
      */
     public function remove($var=null)
@@ -383,7 +417,16 @@
         }
 
         $this->unsets[] = $var;
-        unset($_SESSION[$var]);
+
+        if (isset($_SESSION[$var])) {
+            unset($_SESSION[$var]);
+        }
+        else {
+            $path = explode('.', $var);
+            $key = array_pop($path);
+            $node = &$this->get_node($path, $_SESSION);
+            unset($node[$key]);
+        }
 
         return true;
     }
@@ -415,6 +458,23 @@
             session_decode($data);
     }
 
+    /**
+     * Returns a reference to the node in data array referenced by the given path.
+     * e.g. ['compose','attachments'] will return $_SESSION['compose']['attachments']
+     */
+    private function &get_node($path, &$data_arr)
+    {
+        $node = &$data_arr;
+        if (!empty($path)) {
+            foreach ((array)$path as $key) {
+                if (!isset($node[$key]))
+                    $node[$key] = array();
+                $node = &$node[$key];
+            }
+        }
+
+        return $node;
+    }
 
     /**
      * Serialize session data

--
Gitblit v1.9.1