Add CSRF stateless protection to forms

This commit is contained in:
bohwaz 2022-10-28 23:42:55 +02:00
parent ba50bbba2c
commit c82952de75
4 changed files with 70 additions and 12 deletions

View file

@ -8,6 +8,14 @@ class Users
{
protected ?stdClass $current = null;
public function __construct()
{
if (!session_id()) {
// Protect the cookie : CSRF/JS stealing the cookie
session_set_cookie_params(['samesite' => 'Lax', 'httponly' => true]);
}
}
static public function generatePassword(): string
{
$password = base64_encode(random_bytes(16));

View file

@ -25,21 +25,16 @@ if (!file_exists(DB_FILE)) {
$db->exec(file_get_contents(__DIR__ . '/../schema.sql'));
if (!LDAP::enabled()) {
@session_start();
$users = new Users;
$p = 'karadavdemo';
$users->create('demo', $p, 10, true);
$_SESSION['install_password'] = $p;
$users->login('demo', $p);
$_SESSION['install_password'] = $p;
}
$db->exec('END;');
}
if (isset($_COOKIE[session_name()]) && !isset($_SESSION)) {
@session_start();
}
function html_head(string $title): void
{
$title = htmlspecialchars($title);
@ -102,3 +97,45 @@ function http_log(string $message, ...$params): void
file_put_contents(LOG_FILE, $msg, FILE_APPEND);
}
}
function html_csrf()
{
$expire = time() + 1800;
$random = random_bytes(10);
$action = $_SERVER['REQUEST_URI'];
$token = hash_hmac('sha256', $expire . $random . $action, STORAGE_PATH . session_id());
return sprintf('<input type="hidden" name="_c_" value="%s:%s:%s" />', $token, base64_encode($random), $expire);
}
function csrf_check(): bool
{
if (empty($_POST['_c_'])) {
return false;
}
$verify = strtok($_POST['_c_'], ':');
$random = base64_decode(strtok(':'));
$expire = strtok(false);
if ($expire < time()) {
return false;
}
$action = $_SERVER['REQUEST_URI'];
$token = hash_hmac('sha256', $expire . $random . $action, STORAGE_PATH . session_id());
return hash_equals($token, $verify);
}
function html_csrf_error()
{
if (empty($_POST['_c_'])) {
return;
}
if (!csrf_check()) {
echo '<p class="error">Sorry, but the form expired, please submit it again.</p>';
}
}

View file

@ -13,7 +13,7 @@ if (empty($_GET['nc']) && $users->current()) {
$error = 0;
if (!empty($_POST['login']) && !empty($_POST['password'])) {
if (!empty($_POST['login']) && !empty($_POST['password']) && csrf_check()) {
if ($users->login($_POST['login'], $_POST['password'])) {
$url = null;
@ -55,9 +55,11 @@ echo '
if (isset($_GET['nc'])) {
printf('<input type="hidden" name="nc" value="%s" />', htmlspecialchars($_GET['nc']));
echo '<p class="info">The NextCloud app is trying to access your data. Please login to continue.</p>';
echo '<p class="info">An external application is trying to access your data. Please login to continue and allow access.</p>';
}
echo html_csrf();
echo '
<fieldset>
<legend>Login</legend>

View file

@ -31,12 +31,12 @@ elseif (isset($_GET['create']) && !$ldap) {
$create = true;
}
if ($create && !empty($_POST['create']) && !empty($_POST['login']) && !empty($_POST['password'])) {
if ($create && !empty($_POST['create']) && !empty($_POST['login']) && !empty($_POST['password']) && csrf_check()) {
$users->create(trim($_POST['login']), trim($_POST['password']));
header('Location: ' . WWW_URL . 'users.php');
exit;
}
elseif ($edit && !empty($_POST['save']) && !empty($_POST['login'])) {
elseif ($edit && !empty($_POST['save']) && !empty($_POST['login']) && csrf_check()) {
if (!$ldap && empty($_POST['is_admin']) && $user->id == $me->id) {
die("You cannot remove yourself from admins, ask another admin to do it.");
}
@ -56,7 +56,7 @@ elseif ($edit && !empty($_POST['save']) && !empty($_POST['login'])) {
header('Location: ' . WWW_URL . 'users.php');
exit;
}
elseif ($delete && !empty($_POST['delete'])) {
elseif ($delete && !empty($_POST['delete']) && csrf_check()) {
$users->delete($user);
header('Location: ' . WWW_URL . 'users.php');
exit;
@ -64,9 +64,13 @@ elseif ($delete && !empty($_POST['delete'])) {
html_head('Manage users');
html_csrf_error();
if ($create) {
$csrf = html_csrf();
echo <<<EOF
<form method="post" action="">
{$csrf}
<fieldset>
<legend>Create a new user</legend>
<dl>
@ -77,14 +81,17 @@ if ($create) {
<dd><input type="submit" name="create" value="Create" /></dd>
</dl>
</fieldset>
</form>
EOF;
}
elseif ($edit) {
$csrf = html_csrf();
$login = htmlspecialchars($user->login);
$is_admin = $user->is_admin ? 'checked="checked"' : '';
$quota = $user ? round($user->quota / 1024 / 1024) : DEFAULT_QUOTA;
echo '<form method="post" action="">
' . $csrf . '
<fieldset>
<legend>Edit user</legend>
<dl>';
@ -106,17 +113,21 @@ elseif ($edit) {
<!--<dd>Set to -1 to have unlimited space</dd>-->
<dd><input type="submit" name="save" value="Save" /></dd>
</dl>
</fieldset>';
</fieldset>
</form>';
}
elseif ($delete) {
$csrf = html_csrf();
$login = htmlspecialchars($user->login);
echo <<<EOF
<form method="post" action="">
{$csrf}
<fieldset>
<legend>Delete user</legend>
<h2>Do you want to delete the user "{$login}" and all their files?</h2>
<dd><input type="submit" name="delete" value="Yes, delete" /></dd>
</fieldset>
</form>
EOF;
}
else {