[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#946343: davical: CVE-2019-18345 CVE-2019-18346 CVE-2019-18347



Dear LTS team,

I'm not sure if you're interested in publishing a security update for
davical, but just in case I'm including the diff below (the patch is
the same as for buster and stretch, but adjusted for the much older
version in jessie). Note that I'll be on vacation from tomorrow and
largely AFK for 10-14 days.

Florian

-- 

diff --git a/debian/changelog b/debian/changelog
index 2277ec8f..87e07cd2 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+davical (1.1.3.1-1+deb8u1) jessie-security; urgency=high
+
+  * Fix three cross-site scripting and cross-site request forgery
+    vulnerabilities in the web administration front-end:
+    CVE-2019-18345 CVE-2019-18346 CVE-2019-18347 (closes: #946343)
+
+ -- Florian Schlichting <fsfs@debian.org>  Fri, 13 Dec 2019 16:46:33 +0800
+
 davical (1.1.3.1-1) unstable; urgency=medium
 
   * fix a critical typo in htdocs/always.php
diff --git a/debian/patches/CVE-2019-18345_183456_183457 b/debian/patches/CVE-2019-18345_183456_183457
new file mode 100644
index 00000000..9a8dc49b
--- /dev/null
+++ b/debian/patches/CVE-2019-18345_183456_183457
@@ -0,0 +1,332 @@
+From: Florian Schlichting <fsfs@debian.org>
+Subject: fix CVE-2019-18345 CVE-2019-18346 CVE-2019-18347
+ This combines four upstream commits contained in davical 1.1.9.2:
+  86a8ec530 Added CSRF to the application, Mitigated the XSS vulnerabilities reported by HackDefense
+  1a917b30e Addressed comments made by @puck42
+  c8a0ca453 Fix CSRF not being checked in collection-edit.php
+  e2c6b927c HTTP_REFERER will usually be unset for caldav requests, prevent "Undefined index" warnings
+ The fix was developed by nielsvangijzen <n.van.gijzen@gmail.com>
+Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=946343
+
+--- a/htdocs/admin.php
++++ b/htdocs/admin.php
+@@ -1,4 +1,5 @@
+ <?php
++
+ require_once('./always.php');
+ require_once('classEditor.php');
+ require_once('classBrowser.php');
+@@ -21,7 +22,12 @@
+ $page_elements = array();
+ $code_file = sprintf( 'ui/%s-%s.php', $component, $action );
+ if ( ! @include_once( $code_file ) ) {
+-  $c->messages[] = sprintf('No page found to %s %s%s%s', $action, ($action == 'browse' ? '' : 'a '), $component, ($action == 'browse' ? 's' : ''));
++  $c->messages[] = sprintf(
++      'No page found to %s %s%s%s',
++      htmlspecialchars($action),
++      ($action == 'browse' ? '' : 'a '), $component,
++      ($action == 'browse' ? 's' : '')
++  );
+   include('page-header.php');
+   include('page-footer.php');
+   @ob_flush(); exit(0);
+--- a/htdocs/always.php
++++ b/htdocs/always.php
+@@ -8,6 +8,47 @@
+ 
+ if ( preg_match('{/always.php$}', $_SERVER['SCRIPT_NAME'] ) ) header('Location: index.php');
+ 
++// XSS Protection
++function filter_post(&$val, $index) {
++    if(in_array($index, ["newpass1", "newpass2"])) return;
++    switch (gettype($val)) {
++        case "string":
++            $val = htmlspecialchars($val);
++            break;
++
++        case "array":
++            array_walk_recursive($val, function(&$v) {
++                if (gettype($v) == "string") {
++                    $v = htmlspecialchars($v);
++                }
++            });
++            break;
++    }
++}
++
++function clean_get() {
++    $temp = [];
++
++    foreach($_GET as $key => $value) {
++        // XSS is possible in both key and values
++        $k = htmlspecialchars($key);
++        $v = htmlspecialchars($value);
++        $temp[$k] = $v;
++    }
++
++    return $temp;
++}
++
++// Before anything else is executed we filter all the user input, a lot of code in this project
++// relies on variables that are easily manipulated by the user. These lines and functions filter all those variables.
++if(isset($_POST)) array_walk($_POST, 'filter_post');
++$_GET = clean_get();
++$_SERVER['REQUEST_URI'] = str_replace("&amp;", "&", htmlspecialchars($_SERVER['REQUEST_URI']));
++$_SERVER['HTTP_REFERER'] = htmlspecialchars(@$_SERVER['HTTP_REFERER']);
++
++
++
+ // Ensure the configuration starts out as an empty object.
+ $c = (object) array();
+ $c->script_start_time = microtime(true);
+--- /dev/null
++++ b/inc/csrf_tokens.php
+@@ -0,0 +1,119 @@
++<?php
++
++/**
++ * Update the CSRF token
++ */
++function updateCsrf() {
++    if(!sessionExists()) {
++        session_start();
++    }
++
++    $_SESSION['csrf_token'] = generateCsrf();
++}
++
++/**
++ * Check whether a session is currently active
++ * @return bool
++ */
++function sessionExists() {
++    if (version_compare(phpversion(), '5.4.0', '>')) {
++        return session_id() !== '';
++    } else {
++        return session_status() === PHP_SESSION_ACTIVE;
++    }
++}
++
++/**
++ * Generate a CSRF token, it chooses from 3 different functions based on PHP version and modules
++ * @return bool|string
++ */
++function generateCsrf() {
++    if (version_compare(phpversion(), '7.0.0', '>=')) {
++        $random = generateRandom();
++        if($random !== false) return $random;
++    }
++
++    if (function_exists('mcrypt_create_iv')) {
++        return generateMcrypt();
++    }
++
++    return generateOpenssl();
++}
++
++/**
++ * Generate a random string using the PHP built in function random_bytes
++ * @version 7.0.0
++ * @return bool|string
++ */
++function generateRandom() {
++    try {
++        return bin2hex(random_bytes(32));
++    } catch (Exception $e) {
++        return false;
++    }
++}
++
++/**
++ * Generate a random string using MCRYPT
++ * @return string
++ */
++function generateMcrypt() {
++    return bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
++}
++
++/**
++ * Generate a random string using OpenSSL
++ * @return string
++ */
++function generateOpenssl() {
++    return bin2hex(openssl_random_pseudo_bytes(32));
++}
++
++/**
++ * Checks for session and the existence of a key
++ * after ensuring both are present it returns the
++ * current CSRF token
++ * @return string
++ */
++function getCsrf() {
++    if(!sessionExists()) {
++        session_start();
++    }
++
++    if(!array_key_exists('csrf_token', $_SESSION)) {
++        updateCsrf();
++    }
++
++    return $_SESSION['csrf_token'];
++}
++
++/**
++ * Get a hidden CSRF input field to be used in forms
++ * @return string
++ */
++function getCsrfField() {
++    return sprintf("<input type=\"hidden\" name=\"csrf_token\" value=\"%s\">", getCsrf());
++}
++
++/**
++ * Verify a given CSRF token
++ * @param $csrf_token
++ * @return bool
++ */
++function verifyCsrf($csrf_token) {
++    $current_csrf = getCsrf();
++    // Prefer hash_equals over === because the latter is vulnerable to timing attacks
++    if(function_exists('hash_equals')) {
++        return hash_equals($current_csrf, $csrf_token);
++    }
++
++    return $current_csrf === $csrf_token;
++}
++
++/**
++ * Uses the global $_POST variable to check if the CSRF token is valid
++ * @return bool
++ */
++function verifyCsrfPost() {
++    return (isset($_POST['csrf_token']) && verifyCsrf($_POST['csrf_token']));
++}
+\ No newline at end of file
+--- a/inc/interactive-page.php
++++ b/inc/interactive-page.php
+@@ -12,6 +12,9 @@
+   if ( $wiki_help == 'admin' ) {
+     $wiki_help .= '/' . $_GET['t'] . '/' . $_GET['action'];
+   }
++
++  $wiki_help = htmlspecialchars($wiki_help);
++
+   $wiki_help = 'w/Help/'.$wiki_help;
+ }
+ 
+--- a/inc/ui/collection-edit.php
++++ b/inc/ui/collection-edit.php
+@@ -1,4 +1,5 @@
+ <?php
++require_once("csrf_tokens.php");
+ 
+ // Editor component for collections
+ $editor = new Editor(translate('Collection'), 'collection');
+@@ -66,6 +67,12 @@
+   $can_write_collection = ($session->AllowedTo('Admin') || (bindec($permissions->priv) & privilege_to_bits('DAV::bind')) );
+ }
+ 
++// Verify CSRF token
++if($_SERVER['REQUEST_METHOD'] === "POST" && !verifyCsrfPost()) {
++    $c->messages[] = i18n("A valid CSRF token must be provided");
++    $can_write_collection = false;
++}
++
+ dbg_error_log('collection-edit', "Can write collection: %s", ($can_write_collection? 'yes' : 'no') );
+ 
+ $pwstars = '@@@@@@@@@@';
+@@ -235,6 +242,7 @@
+ $btn_ss = htmlspecialchars(translate('Schedule Send'));    $btn_ss_title = htmlspecialchars(translate('Privileges to delegate scheduling decisions'));
+ 
+ 
++$csrf_field = getCsrfField();
+ $id = $editor->Value('collection_id');
+ $template = <<<EOTEMPLATE
+ ##form##
+@@ -358,6 +366,7 @@
+  <tr> <th class="right">$prompt_description:</th>      <td class="left">##description.textarea.78x6##</td> </tr>
+  <tr> <th class="right"></th>                   <td class="left" colspan="2">##submit##</td> </tr>
+ </table>
++$csrf_field
+ </form>
+ <script language="javascript">
+ toggle_enabled('fld_is_calendar','=fld_timezone','=fld_schedule_transp','!fld_is_addressbook');
+@@ -427,9 +436,11 @@
+     $orig_to_id = $row_data->to_principal;
+     $form_id = $grantrow->Id();
+     $form_url = preg_replace( '#&(edit|delete)_grant=\d+#', '', $_SERVER['REQUEST_URI'] );
++    $csrf_field = getCsrfField();
+ 
+     $template = <<<EOTEMPLATE
+ <form method="POST" enctype="multipart/form-data" id="form_$form_id" action="$form_url">
++  $csrf_field
+   <td class="left" colspan="2"><input type="hidden" name="id" value="$id"><input type="hidden" name="orig_to_id" value="$orig_to_id">##to_principal.select##</td>
+   <td class="left" colspan="2">
+ <input type="button" value="$btn_all" class="submit" title="$btn_all_title" onclick="toggle_privileges('grant_privileges', 'all', 'form_$form_id');">
+--- a/inc/ui/principal-edit.php
++++ b/inc/ui/principal-edit.php
+@@ -1,4 +1,5 @@
+ <?php
++require_once("csrf_tokens.php");
+ 
+ param_to_global('id', 'int', 'old_id', 'principal_id' );
+ 
+@@ -170,6 +171,13 @@
+   $editor->AddAttribute( 'fullname', 'title', translate("The full name for this person, group or other type of principal.") );
+   $editor->SetWhere( 'principal_id='.$id );
+   
++  if($_SERVER['REQUEST_METHOD'] === "POST" && !verifyCsrfPost()) {
++      $c->messages[] = i18n("A valid CSRF token must be provided");
++      $can_write_principal = false;
++  }
++
++  $csrf_field = getCsrfField();
++
+   $editor->AddField('is_admin', 'EXISTS( SELECT 1 FROM role_member WHERE role_no = 1 AND role_member.user_no = dav_principal.user_no )' );
+   $editor->AddAttribute('is_admin', 'title', translate('An "Administrator" user has full rights to the whole DAViCal System'));
+   
+@@ -367,6 +375,7 @@
+  <tr> <th class="right" style="white-space:normal;">$prompt_privileges:</th><td class="left">$privs_html</td> </tr>
+  <tr> <th class="right"></th>                   <td class="left" colspan="2">##submit##</td> </tr>
+ </table>
++ $csrf_field 
+ </form>
+ EOTEMPLATE;
+ 
+@@ -516,9 +525,11 @@
+   global $id, $grouprow;
+ 
+   $form_url = preg_replace( '#&(edit|delete)_group=\d+#', '', $_SERVER['REQUEST_URI'] );
++  $csrf_field = getCsrfField();
+ 
+   $template = <<<EOTEMPLATE
+ <form method="POST" enctype="multipart/form-data" id="add_group" action="$form_url">
++  $csrf_field
+   <td class="left"><input type="hidden" name="id" value="$id"></td>
+   <td class="left" colspan="3">##member_id.select## &nbsp; ##Add.submit##</td>
+   <td class="center"></td>
+@@ -631,8 +642,11 @@
+   $form_id = $grantrow->Id();
+   $form_url = preg_replace( '#&(edit|delete)_grant=\d+#', '', $_SERVER['REQUEST_URI'] );
+ 
++  $csrf_field = getCsrfField();
++
+   $template = <<<EOTEMPLATE
+ <form method="POST" enctype="multipart/form-data" id="form_$form_id" action="$form_url">
++  $csrf_field
+   <td class="left" colspan="2"><input type="hidden" name="id" value="$id"><input type="hidden" name="orig_to_id" value="$orig_to_id">##to_principal.select##</td>
+   <td class="left" colspan="2">$privs_html</td>
+   <td class="center">##submit##</td>
+@@ -759,9 +773,11 @@
+   $form_id = $ticketrow->Id();
+   $ticket_id = $row_data->ticket_id;
+   $form_url = preg_replace( '#&(edit|delete)_[a-z]+=\d+#', '', $_SERVER['REQUEST_URI'] );
++  $csrf_field = getCsrfField();
+ 
+   $template = <<<EOTEMPLATE
+ <form method="POST" enctype="multipart/form-data" id="form_$form_id" action="$form_url">
++  $csrf_field
+   <td class="left">$ticket_id<input type="hidden" name="id" value="$id"><input type="hidden" name="ticket_id" value="$ticket_id"></td>
+   <td class="left"><input type="text" name="target" value="$row_data->target"></td>
+   <td class="left"><input type="text" name="expires" value="$row_data->expires" size="10"></td>
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 00000000..051ba565
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1 @@
+CVE-2019-18345_183456_183457


Reply to: