webpack / webpack-dev-server

Serves a webpack app. Updates the browser on changes. Documentation https://webpack.js.org/configuration/dev-server/.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

webpack-dev-server vulnerable to DNS rebinding attack

edmorley opened this issue · comments

This issue was disclosed privately to @sokra via email on 2017-04-17, and a fix released in webpack-dev-server v2.4.3 / v1.16.4, which were released 2017-04-17.

I'm filing this issue retrospectively to clarify why the additional security check is necessary, in the hope that it makes people reconsider before turning it off (eg #882, #883).

--

Hi Tobias

Thank you for reaching out in webpack/webpack#4599.

The issue I've noticed is in webpack-dev-server - specifically:

  • it uses the node native http library - passing the hostname (default localhost) to http's listen() [1].
  • whilst the listen() docs [2] only refer to hostname, the actual behaviour is "resolve hostname to IP, bind to that IP and accept any connections to that IP regardless of the hostname used for the request" [3].
  • webpack-dev-server doesn't perform any verification of the Host header itself.
  • therefore it is vulnerable to DNS rebinding attacks, similar to that fixed in Rails [4] and Django [5].

Implications:

  • a malicious site can steal static content served by webpack-dev-server.
  • this can include any file from the directory where webpack-dev-server was run, not just those that would end up in dist/ (is this intentional, seems like an additional bug?).
  • worse, if webpack-dev-server's proxy feature [6] is being used, then this exposes any remote code execution vulnerabilities in the backend stack. This is particularly dangerous, since I would imagine it's common to use changeOrigin: True [7], which would bypass any Host header checks in the backend app - and so make typical Rails and Django setups RCE-vulnerable.

STR:

  1. Install nodejs 7.x, webpack 2.3.2 and webpack-dev-server 2.4.2.
  2. Create a new project based on https://webpack.js.org/guides/get-started/#using-webpack-with-a-config
  3. Run ./node_modules/.bin/webpack-dev-server
  4. In another terminal, try requests like:
  • curl -i zzz.malicious-site.com:8080/ --resolve zzz.malicious-site.com:8080:127.0.0.1
  • curl -i zzz.malicious-site.com:8080/package.json --resolve zzz.malicious-site.com:8080:127.0.0.1
  • curl -i zzz.malicious-site.com:8080/.git/config --resolve zzz.malicious-site.com:8080:127.0.0.1

Expected:
HTTP 400s or similar for each curl request, along with a "Invalid Host header" response body - similar to what Django's runserver does.

Actual:
HTTP 200s for cases where the file exists, along with the file contents.

Note: In the examples above, --resolve is being used to quickly emulate what a DNS rebinding attack could achieve, however the same is possible in a browser via a site using DNS rebinding - to demonstrate that this is true:

  1. Use the same project/setup as above
  2. Run DEBUG=express:router ./node_modules/.bin/webpack-dev-server --port 3000
  3. Visit http://dnsrebinder.net/
  4. Open the web console and watch the XHRs to http://<HASH>-bad.dnsrebinder.net:3000/not_found.
  5. When those XHRs change from HTTP 200s to HTTP 404s, it means the DNS for the subdomain has been updated to 127.0.0.1.
  6. Check the webpack-dev-server debug output - you'll see the GET /not_found showing the page circumvented CORS and hit the dev server.
  7. Inspect the headers/body of the HTTP 404 response in the browser web console - you'll see they are from the webpack-dev-server Express server - and could just have easily been an HTTP 200 disclosing local file content if the page had requested a more relevant URL.

dnsrebinder.net (source: [8]) is is a proof of concept for the Rails dev server RCE vulnerability mentioned above, hence the port 3000. It doesn't do anything interesting since it's relying on Rails specific behaviour, but it demonstrates the point without me needing to spin up something similar using AWS Route53 (I can if required). Note: A small number of DNS servers filter 127.0.0.1, if you can't repro, use Google DNS (8.8.8.8 / 8.8.4.4).

To fix this issue a Host header check needs to be added to webpack-dev-server, that's ideally enabled by default, with a big warning shown if there is a way to disable it.

In addition I think the nodejs http listen() docs [2] should be updated - but I'll file an issue there myself later.

[1] https://github.com/webpack/webpack-dev-server/blob/v2.4.2/bin/webpack-dev-server.js#L402
[2] https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback
[3] https://stackoverflow.com/questions/21158686/node-js-express-limit-to-certain-hostname
[4] https://benmmurphy.github.io/blog/2016/07/11/rails-webconsole-dns-rebinding/
[5] https://docs.djangoproject.com/en/1.11/releases/1.10.3/#dns-rebinding-vulnerability-when-debug-true
[6] https://webpack.js.org/configuration/dev-server/#devserver-proxy
[7] https://github.com/chimurai/http-proxy-middleware#http-proxy-options
[8] https://github.com/benmmurphy/rebinder

This was fixed by 2957853 , in the versions released on 2017-04-17.

I read through various issues relating to this and found it all rather unclear.

A useful explanation is available here: https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a

TLDR: This issue still affects you even if you are only running webpack-dev-server within a trusted network. The issue is that you may visit a malicious website which can use this bug to make XHR requests to the dev server, possibly exposing private code or internal services available through the dev server proxy.

why are IP-Addresses considered unsafe though?

There is no way to dns-rebind an IP-address when accessing the dev-server, so it would make sense to not apply the host-check to requests that use an explicit IP instead of a host-name (given that the server is listening on an interface other than 127.0.0.1).

This would possibly stop people everywhere from just using disableHostCheck in order to have their local-network-development environments working.

In addition, as the TLD .local is a reserved gTLD and cannot be used by regular DNS-servers it might make sense to have the allowedHosts-option default to ['.local'].

Would you accept a PR for that?

I agree. I wasn't consulted before the fix for the security issue landed, otherwise I would have suggested doing this from the outset.

#931 is filed for adding this retrospectively fwiw.

thanks! I opened a PR in #1007 to fix the ip-address issue.

This should have been done only when listening on 127.0.0.1. The attack makes practically no sense for people using webpack with the most popular frameworks Angular/React and public IP. How would the attacker know what public IP to rebind to? They won't know, unless it is a very targeted attack, in the broad case this only affects servers listening on 127.0.0.1:3000. Let's assume the attacker does know what public IP the Angular /Rect webpack listens on, so with this attack they avoid CORS and access the public resources of the dev server. So what? If they know the public IP they could have just opened it in their browser and seen the same public resources (assuming no firewall). In the Angular/React case this cannot be used to access local files that are not otherwise already public. Then comes the 'proxy' part, someone please enlighten me how this vulnerability is such a big deal when proxying an API through webpack. Can the attacker make authenticated requests against APIs protected with cookies? No, the rebound domain will not have access to the cookies of the real domain. Can the attacker make authenticates requests against APIs protected by Auth token stored in local storage? No, the rebound domain doesn't have access to local storage. In other words, thank you for discovering this vulnerability but its impact has been heavily overblown for people using webpack on public IP. BTW. thanks for disableHostCheck. Normally I am all for keeping our tools safe, but this feature has been a major source of inconvenience for me working with remote development environments and Angular/React.

I think the main point is that a DNS-rebinding attack can in theory exfiltrate information from a service that is written as if it was inaccessible to another party.

It's not so much about the question if that is actually a problem in any specific case but about being safe in the assumption that a service bound to an internal interface (be it the loopback-interface or a NATed local network) will not be accessible from the outside.

Regarding other IPs than 127.0.0.1 I'd say that yes, you might need a very targeted attack for this, but things like spearphising is actually not unheard of, so why not prevent it if possible.

Then comes the 'proxy' part, someone please enlighten me how this vulnerability is such a big deal when proxying an API through webpack. Can the attacker make authenticated requests against APIs protected with cookies? No, the rebound domain will not have access to the cookies of the real domain.

Yes they can - think changeOrigin and similar (as mentioned in the OP).

However the real problem is development environments where out of the box there are RCE opportunities. This isn't just a hypothetical - see here for example or any Python environment that uses werkzeug, or a Django stack that uses django-debug-toolbar.