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

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



Hi Florian!

Thanks for the patch.  I already prepared a davical update some days
ago, but I will double-check that this set of changes is included.  I
intend to wrap up my work today/tomorrow and get a jessie update of
davical published.

Regards,

-Roberto

On Fri, Dec 13, 2019 at 09:59:52AM +0100, Florian Schlichting wrote:
> 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
> 

-- 
Roberto C. Sánchez
http://people.connexer.com/~roberto
http://www.connexer.com


Reply to: