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

View File

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

View File

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

View File

@ -23,7 +23,9 @@ Router\get_action('login', function() {
Response\html(Template\load('login', array( Response\html(Template\load('login', array(
'errors' => array(), 'errors' => array(),
'values' => array(), 'values' => array(
'csrf' => Model\Config\generate_csrf(),
),
'databases' => Model\Database\get_list(), 'databases' => Model\Database\get_list(),
'current_database' => Model\Database\select() 'current_database' => Model\Database\select()
))); )));
@ -33,6 +35,7 @@ Router\get_action('login', function() {
Router\post_action('login', function() { Router\post_action('login', function() {
$values = Request\values(); $values = Request\values();
Model\Config\check_csrf_values($values);
list($valid, $errors) = Model\User\validate_login($values); list($valid, $errors) = Model\User\validate_login($values);
if ($valid) { if ($valid) {
@ -41,7 +44,7 @@ Router\post_action('login', function() {
Response\html(Template\load('login', array( Response\html(Template\load('login', array(
'errors' => $errors, 'errors' => $errors,
'values' => $values, 'values' => $values + array('csrf' => Model\Config\generate_csrf()),
'databases' => Model\Database\get_list(), 'databases' => Model\Database\get_list(),
'current_database' => Model\Database\select() '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 // Generate a token from /dev/urandom or with uniqid() if open_basedir is enabled
function generate_token() function generate_token()
{ {

View File

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

View File

@ -8,6 +8,9 @@
</div> </div>
<form method="post" action="?action=subscribe" autocomplete="off"> <form method="post" action="?action=subscribe" autocomplete="off">
<?= Helper\form_hidden('csrf', $values) ?>
<?= Helper\form_label(t('Website or Feed URL'), 'url') ?> <?= Helper\form_label(t('Website or Feed URL'), 'url') ?>
<?= Helper\form_text('url', $values, array(), array('required', 'autofocus', 'placeholder="'.t('http://website/').'"')) ?><br/><br/> <?= 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 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 username:') ?> <strong><?= Helper\escape($config['username']) ?></strong></li>
<li><?= t('API token:') ?> <strong><?= Helper\escape($config['api_token']) ?></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> </ul>
</div> </div>
</section> </section>

View File

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

View File

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

View File

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