Add CSRF protections

This commit is contained in:
Frédéric Guillot 2014-11-07 21:44:20 -05:00
parent f4efaadad1
commit e5947db7f1
11 changed files with 93 additions and 22 deletions

View File

@ -16,6 +16,7 @@ body {
body {
margin: 0 auto;
margin-bottom: 30px;
max-width: 780px;
color: #333;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
@ -375,7 +376,6 @@ nav .active a {
font-size: 90%;
display: inline;
padding-right: 5px;
border-right: 1px dotted #ccc;
}
.page-header li:last-child {

View File

@ -14,13 +14,15 @@ Router\get_action('new-db', function() {
Response\html(Template\layout('new_db', array(
'errors' => array(),
'values' => array(),
'values' => array(
'csrf' => Model\Config\generate_csrf(),
),
'menu' => 'config',
'title' => t('New database')
)));
}
Response\redirect('?action=config');
Response\redirect('?action=about');
});
// Create a new database
@ -29,6 +31,7 @@ Router\post_action('new-db', function() {
if (ENABLE_MULTIPLE_DB) {
$values = Request\values();
Model\Config\check_csrf_values($values);
list($valid, $errors) = Model\Database\validate($values);
if ($valid) {
@ -40,18 +43,18 @@ Router\post_action('new-db', function() {
Session\flash_error(t('Unable to create the new database.'));
}
Response\redirect('?action=config');
Response\redirect('?action=about');
}
Response\html(Template\layout('new_db', array(
'errors' => $errors,
'values' => $values,
'values' => $values + array('csrf' => Model\Config\generate_csrf()),
'menu' => 'config',
'title' => t('New database')
)));
}
Response\redirect('?action=config');
Response\redirect('?action=about');
});
// Auto-update
@ -73,22 +76,30 @@ Router\get_action('auto-update', function() {
// Re-generate tokens
Router\get_action('generate-tokens', function() {
Model\Config\new_tokens();
if (Model\Config\check_csrf(Request\param('csrf'))) {
Model\Config\new_tokens();
}
Response\redirect('?action=api');
});
// Optimize the database manually
Router\get_action('optimize-db', function() {
Database::get('db')->getConnection()->exec('VACUUM');
Response\redirect('?action=config');
if (Model\Config\check_csrf(Request\param('csrf'))) {
Database::get('db')->getConnection()->exec('VACUUM');
}
Response\redirect('?action=about');
});
// Download the compressed database
Router\get_action('download-db', function() {
Response\force_download('db.sqlite.gz');
Response\binary(gzencode(file_get_contents(\Model\Database\get_path())));
if (Model\Config\check_csrf(Request\param('csrf'))) {
Response\force_download('db.sqlite.gz');
Response\binary(gzencode(file_get_contents(Model\Database\get_path())));
}
});
// Display preferences page
@ -96,8 +107,7 @@ Router\get_action('config', function() {
Response\html(Template\layout('config', array(
'errors' => array(),
'values' => Model\Config\get_all(),
'db_size' => filesize(\Model\Database\get_path()),
'values' => Model\Config\get_all() + array('csrf' => Model\Config\generate_csrf()),
'languages' => Model\Config\get_languages(),
'timezones' => Model\Config\get_timezones(),
'autoflush_options' => Model\Config\get_autoflush_options(),
@ -115,6 +125,7 @@ Router\get_action('config', function() {
Router\post_action('config', function() {
$values = Request\values() + array('nocontent' => 0);
Model\Config\check_csrf_values($values);
list($valid, $errors) = Model\Config\validate_modification($values);
if ($valid) {
@ -131,8 +142,7 @@ Router\post_action('config', function() {
Response\html(Template\layout('config', array(
'errors' => $errors,
'values' => Model\Config\get_all(),
'db_size' => filesize(\Model\Database\get_path()),
'values' => Model\Config\get_all() + array('csrf' => Model\Config\generate_csrf()),
'languages' => Model\Config\get_languages(),
'timezones' => Model\Config\get_timezones(),
'autoflush_options' => Model\Config\get_autoflush_options(),
@ -160,6 +170,7 @@ Router\get_action('help', function() {
Router\get_action('about', function() {
Response\html(Template\layout('about', array(
'csrf' => Model\Config\generate_csrf(),
'config' => Model\Config\get_all(),
'db_size' => filesize(\Model\Database\get_path()),
'menu' => 'config',
@ -171,6 +182,7 @@ Router\get_action('about', function() {
Router\get_action('api', function() {
Response\html(Template\layout('api', array(
'csrf' => Model\Config\generate_csrf(),
'config' => Model\Config\get_all(),
'menu' => 'config',
'title' => t('API')

View File

@ -136,7 +136,9 @@ Router\get_action('feeds', function() {
Router\get_action('add', function() {
Response\html(Template\layout('add', array(
'values' => array(),
'values' => array(
'csrf' => Model\Config\generate_csrf(),
),
'errors' => array(),
'menu' => 'feeds',
'title' => t('New subscription')
@ -148,6 +150,7 @@ Router\action('subscribe', function() {
if (Request\is_post()) {
$values = Request\values();
Model\Config\check_csrf_values($values);
$url = isset($values['url']) ? $values['url'] : '';
}
else {
@ -173,7 +176,10 @@ Router\action('subscribe', function() {
}
Response\html(Template\layout('add', array(
'values' => array('url' => $url),
'values' => array(
'url' => $url,
'csrf' => Model\Config\generate_csrf(),
),
'menu' => 'feeds',
'title' => t('Subscriptions')
)));

View File

@ -23,7 +23,9 @@ Router\get_action('login', function() {
Response\html(Template\load('login', array(
'errors' => array(),
'values' => array(),
'values' => array(
'csrf' => Model\Config\generate_csrf(),
),
'databases' => Model\Database\get_list(),
'current_database' => Model\Database\select()
)));
@ -33,6 +35,7 @@ Router\get_action('login', function() {
Router\post_action('login', function() {
$values = Request\values();
Model\Config\check_csrf_values($values);
list($valid, $errors) = Model\User\validate_login($values);
if ($valid) {
@ -41,7 +44,7 @@ Router\post_action('login', function() {
Response\html(Template\load('login', array(
'errors' => $errors,
'values' => $values,
'values' => $values + array('csrf' => Model\Config\generate_csrf()),
'databases' => Model\Database\get_list(),
'current_database' => Model\Database\select()
)));

View File

@ -156,6 +156,43 @@ function get_nothing_to_read_redirections()
);
}
// Create a CSRF token
function generate_csrf()
{
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = array();
}
$token = generate_token();
$_SESSION['csrf'][$token] = true;
return $token;
}
// Check CSRF token (form values)
function check_csrf_values(array &$values)
{
if (empty($values['csrf']) || ! isset($_SESSION['csrf'][$values['csrf']])) {
$values = array();
}
else {
unset($_SESSION['csrf'][$values['csrf']]);
unset($values['csrf']);
}
}
// Check CSRF token
function check_csrf($token)
{
if (isset($_SESSION['csrf'][$token])) {
unset($_SESSION['csrf'][$values['csrf']]);
return true;
}
return false;
}
// Generate a token from /dev/urandom or with uniqid() if open_basedir is enabled
function generate_token()
{

View File

@ -22,8 +22,8 @@
<h3><?= t('Database') ?></h3>
<ul>
<li><?= t('Database size:') ?> <strong><?= Helper\format_bytes($db_size) ?></strong></li>
<li><a href="?action=optimize-db"><?= t('Optimize the database') ?></a> <?= t('(VACUUM command)') ?></li>
<li><a href="?action=download-db"><?= t('Download the entire database') ?></a> <?= t('(Gzip compressed Sqlite file)') ?></li>
<li><a href="?action=optimize-db&amp;csrf=<?= $csrf ?>"><?= t('Optimize the database') ?></a> <?= t('(VACUUM command)') ?></li>
<li><a href="?action=download-db&amp;csrf=<?= $csrf ?>"><?= t('Download the entire database') ?></a> <?= t('(Gzip compressed Sqlite file)') ?></li>
<?php if (ENABLE_MULTIPLE_DB): ?>
<li>
<a href="?action=new-db"><?= t('Add a new database (new user)') ?></a>

View File

@ -8,6 +8,9 @@
</div>
<form method="post" action="?action=subscribe" autocomplete="off">
<?= Helper\form_hidden('csrf', $values) ?>
<?= Helper\form_label(t('Website or Feed URL'), 'url') ?>
<?= Helper\form_text('url', $values, array(), array('required', 'autofocus', 'placeholder="'.t('http://website/').'"')) ?><br/><br/>

View File

@ -21,7 +21,7 @@
<li><?= t('API endpoint:') ?> <strong><?= Helper\get_current_base_url().'jsonrpc.php' ?></strong></li>
<li><?= t('API username:') ?> <strong><?= Helper\escape($config['username']) ?></strong></li>
<li><?= t('API token:') ?> <strong><?= Helper\escape($config['api_token']) ?></strong></li>
<li><a href="?action=generate-tokens"><?= t('Generate new tokens') ?></a></li>
<li><a href="?action=generate-tokens&amp;csrf=<?= $csrf ?>"><?= t('Generate new tokens') ?></a></li>
</ul>
</div>
</section>

View File

@ -9,6 +9,8 @@
<section>
<form method="post" action="?action=config" autocomplete="off">
<?= Helper\form_hidden('csrf', $values) ?>
<?= Helper\form_label(t('Username'), 'username') ?>
<?= Helper\form_text('username', $values, $errors, array('required')) ?><br/>

View File

@ -26,6 +26,8 @@
<form method="post" action="?action=login" id="login-form">
<?= Helper\form_hidden('csrf', $values) ?>
<?= Helper\form_label(t('Username'), 'username') ?>
<?= Helper\form_text('username', $values, $errors, array('autofocus', 'required')) ?><br/>

View File

@ -2,10 +2,16 @@
<h2><?= t('New database') ?></h2>
<ul>
<li><a href="?action=config"><?= t('preferences') ?></a></li>
<li><a href="?action=about"><?= t('about') ?></a></li>
<li><a href="?action=help"><?= t('help') ?></a></li>
<li><a href="?action=api"><?= t('api') ?></a></li>
</ul>
</div>
<form method="post" action="?action=new-db" autocomplete="off">
<?= Helper\form_hidden('csrf', $values) ?>
<?= Helper\form_label(t('Database name'), 'name') ?>
<?= Helper\form_text('name', $values, $errors, array('required', 'autofocus')) ?>
<p class="form-help"><?= t('The name must have only alpha-numeric characters') ?></p>