✅Intro to Security First Development
Last updated
Last updated
This video showcased some vulnerable contracts.
Vulnerable contract:
contract Auction {
address highestBidder;
uint256 highestBid;
function bid() payable {
require(msg.value > highestBid);
// @audit-issue Highest bidder's `receive()` could always revert -> DoS
highestBidder.transfer(highestBid);
highestBidder = msg.sender;
highestBid = msg.value;
}
}
Mitigation: Pull-over-Push pattern
contract Auction {
address highestBidder;
uint256 highestBid;
mapping(address => uint256) refunds;
function bid() payable {
require(msg.value > highestBid);
if (highestBidder != 0) {
refunds[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
}
function withdrawRefund() external {
uint256 refund = refunds[msg.sender];
refunds[msg.sender] = 0;
msg.sender.transfer(refund);
}
}
Vulnerable contract:
struct payee {
address addr;
uint256 value;
}
Payee[] payees;
function payOut() external {
uint256 i;
// @audit-issue Long `payees` could exceed block gas limit
while (i < payees.length) {
payees[i].addr.send(payees[i].value);
i++;
}
}
Mitigation: Batches
struct payee {
address addr;
uint256 value;
}
Payee[] payees;
uint256 nextPayeeIndex;
function payOut(uint256 batchSize) external {
uint256 i = nextPayeeIndex;
while (i < nextPayeeIndex + batchSize) {
payees[i].addr.send(payees[i].value);
i++;
}
nextPayeeIndex = i;
}
Vulnerable contract:
contract Bank {
mapping(address => uint) private userBalances;
// @audit-issue Does not follow checks-effects-interactions pattern
function withdrawBalance() external {
uint256 amountToWithdraw = userBalances[msg.sender];
require(msg.sender.call{value: amountToWithdraw}(""));
userBalances[msg.sender] = 0;
}
}
Mitigation: Checks-Effects-Interactions pattern
contract Bank {
mapping(address => uint) private userBalances;
// @audit-issue Does not follow checks-effects-interactions pattern
function withdrawBalance() external {
uint256 amountToWithdraw = userBalances[msg.sender];
userBalances[msg.sender] = 0;
require(msg.sender.call{value: amountToWithdraw}(""));
}
}
Vulnerable:
pragma solidity ^0.7.0;
contract Token {
mapping (address => uint256) public balanceOf;
function transfer(address _to, uint256 _value) external {
/* Check if sender has balance */
// @audit-issue Does not check for the underflow case
require(balanceOf[msg.sender] - _value >= 0);
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
// @audit-issue Could overflow
balanceOf[_to] += _value;
}
}
Mitigation:
pragma solidity ^0.7.0;
contract Token {
mapping (address => uint256) public balanceOf;
function transfer(address _to, uint256 _value) external {
/* Check if sender has balance and for overflows/underflows */
require(balanceOf[msg.sender] >= _value);
require(balanceOf[_to] + _value >= balanceOf[_to]);
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
}
Vulnerable contract:
contract TokenStore {
uint256 public price;
ERC20 public token;
address public owner;
function updatePrice(uint256 _price) {
if (msg.sender == owner) {
price = _price;
}
}
function buy(uint256 quant) returns (uint256) {
require(msg.value > quant * price);
token.transfer(msg.sender. msg.value / price);
}
}
Attacker can front-run user's buy()
transaction by calling updatePrice()
and pay higher gas. Attacker can set price
much higher so that user receives no tokens.
Mitigation:
Permissions on key functions
Buyer specifies price
Timelocks
Vulnerable contract:
contract GetRandomness {
function random() public returns (uint256 result) {
// randomly choose a recent blocknumber
// @audit-issue `block.number` can't be used to generate randomness
uint256 seed = uint256(block.blockhash(block.number));
uint256 lookBack = uint256(seed) % 300;
bytes32 hash = block.blockhash(lookBack);
uint256 randomNumber = uint256(hash);
return uint256(randomNumber % 100);
}
}
Mitigation: Use Chainlink VRF.
Vulnerable contract:
contract EthWrapperToken {
...
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
...
function transfer(address _to, uint256 _value) {
/* Check if sender has balance */
require(balanceOf[msg.sender] - _value >= 0);
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
// @audit-issue Attacker can forcefully send ethers to this contract
require(this.balance == totalSupply);
}
}
Mitigation: Use OpenZeppelin ERC20.