This codelab will walk you through levels in the Security Innovation CTF that are subject to denial-of-service attacks. If you'd like more information on smart contract vulnerabilities, I would recommend visiting https://github.com/sigp/solidity-security-blog or viewing the associated screencast lectures available from class.

What you'll learn

What you'll need

The level contract emulates a contract for managing music records. The contract consists of a Manager contract, a Royalties contract, and the RecordLabel contract. These contracts manage how profits are distributed amongst the artist and the manager. Having legal contracts such as this one be implemented via a smart contract is one of the proposed use cases for smart contracts on Ethereum. As this level shows, it can be difficult to implement securely on the first try.

The level contract is shown below. The constructor creates two other contracts

contract RecordLabel is CtfFramework {
  using SafeMath for uint256;
  uint256 public funds;
  address public royalties;

  constructor(address _ctfLauncher, address _player) public payable CtfFramework(_ctfLauncher, _player)
  {
    royalties = new Royalties(new Manager(_ctfLauncher), _player); 
    funds = funds.add(msg.value);
  }

  function() external payable ctf {
    funds = funds.add(msg.value);
  }

The withdrawFunds...() call allows authorized accounts (i.e. the artist) to initiate a payout of size _withdrawAmount. This must be split between artist, manager, and other receivers of royalties. When one sends _withdrawAmount to the Royalties contract, it will divvy up the amount. Specifically, the Royalties contract will payout Manager and other receivers, then send the money remaining from _withdrawAmount back to RecordLabel contract. This is done by calling back to collectRemainingFunds() of the RecordLabel's contract.

To support this operation, the Royalties contract provides the getLastPayoutAmountAndReset() call to determine how much was distributed to Manager and other receivers. It then subtracts from the initial _withdrawAmount to determine what to pay the artist before then transferring the remaining funds back to artist after the royalty payment. The code for both functions are shown below.

function withdrawFundsAndPayRoyalties(uint256 _withdrawAmount) external ctf {
  require(_withdrawAmount<=funds, "Insufficient Funds in Contract"); 
  funds = funds.sub(_withdrawAmount);
  royalties.call.value(_withdrawAmount)();
  uint256 royaltiesPaid = Royalties(royalties).getLastPayoutAmountAndReset();
  uint256 artistPayout = _withdrawAmount.sub(royaltiesPaid);
  msg.sender.transfer(artistPayout);
}
function collectRemainingFunds() external payable {
  require(msg.sender == royalties, "Unauthorized: Not Royalties Contract");
} 

This contract specifies an owner that will be sent ETH upon withdrawal. It has a payable fallback function to receive ETH. As an artist, you might not like how large of a cut your manager makes out of your work, so perhaps you'd like to find a way to bypass paying it.

contract Manager{
  address public owner;
  constructor(address _owner) public {
    owner = _owner;
  }
  function withdraw(uint256 _balance) public {
    owner.transfer(_balance);
  }
  function () public payable {
  }
} 

The Royalties contract contains the logic for divvying up the proceeds of record sales. It has a collectionsContract variable that specifies the address of the contract that receives payments (e.g. the RecordLabel contract). It also contains the address of the artist, a mapping of royalty receivers, a mapping of receiver addresses to their percentage of the take (receiverToPercentOfProfit), an amountPaid to determine what is leftover after royalties are paid (that should go back to the artist via the RecordLabel contract), and a percentRemaining to track leftover royalties (to be paid to artist)

contract Royalties{
  using SafeMath for uint256;
  address private collectionsContract;
  address private artist;
  address[] private receiver;
  mapping(address => uint256) private receiverToPercentOfProfit;
  uint256 private percentRemaining;
  uint256 public amountPaid;

The constructor for the contract sets the collectionsContract variable to address of the creator (the RecordLabel contract) as well as the artist address. It then adds the _manager address to receiverToPercentOfProfit mapping and sets the take to 80% before updating the percentRemaining variable.

constructor(address _manager, address _artist) public {
        collectionsContract = msg.sender; 
        artist=_artist;
        receiver.push(_manager); 
        receiverToPercentOfProfit[_manager] = 80; 
        percentRemaining = 100 -receiverToPercentOfProfit[_manager];
}

The Royalties contract implements modifiers to protect certain functions so they may only be called by the RecordLabel contract or the artist.

modifier isCollectionsContract() {
  require(msg.sender == collectionsContract, "Unauthorized: Not Collections Contract");
  _;
}
modifier isArtist(){
  require(msg.sender == artist, "Unauthorized: Not Artist");
  _;
}

It also includes a payable fallback function that ensures that it can only be sent money by the RecordLabel contract. It then calls payoutRoyalties() function within contract.

function () public payable isCollectionsContract { 
  payoutRoyalties();
}

The payoutRoyalties() function goes through the mapping of receivers, sends each their share of what has been paid out, updates amountPaid to track the amount of royalties paid out, and sends what is left back to the sender (i.e. the RecordLabel contract) via its collectRemainingFunds() call.

function payoutRoyalties() public payable isCollectionsContract { 
  for (uint256 i = 0; i< receiver.length; i++){
    address current = receiver[i];
    uint256 payout = msg.value.mul(receiverToPercentOfProfit[current]).div(100);
    current.transfer(payout);
    amountPaid = amountPaid.add(payout);
  }
  msg.sender.call.value(msg.value-amountPaid)(bytes4(keccak256("collectRemainingFunds()")));
}

The addRoyaltyReceiver() function allows the artist to add Royalty receivers with a percentage of the take, updating the percentRemaining for book-keeping purposes

function addRoyaltyReceiver(address _receiver, uint256 _percent) external isArtist { 
  require(_percent<percentRemaining, "Precent Requested Must Be Less Than Percent Remaining");
  receiver.push(_receiver); 
  receiverToPercentOfProfit[_receiver] = _percent; 
  percentRemaining = percentRemaining.sub(_percent);
}

The function also implements a function which allows the RecordLabel contract to determine how much was paid out from the last payout (so the contract can pay the artist the difference). The code is shown below:

function getLastPayoutAmountAndReset() external isCollectionsContract returns(uint256){
  uint256 ret = amountPaid;
  amountPaid = 0;
  return ret;
}

In examining the code, the key data structure is the receiverToPercentOfProfit mapping that determines the percentage of the payout, each receiver is given. Note that with the code that has been given, the artist has the ability to modify the mapping. If the artist has the ability to change the Manager's value in the mapping, then the mechanism can be used to recover the 80% of the cut that is sent to that contract, leading to denial-of-service.

In order to attack the contracts, first find their addresses via Etherscan. Copy the level contract's address via the UI and bring its creation transaction up. View the internal transactions associated with the contract's creation to see that along with the original RecordLabel contract creation, the Royalties and Manager contracts are created as well.

Click on the transactions to find the Manager contract creation and get its address.

We want to find the address of the owner of the Manager contract. This is presumably our manager's wallet address. To do so, we'll need to interact with the Manager contract that has been deployed on the blockchain. We do not initially have access to the Manager contract's ABI, but we can easily construct it.

Paste the Manager code into Remix, compile, then go to details to get its ABI.

Use the Manager contract address and ABI to access contract on MyCrypto.

Interact with contract to get its owner (i.e. address of manager)

Repeat the same process with the Royalties contract in order to access its functions via MyCrypto.

The artist has the ability to add addresses to the mapping that controls the percentages of profit and can go to the Royalties contract to add the manager as a receiver. This will allow the artist to overwrite the previously set _percent for the manager from 80 to 0 and thus, cut the manager out. The fatal flaw of the contract is that it did not expect manager to be added as a royalty receiver!

From here, you can then visit the original RecordLabel contract to receive 100% of the royalties and complete the level.

The ability to manipulate state in a smart contract must be tightly controlled. As shown in this level, when access control is done improperly or when the contract fails to handle adversarial access, contracts can have their funds wiped out.

What we've covered