From 197203727417a03d87053a47e5aa5175a76e3e0b Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 17 Oct 2013 04:24:53 -0400
Subject: [PATCH] Fix vulnerability in handling _session argument of utils/save-prefs (#1489382)

---
 program/include/main.inc |  148 +++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 117 insertions(+), 31 deletions(-)

diff --git a/program/include/main.inc b/program/include/main.inc
index 25940bd..7a3e1c0 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -169,14 +169,16 @@
   // get target timestamp
   $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
 
-  $db->query("DELETE FROM ".get_table_name('cache_messages')
+  if ($rcmail->config->get('messages_cache') || $rcmail->config->get('enable_caching')) {
+    $db->query("DELETE FROM ".get_table_name('cache_messages')
         ." WHERE changed < " . $db->fromunixtime($ts));
 
-  $db->query("DELETE FROM ".get_table_name('cache_index')
+    $db->query("DELETE FROM ".get_table_name('cache_index')
         ." WHERE changed < " . $db->fromunixtime($ts));
 
-  $db->query("DELETE FROM ".get_table_name('cache_thread')
+    $db->query("DELETE FROM ".get_table_name('cache_thread')
         ." WHERE changed < " . $db->fromunixtime($ts));
+  }
 
   $db->query("DELETE FROM ".get_table_name('cache')
         ." WHERE created < " . $db->fromunixtime($ts));
@@ -640,20 +642,23 @@
 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
 {
   $value = NULL;
-  
-  if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
-    $value = $_GET[$fname];
-  else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
-    $value = $_POST[$fname];
-  else if ($source==RCUBE_INPUT_GPC)
-    {
+
+  if ($source == RCUBE_INPUT_GET) {
+    if (isset($_GET[$fname]))
+      $value = $_GET[$fname];
+  }
+  else if ($source == RCUBE_INPUT_POST) {
+    if (isset($_POST[$fname]))
+      $value = $_POST[$fname];
+  }
+  else if ($source == RCUBE_INPUT_GPC) {
     if (isset($_POST[$fname]))
       $value = $_POST[$fname];
     else if (isset($_GET[$fname]))
       $value = $_GET[$fname];
     else if (isset($_COOKIE[$fname]))
       $value = $_COOKIE[$fname];
-    }
+  }
 
   return parse_input_value($value, $allow_html, $charset);
 }
@@ -661,7 +666,7 @@
 /**
  * Parse/validate input value. See get_input_value()
  * Performs stripslashes() and charset conversion if necessary
- * 
+ *
  * @param  string   Input value
  * @param  boolean  Allow HTML tags in field value
  * @param  string   Charset to convert into
@@ -687,15 +692,21 @@
   else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
     $value = stripslashes($value);
 
-  // remove HTML tags if not allowed    
+  // remove HTML tags if not allowed
   if (!$allow_html)
     $value = strip_tags($value);
-  
+
+  $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
+
+  // remove invalid characters (#1488124)
+  if ($output_charset == 'UTF-8')
+    $value = rc_utf8_clean($value);
+
   // convert to internal charset
-  if (is_object($OUTPUT) && $charset)
-    return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
-  else
-    return $value;
+  if ($charset && $output_charset)
+    $value = rcube_charset_convert($value, $output_charset, $charset);
+
+  return $value;
 }
 
 /**
@@ -711,10 +722,10 @@
   $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
   foreach ($src as $key => $value) {
     $fname = $key[0] == '_' ? substr($key, 1) : $key;
-    if ($ignore && !preg_match("/($ignore)/", $fname))
+    if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname))
       $out[$fname] = get_input_value($key, $mode);
   }
-  
+
   return $out;
 }
 
@@ -874,23 +885,37 @@
  * @param string Container ID to use as prefix
  * @return string Modified CSS source
  */
-function rcmail_mod_css_styles($source, $container_id)
+function rcmail_mod_css_styles($source, $container_id, $allow_remote=false)
   {
   $last_pos = 0;
   $replacements = new rcube_string_replacer;
 
   // ignore the whole block if evil styles are detected
-  $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source));
-  if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped))
+  $source = rcmail_xss_entity_decode($source);
+  $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
+  $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : '');
+  if (preg_match("/$evilexpr/i", $stripped))
     return '/* evil! */';
 
-  // remove css comments (sometimes used for some ugly hacks)
-  $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
-
   // cut out all contents between { and }
-  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
-  {
-    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
+  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
+    $styles = substr($source, $pos+1, $pos2-($pos+1));
+
+    // check every line of a style block...
+    if ($allow_remote) {
+      $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
+      foreach ($a_styles as $line) {
+        $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
+        // ... and only allow strict url() values
+        if (stripos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims', $line)) {
+          $a_styles = array('/* evil! */');
+          break;
+        }
+      }
+      $styles = join(";\n", $a_styles);
+    }
+
+    $key = $replacements->add($styles);
     $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
     $last_pos = $pos+2;
   }
@@ -928,7 +953,7 @@
 {
   $out = html_entity_decode(html_entity_decode($content));
   $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
-  $out = preg_replace('#/\*.*\*/#Um', '', $out);
+  $out = preg_replace('#/\*.*\*/#Ums', '', $out);
   return $out;
 }
 
@@ -2036,7 +2061,68 @@
 
   public function callback($matches)
   {
-    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
+    return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"';
+  }
+
+  public function replace($body)
+  {
+    return preg_replace_callback(array(
+      '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui',
+      '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui',
+      ),
+      array($this, 'callback'), $body);
+  }
+
+  /**
+   * Convert paths like ../xxx to an absolute path using a base url
+   *
+   * @param string $path     Relative path
+   * @param string $base_url Base URL
+   *
+   * @return string Absolute URL
+   */
+  public static function absolute_url($path, $base_url)
+  {
+    $host_url = $base_url;
+    $abs_path = $path;
+
+    // check if path is an absolute URL
+    if (preg_match('/^[fhtps]+:\/\//', $path)) {
+      return $path;
+    }
+
+    // check if path is a content-id scheme
+    if (strpos($path, 'cid:') === 0) {
+      return $path;
+    }
+
+    // cut base_url to the last directory
+    if (strrpos($base_url, '/') > 7) {
+      $host_url = substr($base_url, 0, strpos($base_url, '/', 7));
+      $base_url = substr($base_url, 0, strrpos($base_url, '/'));
+    }
+
+    // $path is absolute
+    if ($path[0] == '/') {
+      $abs_path = $host_url.$path;
+    }
+    else {
+      // strip './' because its the same as ''
+      $path = preg_replace('/^\.\//', '', $path);
+
+      if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) {
+        foreach ($matches as $a_match) {
+          if (strrpos($base_url, '/')) {
+            $base_url = substr($base_url, 0, strrpos($base_url, '/'));
+          }
+          $path = substr($path, 3);
+        }
+      }
+
+      $abs_path = $base_url.'/'.$path;
+    }
+
+    return $abs_path;
   }
 }
 

--
Gitblit v1.9.1