From 4dea81c5067f0747b43136896abeaaffb272cba1 Mon Sep 17 00:00:00 2001 From: Hannes Mannerheim Date: Fri, 30 Dec 2016 13:25:38 +0100 Subject: [PATCH] csrf fix --- QvitterPlugin.php | 28 ++++++++++++++++++++++++++++ actions/qvitter.php | 9 +++++++++ actions/qvitterlogin.php | 11 +++++++++++ js/ajax-functions.js | 33 +++++++++++++++++++++++++++++++++ js/misc-functions.js | 12 ++++++++++++ 5 files changed, 93 insertions(+) diff --git a/QvitterPlugin.php b/QvitterPlugin.php index e1540da..8ebeae5 100644 --- a/QvitterPlugin.php +++ b/QvitterPlugin.php @@ -422,6 +422,16 @@ class QvitterPlugin extends Plugin { } + /** + * Remove CSRF cookie on logout + * + */ + + function onEndLogout($action) { + common_set_cookie('Qvitter-CSRF', '', 0); + return true; + } + /** * Add script to default ui, to be able to toggle Qvitter with one click @@ -1285,6 +1295,24 @@ class QvitterPlugin extends Plugin { */ public function onEndSetApiUser($user) { + // if we're POST:ing and are logged in using a regular session (i.e. not basic auth or oauth) + // check that we have a correct csrf cookie and header, otherwise deny + if(common_logged_in() && $_SERVER['REQUEST_METHOD'] === 'POST') { + if(!isset($_COOKIE['Qvitter-CSRF'])) { + throw new ServerException(_('Error setting user. Missing authorization cookie data. Please logout and login again.')); + } + $csrf_token = sha1(common_config('qvitter', 'appid').session_id()); + if($_COOKIE['Qvitter-CSRF'] != $csrf_token) { + throw new ServerException(_('Error setting user. Invalid authorization cookie data. Please logout and login again.')); + } + if(!isset($_SERVER['HTTP_X_QVITTER_CSRF'])) { + throw new ServerException(_('Error setting user. Missing authorization header data. Please logout and login again.')); + } + if($_SERVER['HTTP_X_QVITTER_CSRF'] != $csrf_token) { + throw new ServerException(_('Error setting user. Invalid authorization header data. Please logout and login again.')); + } + } + // cleanup sessions, to allow for simultaneous http-requests, // e.g. if posting a notice takes a very long time Session::cleanup(); diff --git a/actions/qvitter.php b/actions/qvitter.php index 5b8e790..61ad204 100644 --- a/actions/qvitter.php +++ b/actions/qvitter.php @@ -45,6 +45,15 @@ class QvitterAction extends ApiAction { parent::prepare($args); + // if we're logged in but we have missing or incorrect csrf cookie, logout + if(common_logged_in()) { + $csrf_token = sha1(common_config('qvitter', 'appid').session_id()); + if(!isset($_COOKIE['Qvitter-CSRF']) || $_COOKIE['Qvitter-CSRF'] != $csrf_token) { + header('Location: '.common_path('').'main/logout'); + die(); + } + } + $user = common_current_user(); return true; diff --git a/actions/qvitterlogin.php b/actions/qvitterlogin.php index 800cc84..f350662 100644 --- a/actions/qvitterlogin.php +++ b/actions/qvitterlogin.php @@ -112,6 +112,17 @@ class QvitterLoginAction extends FormAction common_rememberme($user); } + // make sure we have a unique app id for this Qvitter installation in config + // to use for creating a csrf token + if(common_config('qvitter', 'appid') == false) { + Config::save('qvitter', 'appid', sha1(common_random_hexstr(16))); + } + + // set csrf-cookie + $csrf_token = sha1(common_config('qvitter', 'appid').session_id()); + common_set_cookie('Qvitter-CSRF', $csrf_token, time() + 60*60*24*30); // 1 month + + $url = common_get_returnto(); if ($url) { diff --git a/js/ajax-functions.js b/js/ajax-functions.js index 9f2734d..fcab8d8 100644 --- a/js/ajax-functions.js +++ b/js/ajax-functions.js @@ -334,6 +334,9 @@ function postUpdateBookmarks(newBookmarks) { $.ajax({ url: window.apiRoot + 'qvitter/update_bookmarks.json', cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { bookmarks: bookmarksString }, @@ -358,6 +361,9 @@ function postNewLinkColor(newLinkColor) { $.ajax({ url: window.apiRoot + 'qvitter/update_link_color.json', cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { linkcolor: newLinkColor }, @@ -383,6 +389,9 @@ function postNewBackgroundColor(newBackgroundColor) { $.ajax({ url: window.apiRoot + 'qvitter/update_background_color.json', cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { backgroundcolor: newBackgroundColor }, @@ -412,6 +421,9 @@ function postSetProfilePref(namespace, topic, data, callback) { $.ajax({ url: window.apiRoot + 'qvitter/set_profile_pref.json', cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { namespace: namespace, topic: topic, @@ -448,6 +460,9 @@ function APIFollowOrUnfollowUser(followOrUnfollow,user_id,this_element,actionOnS $.ajax({ url: window.apiRoot + postRequest, cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { user_id: user_id }, @@ -485,6 +500,9 @@ function APIBlockOrUnblockUser(blockOrUnblock,user_id,actionOnSuccess) { $.ajax({ url: window.apiRoot + postRequest, cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { id: user_id }, @@ -515,6 +533,9 @@ function APISandboxCreateOrDestroy(createOrDestroy,userId,actionOnSuccess) { $.ajax({ url: window.apiRoot + 'qvitter/sandbox/' + createOrDestroy + '.json', cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { id: userId }, @@ -545,6 +566,9 @@ function APISilenceCreateOrDestroy(createOrDestroy,userId,actionOnSuccess) { $.ajax({ url: window.apiRoot + 'qvitter/silence/' + createOrDestroy + '.json', cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { id: userId }, @@ -577,6 +601,9 @@ function APIJoinOrLeaveGroup(joinOrLeave,group_id,this_element,actionOnSuccess) $.ajax({ url: window.apiRoot + 'statusnet/groups/' + joinOrLeave + '.json', cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { id: group_id }, @@ -608,6 +635,9 @@ function postQueetToAPI(queetText_txt, in_reply_to_status_id, postToGroups, acti $.ajax({ url: window.apiRoot + 'qvitter/statuses/update.json', cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { status: queetText_txt, source: 'Qvitter', @@ -643,6 +673,9 @@ function postActionToAPI(action, actionOnSuccess) { $.ajax({ url: window.apiRoot + action, cache: false, type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Qvitter-CSRF', getCookieValue('Qvitter-CSRF')); + }, data: { source: 'Qvitter' }, diff --git a/js/misc-functions.js b/js/misc-functions.js index 50bbe6b..b0a8823 100644 --- a/js/misc-functions.js +++ b/js/misc-functions.js @@ -37,6 +37,18 @@ · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */ +/* · + · + · Get cookie by name + · + · @param a: cookie name + · + · · · · · · · · · */ + +function getCookieValue(a) { + var b = document.cookie.match('(^|;)\\s*' + a + '\\s*=\\s*([^;]+)'); + return b ? b.pop() : ''; +} /* · ·