How your ethereum can be stolen through DNS rebinding
With the new buzz around exploiting unauthenticated JSON-RPC services on localhost ignited by Tavis Ormandy, The first thing that came to my mind was ethereum clients(Geth, Mist and Parity).
Most of the ethereum clients run a JSON-RPC service on port 8545 on localhost, but since it’s on localhost, we can’t access it directly from user’s browser due to SOP. Thisissue in the electrum wallet exploited the CORS headers to take control over the user’s electrum wallet through JSON-RPC on localhost.
Geth’s JSON-RPC looked pretty secure as it didn’t return any CORS header, but then cpacia commented something on the half-patch of the electrum wallet which sparked my interest. Here are the exact words
Just disabling CORS is still vulnerable to a DNS rebinding attack. It needs to be authenticated. ~ cpacia
I had heard about DNS rebinding but never tried to look into it much. Since Geth’s JSON-RPC is also unauthenticated, it might be vulnerable to DNS rebinding attacks too?
I started looking into DNS Rebinding, but all the articles were mainly pretty old. Then I asked about it in Bug Bounty Forum and Luke Young linked me his awesome Defcon talk about modern DNS rebinding exploitation. It also included an automated tool to make DNS rebinding achievable on most of the modern browsers.
But I was more curious and I didn’t want to use any pre-made tool, So I began writing my own DNS server. Python has a pretty nice library called
dnslib which handled most of the stuff for me. I registered a domain, setup some glue records pointing towards my server and used them as the nameservers.
I wanted to see how different browsers behaved with very low TTLs, so I made my DNS server return TTLs < 5. Chrome, Firefox and Safari cached the DNS responses for 60 seconds, even though the TTL was less than 5.
60 seconds isn’t such a long time, and I think I can make a user stay on my webpage for at least 60 seconds. Now the only thing left was to actually try it.
I ran geth with the
--rpc flag(in testnet of-course)
geth --rpc --testnet
Now to make it work with geth, I had to run my web server and domain on port 8545 as SOP also follows the port. But this would look kinda sketchy if I sent the link to anyone with port 8545 in it.
Another issue was about multiple users, what if multiple users at the same time visited my domain? The DNS server would get confused as I was using a counter based system and there was no way to differentiate between requests from individual users. This had me stumped for a while until I remembered
I could iframe a random subdomain each time a user visits the main domain and use it as an identification. I know I may not be explaining it well but here’s an example.
Let’s assume that my domain is
attacker.com and my server’s IP is
18.104.22.168 Here is how the flow goes.
- The victim opens
attacker.comin his browser.
- First, a DNS request for
attacker.comis sent to my server and it responds with the real IP
attacker.comis loaded into the user’s browser, which then creates a hidden iframe with a random subdomain
randomrsub.attacker.com:8545and appends it to the body
- Now, a DNS request is sent to my server for the subdomain
randomrsub.attacker.com, and the DNS server again responds with the real IP
22.214.171.124. But this time, since it’s on port 8545, the apache responds with a different virtual host, which begins the DNS rebinding.
randomrsub.attacker.com:8545waits 60 seconds, and then sends an XmlHttpRequest to
- Since the DNS cache has expired, the browser resolves the DNS again. This time, my server responds with an IP of
- Now the request is actually sent to
127.0.0.1:8545/testinstead of my server, and since it’s under the origin of
randomrr.attacker.com:8545, we are able to read the response.
- Since we are generating a random subdomain everytime, we can now even accommodate multiple users as the subdomain can act their identification token.
This could basically be exploited with a stored XSS too. Just point a script src to the js file which adds the iframe and TADA!!
So now we can read responses from JSON-RPC service, which means we can read their ethereum addresses, their balance and potentially steal their ether if their account is unlocked. The JSON-RPC API has pretty decent method called
eth_sendTransaction which can be basically used to send ethereum from the user’s account.
I have set up a Proof-of-Concept on http://rebinddns.ml. If you stay on it for more than 60 seconds and you’re running Geth(or any other ethereum client) with JSON-RPC, you will see an alert() which will contain your ethereum addresses and their balance.
All the files used in the PoC can be found on my github.
min.js- The Js file which generates a hidden iframe of a subdomain on port 8545
main.js- The Js file which executes the DNS Rebinding
server.py- The DNS server written in python
I have verified that Geth, the C++ ethereum client and the python client are vulnerable. The PoC has been tested on Firefox, Chrome and Safari.
PS: This has been reported to the ethereum foundation and is fixed. They also awarded me a nice bounty.
For any questions, you can hit me up on my twitter @ret2got