mirror of https://github.com/zulip/zulip.git
reload: Fix passing data to next browser session.
Apparently, Django's CSRF protection mechanism changed at some point, and now we get a different CSRF token every time the webapp is loaded. This, in turn, caused our reload logic to avoid losing state to be completely ineffective, since the CSRF check in reload.initialize always failed. We fix this in a secure fashion by passing the reload instructions from the browser to its reloaded self via localstorage, keyed by a randomly generated token. The token randomization is primarily relevant for handling several Zulip tabs in the same browser, but also servers to make it very difficult for an attacker to ever trigger this code path by redirecting a browser to `/#reload` URLs. Fixes #3411. Fixes #3687.
This commit is contained in:
parent
64acf84ab1
commit
04db0b5df0
|
@ -69,7 +69,19 @@ function preserve_state(send_after_reload, save_pointer, save_narrow, save_compo
|
|||
}
|
||||
url += "+oldhash=" + encodeURIComponent(oldhash);
|
||||
|
||||
window.location.replace(url);
|
||||
// To protect the browser against CSRF type attacks, the reload
|
||||
// logic uses a random token (to distinct this browser from
|
||||
// others) which is passed via the URL to the browser (post
|
||||
// reloading). The token is a key into local storage, where we
|
||||
// marshall and store the URL.
|
||||
//
|
||||
// TODO: Remove the now-unnecessary URL-encoding logic above and
|
||||
// just pass the actual data structures through local storage.
|
||||
var token = util.random_int(0, 1024*1024*1024*1024);
|
||||
var ls = localstorage();
|
||||
|
||||
ls.set("reload:" + token, url);
|
||||
window.location.replace("#reload:" + token);
|
||||
}
|
||||
|
||||
|
||||
|
@ -77,10 +89,24 @@ function preserve_state(send_after_reload, save_pointer, save_narrow, save_compo
|
|||
// done before the first call to get_events
|
||||
exports.initialize = function reload__initialize() {
|
||||
var location = window.location.toString();
|
||||
var fragment = location.substring(location.indexOf('#') + 1);
|
||||
if (fragment.search("reload:") !== 0) {
|
||||
var hash_fragment = location.substring(location.indexOf('#') + 1);
|
||||
|
||||
// hash_fragment should be e.g. `reload:12345123412312`
|
||||
if (hash_fragment.search("reload:") !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Using the token, recover the saved pre-reload data from local
|
||||
// storage. Afterwards, we clear the reload entry from local
|
||||
// storage to avoid a local storage space leak.
|
||||
var ls = localstorage();
|
||||
var fragment = ls.get(hash_fragment);
|
||||
if (fragment === undefined) {
|
||||
blueslip.error("Invalid hash change reload token");
|
||||
return;
|
||||
}
|
||||
ls.remove(hash_fragment);
|
||||
|
||||
fragment = fragment.replace(/^reload:/, "");
|
||||
var keyvals = fragment.split("+");
|
||||
var vars = {};
|
||||
|
@ -89,12 +115,6 @@ exports.initialize = function reload__initialize() {
|
|||
vars[pair[0]] = decodeURIComponent(pair[1]);
|
||||
});
|
||||
|
||||
// Prevent random people on the Internet from constructing links
|
||||
// that make you send a message.
|
||||
if (vars.csrf_token !== csrf_token) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vars.msg !== undefined) {
|
||||
var send_now = parseInt(vars.send_after_reload, 10);
|
||||
|
||||
|
|
Loading…
Reference in New Issue