Bancor Hack
The Story
On July 9, 2018, Bancor, one of the most prominent decentralized exchanges at the time, suffered a security breach when a wallet used to upgrade smart contracts was compromised. The attackers used this wallet to steal approximately $23.5 million worth of cryptocurrency, including $12.5 million in Ether (ETH), $1 million in Pundi X (NPXS), and $10 million in Bancor's native token (BNT).
In response to the attack, Bancor took the controversial step of freezing the stolen BNT tokens by using a backdoor function built into their smart contracts. While this prevented the theft of $10 million worth of BNT, it raised significant concerns in the cryptocurrency community about Bancor's level of centralization. Many critics argued that a truly decentralized exchange should not have the ability to freeze or control user funds.
The incident sparked a heated debate about the tradeoffs between security and decentralization in blockchain projects. While Bancor successfully limited their losses, they couldn't recover the ETH or NPXS tokens, as they had no such control over those blockchains. The hack became a pivotal case study in how emergency backdoors in smart contracts can be both beneficial for security and contrary to decentralization principles.
Technical Analysis
The Bancor hack was primarily a wallet security breach rather than an exploitation of smart contract code vulnerabilities. However, the key technical aspects involved:
- The attackers gained access to a wallet that had privileges to execute functions on Bancor's smart contracts
- This wallet had authorization to call critical functions, including withdrawal and token issuance
- Once the wallet was compromised, the attackers could directly drain funds from Bancor's contracts
The situation was made possible by privileged functions in the contract:
// Simplified representation of the vulnerable setup
contract BancorToken is ERC20, Ownable {
bool public transfersEnabled = true;
// The backdoor that allowed Bancor to freeze tokens
function disableTokenTransfers(bool _disable) public onlyOwner {
transfersEnabled = !_disable;
}
// Transfer function with centralized control
function transfer(address _to, uint256 _value) public returns (bool) {
require(transfersEnabled, "Transfers disabled");
// Standard ERC20 transfer logic
// ... code omitted for brevity
}
// The function that allowed the attacker to withdraw funds
function withdrawTokens(address _token, uint256 _amount) public onlyOwner {
ERC20(_token).transfer(owner, _amount);
}
}
The critical security issues were:
- A single wallet had extensive privileges over the protocol's smart contracts
- This administrative wallet was not secured using a multi-signature scheme
- The contract included functions that allowed direct withdrawal of user funds
- The same privileged functions that enabled the theft also enabled Bancor to freeze the stolen BNT
Lessons Learned
- Administrative wallets should be secured using multi-signature schemes
- Privileged functions in contracts should require multiple independent authorizations
- The principle of least privilege should be applied to smart contract design
- Emergency functions in contracts should be carefully designed with appropriate checks and balances
- Projects should be transparent about centralized control mechanisms in supposedly decentralized systems