Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible security vulnerability to connect to notebook from another tab in the browser or another browser and execute arbitrary code #1830

Closed
frejanordsiek opened this issue Oct 11, 2016 · 20 comments

Comments

@frejanordsiek
Copy link

I saw that Guile just fixed a method that a browser could connect to it even if it only listened on localhost and possibly run arbitrary code (https://lists.gnu.org/archive/html/guile-user/2016-10/msg00007.html). It seems like Jupyter notebooks would be vulnerable to the same sort of attack.

The attack, which is described at http://bouk.co/blog/hacking-developers/ (cited in the guile bug report), is basically malicious a cross protocol scripting attack combined with DNS rebinding to change the attacking code's DNS record to localhost as the article puts it.

Given that Jupyter notebooks listen to a TCP port on localhost using http, it would seem that Jupyter notebooks might be even more vulnerable to the same sort of attack. I do hope I am wrong on this, though, and jupyter notebooks are immune.

The mitigation that Guile suggests, using a unix socket, would obviously not work for Jupyter notebooks. The whole point is to use them in the browser. Only thing I can think of, if it is an issue, would be to have the jupyter notebook, when it is run, generate some sort of random authentication information and print it out to the terminal and wait a bit before starting everything so that the user can copy it or make a pop-up window in a GUI environment that shows it.

@Carreau
Copy link
Member

Carreau commented Oct 11, 2016

Hi there,

Thanks for the report. For information we prefer security vulnerability report to be disclosed on security(at)ipython.org in order to investigate without potential malicious listener to use the hypothetic vulnerability. We'll have a look.

@frejanordsiek
Copy link
Author

Oh, sorry about that. Do you think it is too late to just delete this issue report or do you think too many people have been notified about it?

@Carreau
Copy link
Member

Carreau commented Oct 11, 2016

Oh, sorry about that. Do you think it is too late to just delete this issue report or do you think too many people have been notified about it?

No problem, now that's it's here there are little advantage of removing it.

@frejanordsiek
Copy link
Author

Didn't know ipython.org was still around. I should have looked for that at least for a bit first instead of only digging around on jupyter.org

@Carreau
Copy link
Member

Carreau commented Oct 11, 2016

So a couple of information, regardless of whether you run a localhost server or a remote one we always strongly suggest setting up https/SSL and password.

This is in our docs and we are enforcing that a little more each time we make a new major release. We also provide a tool to automaticlly secure notebooks.

As far as I can tell, once a notebook server is secured over https + password the server should be immune to the attach describe.

If they are over http, then notebook are anyway susceptible to another wide range of attack. Maybe not remote with DNS poisoning, but they give all users and program of the current machine shell access as the user who run the notebook servers/kernels.

Unfortunately it is relatively hard to automatically generate a trusted-self-signed certificate for a machine it is (still) something we cannot enforce without manual user intervention.

I'm going to refer to @rgbkrk, @minrk and @yuvipanda to be sure, and likely push a bit more user to set-up ssl.

Didn't know ipython.org was still around

Oh, we are the same developers and IPython is still actively developed. We use the same security mailing list.

@frejanordsiek
Copy link
Author

That is good to hear. That does sound like it probably mitigates it (I am not an expert, though)

I guess I fell a bit behind the news in the last couple years with the split.

@Carreau
Copy link
Member

Carreau commented Oct 11, 2016

I guess I fell a bit behind the news in the last couple years with the split.

Sorry about that.

The problem was people asking :

is there IPyhon for R ? Is there IPython for Haskell

So the part of IPython which was not purely python became Jupyter.
Jupyter on it's own does nothing for code execution, so when you use Jupyter with Python you are actually using (Jupyter + IPython) as once, but you can also use (Jupyter+IRkernel) or (Jupyter+Scala kernel) .

Does that make some sens ?

@frejanordsiek
Copy link
Author

That makes sense.

The split was rather nice (i have used the julia and octave kernels some)

@jasongrout
Copy link
Member

What we did in the Sage notebook (I think implemented by @robertwb) was to by default generate a long random key when the server starts up, and we start the browser with that key in the URL. In Jupyter's case, it's a bit more complicated, but perhaps we could always authenticate the user (either using a random key at startup, or the existing password mechanism) to set an authentication cookie.

@minrk
Copy link
Member

minrk commented Oct 12, 2016

@jasongrout we should be doing that, and once we do, we can require setting a token on the local notebook server.

@gnestor
Copy link
Contributor

gnestor commented Oct 14, 2016

FYI, @minrk has submitted a PR to implement this token authentication: #1831

@akhmerov
Copy link
Member

I might be misunderstanding something, but since the kernel can execute arbitrary code, one wouldn't need the DNS trick (the kernel could e.g. email the private data)? If so, shouldn't this be a rather severe vulnerability, where instead of running a DNS, one could execute an exploit using even a static webpage?

@takluyver
Copy link
Member

Normally the cross-origin protection should mean that only pages loaded from the Jupyter server can talk to the server REST API and open websockets to kernels. I'm not a security expert though, so if you think there's another way round that, please try it and let us know.

@akhmerov
Copy link
Member

The link http://bouk.co/blog/hacking-developers/ mentioned that cross-origin protection doesn't prevent POST requests. Would this be enough to submit code to the kernel?

@takluyver
Copy link
Member

Running code on a kernel requires a websocket connection, but you could do enough to be concerning just by POST-ing. We also do origin checks on the server side, which should prevent this by checking the headers before taking the action. But we skip the check if either the Host or Origin header is missing, as we assume that's a local script using the REST API. The example in the blog looks like browsers might sometimes send a request without the Origin header, which might be a problem.

@rgbkrk I'd appreciate your assistance once you're up and about :-)

@takluyver
Copy link
Member

With an AJAX request, and it looks like Origin and Host are both forbidden names which cannot be set from Javascript. Now I'll try with forms, as shown in the blog post.

@takluyver
Copy link
Member

With a form submission, it appears that you can perform any operation which doesn't require a JSON body. The ones I can find are:

  • /api/contents/<dir_path> - create a new, unnamed file in dir_path
  • /api/kernels - start a new kernel with the default kernel spec.
  • /api/kernels/<uuid>/<action> - interrupt or restart a kernel. You need to know the kernel ID for this, however.

Here's how to replicate it:

<form enctype="text/plain" method="POST" action="http://localhost:8888/api/kernels">
<input type="submit" value="Submit" />
</form>

With a running notebook server, serve this (python3 -m http.server), open in a browser and click submit. On my system, Chromium sends an Origin header, so the origin check blocks it, but Firefox does not, so a kernel starts.

A simple mitigation for the first two cases would be for get_json_body to expect a JSON object, even if it's empty. What I'm doing currently works because it allows a totally empty request body. This doesn't feel like a very robust solution, though.

takluyver added a commit to takluyver/notebook that referenced this issue Dec 12, 2016
In discussion on jupytergh-1830, I found that HTML forms in Firefox do not send
an Origin header. You can therefore submit a POST request with an empty
body to trigger certain actions, such as starting a kernel, avoiding the
origin check we do.

This mitigates that for creating files and starting kernels, by
requiring a JSON body, even if there's no data. I cannot currently find
a way to create a JSON body in a request sent from a form.

The other cases I've found are interrupting and restarting kernels. This
does not affect those cases, but they are only possible if you have a
kernel ID.
@rgbkrk
Copy link
Member

rgbkrk commented Dec 12, 2016

From my limited reading (and a much earlier investigation) the websockets are extremely strict about the origin and header from the browser matching expectations. The usual REST requests are certainly subject to this type of attack though.

@akhmerov
Copy link
Member

So how serious is this after all? Is it still true that *hubs are secure? What about notebooks, is it worthy to backport a security patch for the versions that are still supported?

@minrk minrk modified the milestones: 4.3, 5.0 Dec 13, 2016
@minrk
Copy link
Member

minrk commented Dec 13, 2016

@akhmerov an authenticated server is protected from the DNS issue, so Hubs are not affected. The general fix is to enable a version of authentication that is acceptable to use by default (i.e. not a password that people have to remember).

4.3 is the backport of the security fix to 4.x. Backporting farther would probably not be appropriate.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 20, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants