فهرست منبع

CSRF token, update HTTP Referrer policy to same-origin

https://www.w3.org/TR/referrer-policy/#referrer-policy-no-referrer
https://github.com/FreshRSS/FreshRSS/issues/570
https://github.com/FreshRSS/FreshRSS/issues/955
https://github.com/FreshRSS/FreshRSS/issues/1198
https://github.com/FreshRSS/FreshRSS/issues/565
https://github.com/FreshRSS/FreshRSS/issues/554
Alexandre Alapetite 9 سال پیش
والد
کامیت
e6fd34bdda

+ 1 - 1
app/FreshRSS.php

@@ -57,7 +57,7 @@ class FreshRSS extends Minz_FrontController {
 
 	private static function initAuth() {
 		FreshRSS_Auth::init();
-		if (Minz_Request::isPost() && !is_referer_from_same_domain()) {
+		if (Minz_Request::isPost() && !(is_referer_from_same_domain() && FreshRSS_Auth::isCsrfOk())) {
 			// Basic protection against XSRF attacks
 			FreshRSS_Auth::removeAccess();
 			$http_referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];

+ 21 - 0
app/Models/Auth.php

@@ -124,6 +124,7 @@ class FreshRSS_Auth {
 		self::$login_ok = false;
 		$conf = Minz_Configuration::get('system');
 		Minz_Session::_param('currentUser', $conf->default_user);
+		Minz_Session::_param('csrf');
 
 		switch ($conf->auth_type) {
 		case 'form':
@@ -156,6 +157,26 @@ class FreshRSS_Auth {
 		$auth_type = $conf->auth_type;
 		return $auth_type === 'form';
 	}
+
+	public static function csrfToken() {
+		$csrf = Minz_Session::param('csrf');
+		if ($csrf == '') {
+			$salt = FreshRSS_Context::$system_conf->salt;
+			$csrf = sha1($salt . uniqid(mt_rand(), true));
+			Minz_Session::_param('csrf', $csrf);
+		}
+		return $csrf;
+	}
+	public static function isCsrfOk($token = null) {
+		$csrf = Minz_Session::param('csrf');
+		if ($csrf == '') {
+			return true;	//Not logged in yet
+		}
+		if ($token === null) {
+			$token = Minz_Request::param('_csrf');
+		}
+		return $token === $csrf;
+	}
 }
 
 

+ 1 - 0
app/layout/aside_feed.phtml

@@ -20,6 +20,7 @@
 	<?php } ?>
 
 	<form id="mark-read-aside" method="post">
+	<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 	<ul class="tree">
 		<li class="tree-folder category all<?php echo FreshRSS_Context::isCurrentGet('a') ? ' active' : ''; ?>">
 			<div class="tree-folder-title">

+ 1 - 1
app/layout/layout.phtml

@@ -42,7 +42,7 @@
 ?>
 		<link rel="alternate" type="application/rss+xml" title="<?php echo $this->rss_title; ?>" href="<?php echo Minz_Url::display($url_rss); ?>" />
 <?php } if (!FreshRSS_Context::$system_conf->allow_referrer) { ?>
-		<meta name="referrer" content="origin" />
+		<meta name="referrer" content="never" />
 <?php } if (FreshRSS_Context::$system_conf->allow_robots) { ?>
 		<meta name="description" content="<?php echo htmlspecialchars(FreshRSS_Context::$name . ' | ' . FreshRSS_Context::$description, ENT_COMPAT, 'UTF-8'); ?>" />
 <?php } else { ?>

+ 1 - 0
app/layout/nav_menu.phtml

@@ -88,6 +88,7 @@
 		        type="submit"><?php echo _t('gen.action.mark_read'); ?></button>
 
 		<div class="dropdown">
+			<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 			<div id="dropdown-read" class="dropdown-target"></div>
 
 			<a class="dropdown-toggle btn" href="#dropdown-read"><?php echo _i('down'); ?></a>

+ 1 - 0
app/views/auth/formLogin.phtml

@@ -6,6 +6,7 @@
 	<?php } ?>
 
 	<form id="crypto-form" method="post" action="<?php echo _url('auth', 'login'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<div>
 			<label for="username"><?php echo _t('gen.auth.username'); ?></label>
 			<input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" />

+ 1 - 0
app/views/auth/index.phtml

@@ -4,6 +4,7 @@
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url('auth', 'index'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('admin.auth.type'); ?></legend>
 
 		<div class="form-group">

+ 28 - 27
app/views/auth/register.phtml

@@ -1,33 +1,34 @@
 <div class="prompt">
-    <h1><?php echo _t('gen.auth.registration'); ?></h1>
+	<h1><?php echo _t('gen.auth.registration'); ?></h1>
 
-    <form method="post" action="<?php echo _url('user', 'create'); ?>">
-        <div>
-            <label class="group-name" for="new_user_name"><?php echo _t('gen.auth.username'), '<br />', _i('help'), ' ', _t('gen.auth.username.format'); ?></label>
-            <input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" autocomplete="off" pattern="[0-9a-zA-Z]{1,16}" />
-        </div>
+	<form method="post" action="<?php echo _url('user', 'create'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
+		<div>
+			<label class="group-name" for="new_user_name"><?php echo _t('gen.auth.username'), '<br />', _i('help'), ' ', _t('gen.auth.username.format'); ?></label>
+			<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" autocomplete="off" pattern="[0-9a-zA-Z]{1,16}" />
+		</div>
 
-        <div>
-            <label class="group-name" for="new_user_passwordPlain"><?php echo _t('gen.auth.password'), '<br />', _i('help'), ' ', _t('gen.auth.password.format'); ?></label>
-            <div class="stick">
-                <input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" required="required" autocomplete="off" pattern=".{7,}" />
-                <a class="btn toggle-password" data-toggle="new_user_passwordPlain"><?php echo _i('key'); ?></a>
-            </div>
-            <noscript><b><?php echo _t('gen.js.should_be_activated'); ?></b></noscript>
-        </div>
+		<div>
+			<label class="group-name" for="new_user_passwordPlain"><?php echo _t('gen.auth.password'), '<br />', _i('help'), ' ', _t('gen.auth.password.format'); ?></label>
+			<div class="stick">
+				<input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" required="required" autocomplete="off" pattern=".{7,}" />
+				<a class="btn toggle-password" data-toggle="new_user_passwordPlain"><?php echo _i('key'); ?></a>
+			</div>
+			<noscript><b><?php echo _t('gen.js.should_be_activated'); ?></b></noscript>
+		</div>
 
-        <div>
-            <?php
-                $redirect_url = urlencode(Minz_Url::display(
-                    array('c' => 'index', 'a' => 'index'),
-                    'php', true
-                ));
-            ?>
-            <input type="hidden" name="r" value="<?php echo $redirect_url; ?>" />
-            <button type="submit" class="btn btn-important"><?php echo _t('gen.action.create'); ?></button>
-            <a class="btn" href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.cancel'); ?></a>
-        </div>
-    </form>
+		<div>
+			<?php
+				$redirect_url = urlencode(Minz_Url::display(
+					array('c' => 'index', 'a' => 'index'),
+					'php', true
+				));
+			?>
+			<input type="hidden" name="r" value="<?php echo $redirect_url; ?>" />
+			<button type="submit" class="btn btn-important"><?php echo _t('gen.action.create'); ?></button>
+			<a class="btn" href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.cancel'); ?></a>
+		</div>
+	</form>
 
-    <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('gen.freshrss.about'); ?></a></p>
+	<p><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('gen.freshrss.about'); ?></a></p>
 </div>

+ 2 - 0
app/views/configure/archiving.phtml

@@ -4,6 +4,7 @@
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url('configure', 'archiving'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('conf.archiving'); ?></legend>
 		<p><?php echo _i('help'); ?> <?php echo _t('conf.archiving.help'); ?></p>
 
@@ -55,6 +56,7 @@
 	</form>
 
 	<form method="post" action="<?php echo _url('entry', 'optimize'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('conf.archiving.advanced'); ?></legend>
 
 		<div class="form-group">

+ 1 - 0
app/views/configure/display.phtml

@@ -4,6 +4,7 @@
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url('configure', 'display'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('conf.display'); ?></legend>
 
 		<div class="form-group">

+ 1 - 0
app/views/configure/queries.phtml

@@ -4,6 +4,7 @@
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url('configure', 'queries'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('conf.query'); ?></legend>
 
 		<?php foreach ($this->queries as $key => $query) { ?>

+ 1 - 0
app/views/configure/reading.phtml

@@ -4,6 +4,7 @@
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url('configure', 'reading'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('conf.reading'); ?></legend>
 
 		<div class="form-group">

+ 1 - 0
app/views/configure/sharing.phtml

@@ -15,6 +15,7 @@
 			<a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo _i('close'); ?></a></div>
 			<a target="_blank" class="btn" title="<?php echo _t('conf.sharing.more_information'); ?>" href="##help##"><?php echo _i('help'); ?></a>
 			</div></div>'>
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('conf.sharing'); ?></legend>
 		<?php
 			foreach (FreshRSS_Context::$user_conf->sharing as $key => $share_options) {

+ 1 - 0
app/views/configure/shortcut.phtml

@@ -12,6 +12,7 @@
 	<?php $s = FreshRSS_Context::$user_conf->shortcuts; ?>
 
 	<form method="post" action="<?php echo _url('configure', 'shortcut'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('conf.shortcut'); ?></legend>
 
 		<noscript><p class="alert alert-error"><?php echo _t('conf.shortcut.javascript'); ?></p></noscript>

+ 1 - 0
app/views/configure/system.phtml

@@ -4,6 +4,7 @@
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url('configure', 'system'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('admin.system'); ?></legend>
 
 		<div class="form-group">

+ 1 - 0
app/views/extension/index.phtml

@@ -6,6 +6,7 @@
 	<h1><?php echo _t('admin.extensions.title'); ?></h1>
 
 	<form id="form-extension" method="post">
+	<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 	<?php if (!empty($this->extension_list['system'])) { ?>
 	<h2><?php echo _t('admin.extensions.system'); ?></h2>
 	<?php

+ 1 - 0
app/views/feed/add.phtml

@@ -7,6 +7,7 @@
 	<?php } ?>
 
 	<form method="post" action="<?php echo _url('feed', 'add'); ?>" autocomplete="off">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('sub.feed.informations'); ?></legend>
 		<?php if ($this->load_ok) { ?>
 		<div class="form-group">

+ 1 - 0
app/views/helpers/feed/update.phtml

@@ -18,6 +18,7 @@
 	<?php } ?>
 
 	<form method="post" action="<?php echo _url('subscription', 'feed', 'id', $this->feed->id()); ?>" autocomplete="off">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('sub.feed.informations'); ?></legend>
 		<div class="form-group">
 			<label class="group-name" for="name"><?php echo _t('sub.feed.title'); ?></label>

+ 1 - 0
app/views/helpers/javascript_vars.phtml

@@ -16,6 +16,7 @@ echo htmlspecialchars(json_encode(array(
 		'html5_notif_timeout' => FreshRSS_Context::$user_conf->html5_notif_timeout,
 		'auth_type' => FreshRSS_Context::$system_conf->auth_type,
 		'current_view' => Minz_Request::actionName(),
+		'csrf' => FreshRSS_Auth::csrfToken(),
 	),
 	'shortcuts' => array(
 		'mark_read' => @$s['mark_read'],

+ 1 - 0
app/views/helpers/pagination.phtml

@@ -15,6 +15,7 @@
 ?>
 
 <form id="mark-read-pagination" method="post">
+<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 <ul class="pagination">
 	<li class="item pager-next">
 	<?php if (FreshRSS_Context::$next_id) { ?>

+ 2 - 0
app/views/importExport/index.phtml

@@ -4,6 +4,7 @@
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url('importExport', 'import'); ?>" enctype="multipart/form-data">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('sub.import_export.import'); ?></legend>
 		<div class="form-group">
 			<label class="group-name" for="file">
@@ -23,6 +24,7 @@
 
 	<?php if (count($this->feeds) > 0) { ?>
 	<form method="post" action="<?php echo _url('importExport', 'export'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('sub.import_export.export'); ?></legend>
 		<div class="form-group">
 			<div class="group-controls">

+ 1 - 0
app/views/index/logs.phtml

@@ -3,6 +3,7 @@
 
 	<h1><?php echo _t('index.log'); ?></h1>
 	<form method="post" action="<?php echo _url('index', 'logs'); ?>"><p>
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<input type="hidden" name="clearLogs" />
 		<button type="submit" class="btn"><?php echo _t('index.log.clear'); ?></button>
 	</p></form>

+ 1 - 0
app/views/stats/idle.phtml

@@ -19,6 +19,7 @@
 			<h2><?php echo _t('gen.date.' . $period); ?></h2>
 
 			<form id="form-delete" method="post">
+			<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 			<?php foreach ($feeds as $feed) { ?>
 			<ul class="horizontal-list">
 				<li class="item">

+ 6 - 1
app/views/subscription/index.phtml

@@ -6,6 +6,7 @@
 	<h2><?php echo _t('sub.title'); ?></h2>
 
 	<form id="add_rss" method="post" action="<?php echo _url('feed', 'add'); ?>" autocomplete="off">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<div class="stick">
 			<input type="url" name="url_rss" class="long" placeholder="<?php echo _t('sub.feed.add'); ?>" />
 			<div class="dropdown">
@@ -56,13 +57,16 @@
 
 		<ul class="box-content box-content-centered">
 			<form action="<?php echo _url('category', 'create'); ?>" method="post">
+				<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 				<li class="item"><input type="text" id="new-category" name="new-category" placeholder="<?php echo _t('sub.category.new'); ?>" /></li>
 				<li class="item"><button class="btn btn-important" type="submit"><?php echo _t('gen.action.submit'); ?></button></li>
 			</form>
 		</ul>
 	</div>
 
-	<form id="controller-category" method="post" aria-hidden="true"></form>
+	<form id="controller-category" method="post" aria-hidden="true">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
+	</form>
 
 	<?php
 		foreach ($this->categories as $cat) {
@@ -71,6 +75,7 @@
 	<div class="box">
 		<div class="box-title">
 			<form action="<?php echo _url('category', 'update', 'id', $cat->id()); ?>" method="post">
+				<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 				<input type="text" name="name" value="<?php echo $cat->name(); ?>" />
 
 				<div class="dropdown">

+ 2 - 0
app/views/user/manage.phtml

@@ -4,6 +4,7 @@
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url('user', 'create'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('admin.user.create'); ?></legend>
 
 		<div class="form-group">
@@ -46,6 +47,7 @@
 	</form>
 
 	<form method="post" action="<?php echo _url('user', 'delete'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('admin.user.users'); ?></legend>
 
 		<div class="form-group">

+ 2 - 0
app/views/user/profile.phtml

@@ -4,6 +4,7 @@
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url('user', 'profile'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('conf.profile'); ?></legend>
 
 		<div class="form-group">
@@ -52,6 +53,7 @@
 
 	<?php if (!FreshRSS_Auth::hasAccess('admin')) { ?>
 	<form id="crypto-form" method="post" action="<?php echo _url('user', 'delete'); ?>">
+		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<legend><?php echo _t('conf.profile.delete'); ?></legend>
 
 		<p class="alert alert-warn"><span class="alert-head"><?php echo _t('gen.short.attention'); ?></span> <?php echo _t('conf.profile.delete.warn'); ?></p>

+ 1 - 1
lib/lib_rss.php

@@ -391,7 +391,7 @@ function cryptAvailable() {
 
 function is_referer_from_same_domain() {
 	if (empty($_SERVER['HTTP_REFERER'])) {
-		return false;
+		return true;	//Accept empty referer while waiting for good support of meta referrer same-origin policy in browsers
 	}
 	$host = parse_url(((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https://' : 'http://') .
 		(empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST']));

+ 11 - 2
p/scripts/main.js

@@ -134,7 +134,10 @@ function mark_read(active, only_not_read) {
 	$.ajax({
 		type: 'POST',
 		url: url,
-		data : { ajax: true }
+		data : {
+			ajax: true,
+			_csrf: context.csrf,
+		},
 	}).done(function (data) {
 		var $r = active.find("a.read").attr("href", data.url),
 			inc = 0;
@@ -178,7 +181,10 @@ function mark_favorite(active) {
 	$.ajax({
 		type: 'POST',
 		url: url,
-		data : { ajax: true }
+		data : {
+			ajax: true,
+			_csrf: context.csrf,
+		},
 	}).done(function (data) {
 		var $b = active.find("a.bookmark").attr("href", data.url),
 			inc = 0;
@@ -775,6 +781,9 @@ function updateFeed(feeds, feeds_count) {
 	$.ajax({
 		type: 'POST',
 		url: feed.url,
+		data : {
+			_csrf: context.csrf,
+		},
 	}).always(function (data) {
 		feed_processed++;
 		$("#actualizeProgress .progress").html(feed_processed + " / " + feeds_count);