Commit 86a8ec53 authored by Niels's avatar Niels

Added CSRF to the application (took in account backwards compatibility)

Mitigated the XSS vulnerabilities reported by HackDefense
Advisories for said vulnerabilities can be found here:
https://hackdefense.com/publications/cve-2019-18345-davical-caldav-server-vulnerability
https://hackdefense.com/publications/cve-2019-18346-davical-caldav-server-vulnerability
https://hackdefense.com/publications/cve-2019-18347-davical-caldav-server-vulnerability
parent 710bc6cc
<?php
require_once('./always.php');
require_once('classEditor.php');
require_once('classBrowser.php');
......@@ -25,7 +26,12 @@ require_once('interactive-page.php');
$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);
......
......@@ -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);
......
<?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 false;
}
/**
* 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
......@@ -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;
}
......
<?php
require_once("csrf_tokens.php");
// Editor component for collections
$editor = new Editor(translate('Collection'), 'collection');
......@@ -273,6 +274,7 @@ EOPRIV;
$submit_row = '';
}
$csrf_field = getCsrfField();
$id = $editor->Value('collection_id');
$template = <<<EOTEMPLATE
##form##
......@@ -384,6 +386,7 @@ label.privilege {
<tr> <th class="right">$prompt_description:</th> <td class="left">##description.textarea.78x6##</td> </tr>
$submit_row
</table>
$csrf_field
</form>
<script language="javascript">
toggle_enabled('fld_is_calendar','=fld_timezone','=fld_schedule_transp','!fld_is_addressbook');
......@@ -453,9 +456,11 @@ if ( $editor->Available() ) {
$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');">
......
<?php
require_once("csrf_tokens.php");
param_to_global('id', 'int', 'old_id', 'principal_id' );
......@@ -181,6 +182,13 @@ function principal_editor() {
$editor->AddAttribute( 'email', 'title', translate("The email address identifies principals when processing invitations and freebusy lookups. It should be set to a unique value.") );
$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'));
......@@ -237,7 +245,7 @@ function principal_editor() {
$c->messages[] = i18n("Updating Principal record.");
}
$editor->Write();
if ( $_POST['type_id'] != 3 && $editor->IsCreate() ) {
if ( $_POST['type_id'] != 3 && $editor->IsCreate() ) {
/** We only add the default calendar if it isn't a group, and this is a create action */
require_once('auth-functions.php');
CreateHomeCollections($editor->Value('username'));
......@@ -396,6 +404,7 @@ label.privilege {
<tr> <th class="right" style="white-space:normal;">$prompt_privileges:</th><td class="left">$privs_html</td> </tr>
$submit_row
</table>
$csrf_field
</form>
EOTEMPLATE;
......@@ -545,9 +554,11 @@ function edit_group_row( $row_data ) {
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>
......@@ -660,8 +671,11 @@ function edit_grant_row_principal( $row_data ) {
$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>
......@@ -788,9 +802,11 @@ function edit_ticket_row( $row_data ) {
$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>
......@@ -1011,8 +1027,11 @@ function edit_binding_row( $row_data ) {
$source_title = translate('Path to collection you wish to bind, like /user1/calendar/ or https://cal.example.com/user2/cal/');
$access_title = translate('optional');
$csrf_field = getCsrfField();
$template = <<<EOTEMPLATE
<form method="POST" enctype="multipart/form-data" id="form_$form_id" action="$form_url">
$csrf_field
<td class="left">&nbsp;<input type="hidden" name="id" value="$id"></td>
<td class="left"><input type="text" name="dav_name" value="$row_data->dav_name" size="25"></td>
<td class="left"><input type="text" name="dav_displayname" size="20"></td>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment