버그헌팅하다가 문제 푸는게 좀 늦어졌다.
마찬가지로 컨트랙트의 ownership을 획득해야하는 문제로 보인다
Remix IDE가 도움이 될거라고 하는데 코드 분석을 하고 한번 Remix에 돌려보자.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import 'openzeppelin-contracts-06/math/SafeMath.sol';
contract Fallout {
using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}
저번 문제보다는 코드가 좀 짧다.
우선
import 'openzeppelin-contracts-06/math/SafeMath.sol';
SafeMath.sol 이라는 파일을 import해서 사용하려는 것으로 보인다.
using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;
using X for Y;
는 X 라이브러리를 Y 타입으로 사용한다는 뜻이다.
SafeMath는 openzeppelin에서 개발한 라이브러리인 모양이다. github에서 소스코드를 찾아볼 수 있었다.
SafeMath.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol) pragma solidity ^0.8.0; // CAUTION // This version of SafeMath should only be used with Solidity 0.8 or later, // because it relies on the compiler's built in overflow checks. /** * @dev Wrappers over Solidity's arithmetic operations. * * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler * now has built in overflow checking. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } } /** * @dev Returns the subtraction of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b > a) return (false, 0); return (true, a - b); } } /** * @dev Returns the multiplication of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } } /** * @dev Returns the division of two unsigned integers, with a division by zero flag. * * _Available since v3.4._ */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a / b); } } /** * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. * * _Available since v3.4._ */ function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a % b); } } /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { return a + b; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return a - b; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { return a * b; } /** * @dev Returns the integer division of two unsigned integers, reverting on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return a / b; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * reverting when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return a % b; } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {trySub}. * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { unchecked { require(b <= a, errorMessage); return a - b; } } /** * @dev Returns the integer division of two unsigned integers, reverting with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { unchecked { require(b > 0, errorMessage); return a / b; } } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * reverting with custom message when dividing by zero. * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {tryMod}. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { unchecked { require(b > 0, errorMessage); return a % b; } } }
생성자
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
allocate function
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
allocate 함수는 payable 함수이고 msg.sender가 전송한 이더를 allocations[msg.sender] 매핑 변수에 더해주는 함수
sendAllocation function
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
sendAlloction 함수는 address를 매개변수로 받고 함수가 실행되기 위한 조건이 하나 붙는다. allocations[allocator] 매핑 값이 0보다 크면 transfer 함수로 allocator에게 allocations[allocator] 매핑의 값을 전송한다.
collectAllocations function
function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}
owner만 호출할 수 있고 msg.sender에게 this의 balance를 전송한다.
allocatorBalance function
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
현재 allocator의 allocations 매핑 변수 값을 알려준다.
코드가 짧아 분석은 이렇게 단간하긴한데, 어디가 취약점인지 아직 모르겠다.
그래서 SafeMath와 문제 소스코드를 가져와서 Remix IDE에 넣고 컴파일 후 실행시켜보았다.
이렇게 나오는데 함수의 개수가 좀 많다. 위에 코드랑 비교해보니 하나 더 많은데
Fal1out
이라는 함수가 존재한다. 이게 코드 분석할 때 저번 문제와 너무 비슷해서 그냥 넘어갔는데, 다시 한 번 보자.
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
생성자라고 주석도 달아놔서 아 생성자구나 하고 넘어갔는데 function이 붙은거 보니 함수네?
코드를 보면 그냥 컨트랙트 호출자를 owner로 만들어준다. 바로 이 함수 이용하면 ownership을 획득할 수 있다.
그러면 현재 contract의 owner는 0x00000000000000000000이니 Fal1out 함수를 호출하여 owner를 바꿀 수 있는 지 보자
명령어는 다음과 같이 입력했다. 함수가 payable이기 때문에 이후 호출할 collectAllocations함수를 이용하기 위해 sendTransaction함수로 0.0001 이더를 전송하였다.
await contract.Fal1out.sendTransaction({from:"0x3501a130831dd493CaBf25FEe9a7Ad4D1F7A3e35",to:"0x1fce3f95e036C0aB2D6FF9210282B5F847C4c32E",value:toWei("0.0001")})
owner가 나의 주소로 바뀐 것을 확인할 수 있었다.
이제 onlyOwner만 호출할 수 있는 collectAllocations 함수로 내가 보낸 이더를 다시 나에게 전송하면 문제를 풀 수 있을 것 같다.
중간에 명령어 하나 입력한게 있는데 이건 무시하면 되고, collectAllocations 함수 호출이 완료되었으니 submit을 진행하면
문제를 풀었다는 메시지가 나온다.
Uploaded by N2T