Version in base suite: 1.1.8-1 Base version: davical_1.1.8-1 Target version: davical_1.1.8-1+deb10u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/d/davical/davical_1.1.8-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/d/davical/davical_1.1.8-1+deb10u1.dsc changelog | 8 patches/CVE-2019-18345_183456_183457 | 357 +++++++++++++++++++++++++++++++++++ patches/series | 1 3 files changed, 366 insertions(+) diff -Nru davical-1.1.8/debian/changelog davical-1.1.8/debian/changelog --- davical-1.1.8/debian/changelog 2019-01-30 21:52:41.000000000 +0000 +++ davical-1.1.8/debian/changelog 2019-12-11 17:08:40.000000000 +0000 @@ -1,3 +1,11 @@ +davical (1.1.8-1+deb10u1) buster-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 Thu, 12 Dec 2019 01:08:40 +0800 + davical (1.1.8-1) unstable; urgency=medium * New upstream release diff -Nru davical-1.1.8/debian/patches/CVE-2019-18345_183456_183457 davical-1.1.8/debian/patches/CVE-2019-18345_183456_183457 --- davical-1.1.8/debian/patches/CVE-2019-18345_183456_183457 1970-01-01 00:00:00.000000000 +0000 +++ davical-1.1.8/debian/patches/CVE-2019-18345_183456_183457 2019-12-11 17:08:40.000000000 +0000 @@ -0,0 +1,357 @@ +From: Florian Schlichting +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 +Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=946343 + +diff --git a/htdocs/admin.php b/htdocs/admin.php +index b48b8558..3efe4b8a 100644 +--- a/htdocs/admin.php ++++ b/htdocs/admin.php +@@ -1,4 +1,5 @@ + 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); +diff --git a/htdocs/always.php b/htdocs/always.php +index 3e457bee..cd223e7d 100644 +--- 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("&", "&", 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); +diff --git a/inc/csrf_tokens.php b/inc/csrf_tokens.php +new file mode 100644 +index 00000000..9d05ec4e +--- /dev/null ++++ b/inc/csrf_tokens.php +@@ -0,0 +1,119 @@ ++')) { ++ 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("", 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 +diff --git a/inc/interactive-page.php b/inc/interactive-page.php +index 86c88898..c0e87389 100644 +--- a/inc/interactive-page.php ++++ b/inc/interactive-page.php +@@ -20,6 +20,9 @@ if ( isset($_SERVER['SCRIPT_NAME']) ) { + if ( $wiki_help == 'admin' ) { + $wiki_help .= '/' . $_GET['t'] . '/' . $_GET['action']; + } ++ ++ $wiki_help = htmlspecialchars($wiki_help); ++ + $wiki_help = 'w/Help/'.$wiki_help; + } + +diff --git a/inc/ui/collection-edit.php b/inc/ui/collection-edit.php +index 4fa778c9..81bffb0b 100644 +--- a/inc/ui/collection-edit.php ++++ b/inc/ui/collection-edit.php +@@ -1,4 +1,5 @@ + 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 = '@@@@@@@@@@'; +@@ -273,6 +280,7 @@ EOPRIV; + $submit_row = ''; + } + ++$csrf_field = getCsrfField(); + $id = $editor->Value('collection_id'); + $template = << $prompt_description: ##description.textarea.78x6## + $submit_row + ++$csrf_field + +