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 |  105 +++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 91 insertions(+), 14 deletions(-)

diff --git a/program/include/main.inc b/program/include/main.inc
index 7147649..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));
@@ -883,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;
   }
@@ -937,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;
 }
 
@@ -2045,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