experimental CSRF protection

This commit is contained in:
Andrew Dolgov 2011-12-26 12:02:52 +04:00
parent 036cd3a410
commit 8484ce2258
14 changed files with 106 additions and 9 deletions

View File

@ -1,5 +1,5 @@
<?php <?php
set_include_path(get_include_path() . PATH_SEPARATOR . set_include_path(get_include_path() . PATH_SEPARATOR .
dirname(__FILE__) . "/include"); dirname(__FILE__) . "/include");
/* remove ill effects of magic quotes */ /* remove ill effects of magic quotes */
@ -20,6 +20,11 @@
$op = $_REQUEST["op"]; $op = $_REQUEST["op"];
@$method = $_REQUEST['subop'] ? $_REQUEST['subop'] : $_REQUEST["method"]; @$method = $_REQUEST['subop'] ? $_REQUEST['subop'] : $_REQUEST["method"];
if (!$method)
$method = 'index';
else
$method = strtolower($method);
/* Public calls compatibility shim */ /* Public calls compatibility shim */
$public_calls = array("globalUpdateFeeds", "rss", "getUnread", "getProfiles", "share", $public_calls = array("globalUpdateFeeds", "rss", "getUnread", "getProfiles", "share",
@ -30,6 +35,11 @@
return; return;
} }
$csrf_token = $_REQUEST['csrf_token'];
if (!$csrf_token)
error_log("[$op/$method] CSRF: [$csrf_token]\n", 3, "/tmp/csrf.log");
require_once "functions.php"; require_once "functions.php";
require_once "sessions.php"; require_once "sessions.php";
require_once "sanity_check.php"; require_once "sanity_check.php";
@ -138,13 +148,17 @@
$handler = new $op($link, $_REQUEST); $handler = new $op($link, $_REQUEST);
if ($handler) { if ($handler) {
if ($handler->before($method)) { if (validate_csrf($csrf_token) || $handler->csrf_ignore($method)) {
if ($method && method_exists($handler, $method)) { if ($handler->before($method)) {
$handler->$method(); if ($method && method_exists($handler, $method)) {
} else if (method_exists($handler, 'index')) { $handler->$method();
$handler->index(); }
$handler->after();
return;
} }
$handler->after(); } else {
header("Content-Type: text/plain");
print json_encode(array("error" => array("code" => 6)));
return; return;
} }
} }

View File

@ -1,6 +1,12 @@
<?php <?php
class Article extends Protected_Handler { class Article extends Protected_Handler {
function csrf_ignore($method) {
$csrf_ignored = array("redirect");
return array_search($method, $csrf_ignored) !== false;
}
function redirect() { function redirect() {
$id = db_escape_string($_REQUEST['id']); $id = db_escape_string($_REQUEST['id']);

View File

@ -1,6 +1,12 @@
<?php <?php
class Feeds extends Protected_Handler { class Feeds extends Protected_Handler {
function csrf_ignore($method) {
$csrf_ignored = array("index");
return array_search($method, $csrf_ignored) !== false;
}
private function feedlist_init_cat($cat_id, $hidden = false) { private function feedlist_init_cat($cat_id, $hidden = false) {
$obj = array(); $obj = array();
$cat_id = (int) $cat_id; $cat_id = (int) $cat_id;

View File

@ -8,6 +8,10 @@ class Handler {
$this->args = $args; $this->args = $args;
} }
function csrf_ignore($method) {
return true;
}
function before() { function before() {
return true; return true;
} }

View File

@ -1,5 +1,12 @@
<?php <?php
class Pref_Feeds extends Protected_Handler { class Pref_Feeds extends Protected_Handler {
function csrf_ignore($method) {
$csrf_ignored = array("index", "getfeedtree", "add", "editcats", "editfeed");
return array_search($method, $csrf_ignored) !== false;
}
function batch_edit_cbox($elem, $label = false) { function batch_edit_cbox($elem, $label = false) {
print "<input type=\"checkbox\" title=\"".__("Check to enable field")."\" print "<input type=\"checkbox\" title=\"".__("Check to enable field")."\"
onchange=\"dijit.byId('feedEditDlg').toggleField(this, '$elem', '$label')\">"; onchange=\"dijit.byId('feedEditDlg').toggleField(this, '$elem', '$label')\">";

View File

@ -1,6 +1,12 @@
<?php <?php
class Pref_Filters extends Protected_Handler { class Pref_Filters extends Protected_Handler {
function csrf_ignore($method) {
$csrf_ignored = array("index", "getfiltertree", "edit");
return array_search($method, $csrf_ignored) !== false;
}
function filter_test($filter_type, $reg_exp, function filter_test($filter_type, $reg_exp,
$action_id, $action_param, $filter_param, $inverse, $feed_id) { $action_id, $action_param, $filter_param, $inverse, $feed_id) {

View File

@ -1,6 +1,12 @@
<?php <?php
class Pref_Instances extends Protected_Handler { class Pref_Instances extends Protected_Handler {
function csrf_ignore($method) {
$csrf_ignored = array("index", "edit");
return array_search($method, $csrf_ignored) !== false;
}
function before() { function before() {
if (parent::before()) { if (parent::before()) {
if ($_SESSION["access_level"] < 10) { if ($_SESSION["access_level"] < 10) {

View File

@ -1,6 +1,12 @@
<?php <?php
class Pref_Labels extends Protected_Handler { class Pref_Labels extends Protected_Handler {
function csrf_ignore($method) {
$csrf_ignored = array("index", "getlabeltree", "edit");
return array_search($method, $csrf_ignored) !== false;
}
function edit() { function edit() {
$label_id = db_escape_string($_REQUEST['id']); $label_id = db_escape_string($_REQUEST['id']);

View File

@ -1,6 +1,12 @@
<?php <?php
class Pref_Prefs extends Protected_Handler { class Pref_Prefs extends Protected_Handler {
function csrf_ignore($method) {
$csrf_ignored = array("index");
return array_search($method, $csrf_ignored) !== false;
}
function changepassword() { function changepassword() {
$old_pw = $_POST["old_password"]; $old_pw = $_POST["old_password"];

View File

@ -1,6 +1,5 @@
<?php <?php
class Pref_Users extends Protected_Handler { class Pref_Users extends Protected_Handler {
function before() { function before() {
if (parent::before()) { if (parent::before()) {
if ($_SESSION["access_level"] < 10) { if ($_SESSION["access_level"] < 10) {
@ -12,6 +11,12 @@ class Pref_Users extends Protected_Handler {
return false; return false;
} }
function csrf_ignore($method) {
$csrf_ignored = array("index");
return array_search($method, $csrf_ignored) !== false;
}
function userdetails() { function userdetails() {
header("Content-Type: text/xml"); header("Content-Type: text/xml");

View File

@ -1,6 +1,12 @@
<?php <?php
class RPC extends Protected_Handler { class RPC extends Protected_Handler {
function csrf_ignore($method) {
$csrf_ignored = array("sanitycheck", "buttonplugin");
return array_search($method, $csrf_ignored) !== false;
}
function setprofile() { function setprofile() {
$id = db_escape_string($_REQUEST["id"]); $id = db_escape_string($_REQUEST["id"]);

View File

@ -721,6 +721,7 @@
$_SESSION["uid"] = db_fetch_result($result, 0, "id"); $_SESSION["uid"] = db_fetch_result($result, 0, "id");
$_SESSION["name"] = db_fetch_result($result, 0, "login"); $_SESSION["name"] = db_fetch_result($result, 0, "login");
$_SESSION["access_level"] = db_fetch_result($result, 0, "access_level"); $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
$_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " . db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
$_SESSION["uid"]); $_SESSION["uid"]);
@ -810,6 +811,10 @@
} }
} }
function validate_csrf($csrf_token) {
return $csrf_token == $_SESSION['csrf_token'];
}
function validate_session($link) { function validate_session($link) {
if (SINGLE_USER_MODE) return true; if (SINGLE_USER_MODE) return true;
@ -2064,6 +2069,8 @@
$params["collapsed_feedlist"] = (int) get_pref($link, "_COLLAPSED_FEEDLIST"); $params["collapsed_feedlist"] = (int) get_pref($link, "_COLLAPSED_FEEDLIST");
$params["csrf_token"] = $_SESSION["csrf_token"];
return $params; return $params;
} }

View File

@ -1,6 +1,25 @@
var notify_silent = false; var notify_silent = false;
var loading_progress = 0; var loading_progress = 0;
var sanity_check_done = false; var sanity_check_done = false;
var init_params = {};
Ajax.Base.prototype.initialize = Ajax.Base.prototype.initialize.wrap(
function (callOriginal, options) {
if (getInitParam("csrf_token") != undefined) {
Object.extend(options, options || { });
if (Object.isString(options.parameters))
options.parameters = options.parameters.toQueryParams();
else if (Object.isHash(options.parameters))
options.parameters = options.parameters.toObject();
options.parameters["csrf_token"] = getInitParam("csrf_token");
}
return callOriginal(options);
}
);
/* add method to remove element from array */ /* add method to remove element from array */

View File

@ -5,7 +5,6 @@ var _active_feed_id = 0;
var _active_feed_is_cat = false; var _active_feed_is_cat = false;
var hotkey_prefix = false; var hotkey_prefix = false;
var hotkey_prefix_pressed = false; var hotkey_prefix_pressed = false;
var init_params = {};
var _force_scheduled_update = false; var _force_scheduled_update = false;
var last_scheduled_update = false; var last_scheduled_update = false;