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 <f...@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 <f...@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.gij...@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("&", "&", > 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## > ##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