We have just released Jupyter Notebook 4.3.0 and 4.3.1 with some important security fixes.
You can check out the changelog for more details on the many fixes and improvements. I'm going to focus on the security changes in this post.
4.3.0: Token authentication
The biggest change in 4.3 is the addition of token-based authentication, which is enabled by default. Several of the security issues the notebook has had over the years would have been avoided if the notebook ran with authentication by default. We did not do this for a long time, because the process of enabling and entering a password on a localhost-only web application was deemed too cumbersome, and the risks too small, given that it listened only on localhost. With automatic token authentication, I think we have a solution that should be convenient enough for most users that we can now switch to enabling authentication by default.
If you use Jupyter in its default configuration, which is to open your browser automatically when you type jupyter notebook
, this shouldn't have much impact on your daily work. In this case, a one-time token is generated, and your browser opens with this in the URL. That token is immediately consumed, and your browser will be authenticated, storing the information in a cookie for future requests.
If you access the notebook without authentication, you will see this page:
This may happen if you switch browsers, or use the notebook with the --no-browser
option, in which you will need to enter the token manually the first time you connect to a server with a given browser. This can be done at the login page above, or via a URL containing the token. The notebook server logs this URL when it starts, so you can paste the URL into your browser:
[C 16:11:17.581 NotebookApp]
Copy/paste this URL into your browser when you connect for the first time,
to login with a token:
http://localhost:8888/?token=20abd368...
After you have logged in with this token just once, a cookie is set and used thereafter, even across restarts of the notebook server. So you shouldn't need to deal with the token after the first time you have used the notebook on a given machine, until/unless you switch browsers or launch new notebook servers on different ports. This does mean that cookies are required for authenticated access to notebooks, and clearing cookies means needing to login again.
At any time, you can see all of your current notebook servers with their token-authenticated URLs by running the jupyter notebook list
command:
$ jupyter notebook list
Currently running servers:
http://localhost:8888/?token=1234... :: /Users/you/notebooks
We didn't communicate the information about token authentication very well in 4.3.0, and I apologize for that. 4.3.1 should make the token behavior much clearer, with more information in both the login form and the notebook log output about how to use the tokens and when you need them. You can see the docs for more details. We're always open to improvements, so please open an Issue if you are having trouble.
4.3.1
4.3.1 follows shortly after 4.3.0, with a fix for a CSRF vulnerability (CVE-2016-9971), affecting users of the Firefox or Microsoft (IE, Edge) browsers, and any other browsers that do not set the Origin header on cross-site forms. WebKit and Blink based browsers like Safari and Chrome are not affected. The effect of this vulnerability is the ability of forms on malicious websites visited by the user to spawn new kernels and create empty, untitled files on the user's notebook server. CORS protections already in place prevent further access to these kernels and editing the content of any files.
Summary:
- CVE-2016-9971
- Affected notebook versions: all notebook releases < 4.3.1
- Affected users: Users of Firefox or Microsoft browsers, or other browsers that do not set the Origin header on forms.
- Results: Visiting malicious pages with these browsers could result in unauthorized launch of new kernels or creation of empty, untitled files on the user's notebook server
- Fix: Upgrade to notebook-4.3.1, which uses an
xsrf
token via cookies to validate the origin of forms. See tornado docs for details.
Update: practical effects of xsrf for extensions
This note was omitted when first posted.
The use of the xsrf token affects existing extensions / etc. that work directly with the notebook server via REST API. To make requests to the rest API, the _xsrf
cookie must be sent back to the server in the X-XSRF-Token
header of API requests, for example:
function _get_cookie(name) {
// from tornado docs: http://www.tornadoweb.org/en/stable/guide/security.html
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
function _add_xsrf_token(settings) {
// Adds xsrf token header to jquery ajax settings
settings = settings || {};
var xsrf_token = _get_cookie('_xsrf');
if (xsrf_token) {
if (!settings.headers) {
settings.headers = {};
}
settings.headers['X-XSRFToken'] = xsrf_token;
}
return settings;
}
function ajax(url, settings) {
// like $.ajax, but ensure xsrf token is added to headers
settings = _add_xsrf_token(settings);
return $.ajax(url, settings);
};
This code will also work for any previous version of the notebook, where the xsrf cookie will be undefined and ignored.
If you're using fetch
, you'll need to set credentials: true
as well so that the Cookie
header is set.
var xsrfToken = _get_cookie('_xsrf')
fetch("http://127.0.0.1:8888/api/contents/",
{
method: "POST",
credentials: "include",
headers: {
"X-XSRFToken": xsrfToken,
"Content-Type": "application/json",
},
body: { name: "Test.py" }
}
)