Intro: Challenge 1
Sjors Provoost has put up the original challenge with the intent “to hone [his] skills at writing secure smart contract code.” He funded the contract with ~ 9.5 ETH for there to be a real bounty for completing the challenge (i.e. hacking it). The code is simple and doesn’t leave much to imagination, though.
…It even keeps the money if the receiving end is another dapp that does, well, almost anything. That has become “classic” fast, in both theoretical attack vectors, and practical malicious ones.
However, it could also be normal for a well-meaning dapp. These
days, it’s a wallet. But say you’re a self-flying postal drone
trying to withdraw the client’s tokens to pay for electricity, and
doing some accounting in the process. Remember, as a drone you don’t
get to send your own transactions, and might as well piggy-back on
client-initiated transactions (if you know the token you’re withdrawing
call() instead of
Vulnerable: Challenge 2
Anyway, Sjors put another challenge up, and funded it with ~ 10 ETH, that should allow for described behaviour. In that, he had this:
In the Ethereum language Solidity, all functions are
public by default.
public functions can be called by anyone - that is, any account.
The obvious way to sweep this contract would then be to
start an Ethereum client, load the contract’s ABI, and call
withdrawEtherOrThrow(...) with the desired amount.
The day before I found a possible 50/50 drain vector in
TokenWithInvariants example; I couldn’t provide
code then, since I didn’t know the dapp deployment procedure, and
had done zero actual Solidity programming. This time, I decided I’d
write a contract to do the sweep. Here it is, in the form it’s been
deployed onto the blockchain.
The interesting part is, of course, the fallback function (the nameless one). It should have worked like this:
- I send a transaction from account
noelwith anything but 10 ether attached to it.
chal2sweepmakes a call to the vulnerable
DaoChallengemethod and requests a withdrawal of 10 ether.
DaoChallengehonours the request and sends it to
msg.sender, which is again
- That hits the fallback function of
chal2sweepagain, but since the amount is exactly 10 ether, the body is not executed.
Any updates I do to it will be pushed there. Of those, I can immediately think of:
- being able to change the target address - although this is kind of useless without being able to specify the target’s vulnerable function, and perhaps the amount;
- using the
only_noelmodifier instead of
msg_value_not(...)- after all, I only want my calls to result in further malicious calls.
I never got to test if that code works as intended, because someone
beat me to it. In particular, I never got to test if
chal.call("withdrawEtherOrThrow", 10000000000000000000) is the correct
It was somewhat disappointing that some schmuck, who probably even wouldn’t share his exploit, beat me to it by less than 30 minutes. (I assumed it was a “schmuck”, since they didn’t come public immediately after - not on Medium, Reddit, or GitHub.)
In retrospect, a de-facto bug bounty is much motivation for entry-level people like myself. No one in their right minds would hire us for fixed-salary code audit, but money on the line is good enough for all-or-nothing, learn-as-you-go activities.
I might just fire up a testnet node again to test all my sloppy code. And other folk’s, too.