(Before I begin — this bug was responsibly disclosed and has been fixed in the version 1.0.9 and later of EOSIO software, so I highly recommend you to update)

I’ve been doing Application Security as a full-time job for about 5 years now, and I’ve been an avid reader of bug bounty responsible disclosure stories way before it became “a thing”, but somehow I never participated in public bug bounties. So this is a first for me and I’m excited to discuss my experience with the HackerOne-backed EOSIO program.

In the past few months I’ve been providing InfoSec and AppSec guidance to an awesome team of folks who got me interested in EOSIO and blockchain technology in general. Previously I’ve never been much interested in cryptocurrencies (especially bitcoin), but when I first heard about smart contracts and then Proof Of Stake (vs Proof Of Work — which I think is simply not scalable and a massive waste of energy) my AppSec spider-sense tingled. The idea of blindly executing untrusted arbitrary code that’s flying over a peer-to-peer network is crazy scary, but terribly exciting because of the promise of decentralization and consensus algorithms that modern blockchains provide.

A little more than a month ago, I was writing a script to automate various tasks (like setting up an EOSIO account with the right multi-sig permissions) and I noticed that successive calls to cleos would not prompt for the wallet passphrase on every call. I could see that cleos was spawning a detached keosd process so that it would keep alive for 15 minutes after passphrase prompt (this is the default documented behavior — which can be changed in your config.ini ).

As I said, I often start my day by combing through the daily security news and bug bounty write-ups on /r/netsec, that day I saw an article about DNS rebinding in IOT devices. This reminded me about a NorthSec 2018 talk by two Akamai security researchers. During that talk they discussed a new tool they’ve developed which significantly increases the performance and usability of DNS rebinding attacks. I heard about DNS rebinding some ten years ago, but it wasn’t until Tavis Ormandy tweeted about a bug in Blizzard’s daemon and later about the same problem in many Torrent clients that this old (circa 1996) vulnerability category became hot again.

That morning it didn’t take long for me to add it all up… What if keosd was vulnerable to DNS rebinding and given that it accepts EOS signing transaction for 15 minutes after passphrase prompt, that would be a credible remote attack for a threat actor to pull off against whales. That same afternoon, I tested a few basic assumptions by sniffing the plaintext HTTP requests with Wireshark to see how the requests were structured and noticed that while cleos was sending the HTTP request Host header, it was not compliant RFC7230 section 5.4 (i.e. the keosd listening port 8900 was not specified)…

Wireshark packet capture of loopback interface showing HTTP request between `cleos` and `keosd`

I quickly added an /etc/hosts file entry for 127.0.0.1 example.com so I could validate my hypothesis using curl . My hands must’ve been a little bit sweaty as I typed curl -X POST http://example.com:8900/v1/wallet/get_public_keys and saw the public key for my test wallet! Everything indicated that it was exploitable.

Then I considered trying the new tool from the Akamai guys, but I was lazy and their public service did not appear to be up. That’s when I found out about whonow tool on GitHub that essentially offered the same functionality, but was readily usable without deploying anything special ahead of time (http://rebind.network).

I quickly spun up a Google Cloud compute instance with a static public IP and crafted the URL with magic hostname that would first have configurable and deterministic DNS resolution behavior

The first part specifies that it should resolve to 35.23.3.13 once, then resolve to 127.0.0.1 (loopback where keosd is waiting on port 8900 ) and repeat in a loop. Basically this tool / service acts as an authoritative DNS server for the zone rebind.network and resolves by following the specified algorithm in the domain tokens.

I then proceeded to set up a virtual host with nginx to listen on port 8900 and slapped together some ugly bits of HTML and Javascript for the POC.

<html>

<body>

<script>

console.log("Loaded from 35.203.35.123:8900");

setTimeout(function() {

fetch("/v1/wallet/get_public_keys", { method: "POST" })

.then(function(r) {

return r.json();

})

.then(function(json) {

alert(json);

});

}, 30000); // Wait 30 seconds

</script>

</body>

</html>

I then ran cleos wallet unlock once to unlock the keosd wallet for 15 minutes and opened Chrome to test it out.

Hooray!