web/account: add referrer check

This commit is contained in:
Calvin Montgomery 2017-11-05 16:17:37 -08:00
parent b876c8907a
commit 875337d9a6
3 changed files with 61 additions and 3 deletions

13
NEWS.md
View File

@ -1,3 +1,16 @@
2017-11-05
==========
The latest commit introduces a referrer check in the account page handlers.
This is added as a short-term mitigation for a recent report that account
management functions (such as deleting channels) can be executed without the
user's consent if placed in channel JS.
Longer term options are being considered, such as moving account management to a
separate subdomain to take advantage of cross-origin checks in browsers, and
requiring the user to re-enter their password to demonstrate intent. As always,
I recommend admins take extreme caution when accepting channel JS.
2017-09-26 2017-09-26
========== ==========

View File

@ -2,7 +2,7 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.51.2", "version": "3.51.3",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },

View File

@ -15,7 +15,7 @@ var session = require("../session");
var csrf = require("./csrf"); var csrf = require("./csrf");
const url = require("url"); const url = require("url");
const LOGGER = require('@calzoneman/jsli')('database/accounts'); const LOGGER = require('@calzoneman/jsli')('web/accounts');
let globalMessageBus; let globalMessageBus;
let emailConfig; let emailConfig;
@ -28,12 +28,42 @@ function handleAccountEditPage(req, res) {
sendPug(res, "account-edit", {}); sendPug(res, "account-edit", {});
} }
function verifyReferrer(req, expected) {
const referrer = req.header('referer');
if (!referrer) {
return true;
}
try {
const parsed = url.parse(referrer);
if (parsed.pathname !== expected) {
LOGGER.warn(
'Possible attempted forgery: %s POSTed to %s',
referrer,
expected
);
return false;
}
return true;
} catch (error) {
return false;
}
}
/** /**
* Handles a POST request to edit a user"s account * Handles a POST request to edit a user"s account
*/ */
function handleAccountEdit(req, res) { function handleAccountEdit(req, res) {
csrf.verify(req); csrf.verify(req);
if (!verifyReferrer(req, '/account/edit')) {
res.status(403).send('Mismatched referrer');
return;
}
var action = req.body.action; var action = req.body.action;
switch(action) { switch(action) {
case "change_password": case "change_password":
@ -43,7 +73,7 @@ function handleAccountEdit(req, res) {
handleChangeEmail(req, res); handleChangeEmail(req, res);
break; break;
default: default:
res.send(400); res.sendStatus(400);
break; break;
} }
} }
@ -197,6 +227,11 @@ async function handleAccountChannelPage(req, res) {
function handleAccountChannel(req, res) { function handleAccountChannel(req, res) {
csrf.verify(req); csrf.verify(req);
if (!verifyReferrer(req, '/account/channels')) {
res.status(403).send('Mismatched referrer');
return;
}
var action = req.body.action; var action = req.body.action;
switch(action) { switch(action) {
case "new_channel": case "new_channel":
@ -395,6 +430,11 @@ function validateProfileImage(image, callback) {
async function handleAccountProfile(req, res) { async function handleAccountProfile(req, res) {
csrf.verify(req); csrf.verify(req);
if (!verifyReferrer(req, '/account/profile')) {
res.status(403).send('Mismatched referrer');
return;
}
const user = await webserver.authorize(req); const user = await webserver.authorize(req);
// TODO: error message // TODO: error message
if (!user) { if (!user) {
@ -465,6 +505,11 @@ function handlePasswordResetPage(req, res) {
function handlePasswordReset(req, res) { function handlePasswordReset(req, res) {
csrf.verify(req); csrf.verify(req);
if (!verifyReferrer(req, '/account/passwordreset')) {
res.status(403).send('Mismatched referrer');
return;
}
var name = req.body.name, var name = req.body.name,
email = req.body.email; email = req.body.email;