[Decentralized Application Security Project (DASP)] (http://www.dasp.co/index.html)
1. Reentrancy (recursive call vulnerability call to the unknown)
function withdraw(uint _amount) {
require(balances[msg.sender] >= _amount);
msg.sender.call.value(_amount)();
balances[msg.sender] -= _amount;
}
- msg.sender 가 smart contract 였을때 해당 smart contract가 실행이 되는데 이때 Fallback 함수에서 다시 withdraw 를 실행하게 되면 무한으로 계속 인출 되게 된다. (다오 사건)
2. Access Control
function initContract() public {
owner = msg.sender;
}
- public으로 외부에서 아무나 접근할 수 있어서 문제가 발생.
function () payable {
if (msg.value > 0)
Deposit(msg.sender, msg.value);
else if (msg.data.length > 0)
_walletLibary.delegatecall(msg.data);
}
contract WalletLibary is WalletEvents {
function initMultiOwned(address[] _owners, uint _required) {
}
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initMultiOwned(_owners, _required);
}
}
- delegate call 을 통해서 라이브러리에 있는 함수를 호출해서 문제 발생. (Parity Multi-sig Wallet)
3. Arithmetic Issues
function withdraw(uint _amount) {
require(balances[msg.sender] - _amount > 0);
msg.sender.transfer(_amount);
balances[msg.sender] -= _amount;
}
- 위코드는 실제로 내 잔고 보다 _amount 양이 많다면 결과는 마이너스 값을 가지게 된다. 하지만 _amount 타입이 unsigned integer 이므로 마이너스 를 표현하기 위해서 어마무시한 큰 숫자로 변하게 된다.
- openzeppelin safeMath lib 사용 하여 방지. (https://github.com/OpenZeppelin/openzeppelin-solidity)
4. Unchecked Return Values For Low Level Calls
function withdraw(uint256 _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] -= _amount;
etherLeft -= _amount;
msg.sender.send(_amount);
}
- msg.sender.send()를 할때 문제가 발생할때에 대한 예외가 필요하다.
- msg.sender.transfer 를 사용하거나 msg.sender.send 의 반환값을 확인하여 성공 유무에 따라서 별도의 예외처리가 필요하다.
5. Denial of Service
function becomePresident() payable {
require(msg.value >= price); // must pay the price to become president
president.transfer(price); // we pay the previous president
president = msg.sender; // we crown the new president
price = price * 2; // we double the price to become president
}
function selectNextWinners(uint256 _largestWinner) {
for (uint256 i = 0; i < largestWinner, i++) {
// heavy code
}
largestWinner = _largestWinner;
}
- Gas Limit에 도달했거나 예상치 못한 throw 를 실행시켰을 때 사용할 수 없는 계약이 되어버리는 취약점.
- 악성 사용자가 Gas를 소모하게 되는 트랜잭션에 특정 조건을 증가시키게 되면 필요한 Gas 양이 Gas Limit 에 초과가 되면며 실행이 되지 못한게된다.
6. Bad Randomness
uint256 private seed;
function play() public payable {
require(msg.value >= 1 ether);
iteration++;
uint randomNumber = uint(keccak256(seed + iteration));
if (randomNumber % 2 == 0) {
msg.sender.transfer(this.balance);
}
}
function play() public payable {
require(msg.value >= 1 ether);
if (block.blockhash(blockNumber) % 2 == 0) {
msg.sender.transfer(this.balance);
}
}
- Contract 내 생성된 랜던값은 모든 노드들이 같은 결과물을 실행해야 하기 때문에 의 랜덤의 결과값이 같아야 하므로 하드웨어적인 랜던값을 사용하지 못하고 TimeStamp, Seed, Block을 기준으로 랜던값을 생성해 내게 된다.
- 위 코드는 짝수일 경우 Ethereum 을 보내게 되는데 악의적인 Smart Contract가 짝수일때만 요청을 보내게 되면 항상 이기게 된다.
7. Front-Running
1. Alice는 일정량의 가스를 포함한 트랜잭션을 수행한다.(해결책 포함)
2. 네트워크에있는 누군가는 Alice의 트랜잭션(해결책 포함)을 보고 더 높은 가스 가격으로 제출하고 최종 마이닝 되기를 기다린다.
3. 마이너들은 더 높은 수수료를 받기위해서 두번째 트랜잭션을 채택하게 되고, 결국 공격자의 트랜잭션이 선택되어 지게된다.
- 이더리움 마이너 들은 외부 소유 주소 (EOA)를 대신하여 코드 실행에 대한 가스 요금을 통해 항상 보상을받으며, 사용자는 거래를보다 신속하게 채굴 할 수 있도록 더 높은 수수료를 지정할 수 있다.
Ethereum 블록 체인은 공개되어 있으므로 누구나 다른 사람들의 보류중인 거래 내용을 볼 수 있다. 즉, 특정 사용자가 퍼즐이나 다른 중요한 비밀에 대한 솔루션을 공개하는 경우 악의적 인 사용자가 솔루션을 훔쳐 원래 거래를 선점하기 위해 더 높은 수수료로 거래를 복사 할 수 있게된다.
8. Time manipulation
function play() public {
require(now > 1521763200 && neverPlayed == true);
neverPlayed = false;
msg.sender.transfer(1500 ether);
}
- 특정시간에 선착순 한명한테 보상을 준다고 하였을때 시간을 조작해서 우선순위를 조작할 수 있다.
9 Short Address Attack
- Ethereum Address 는 20byte로 인자값 32byte는 아래와 같이 채워지게 된다.
(함수의해쉬값[4byte], Zero-padding[12byte] + Address[29byte] , 함수의 인자2 [32byte] )
문제는 Address 주소가 19byte 뿐이라면 데이타구조가 (함수의해쉬값 [4byte], Zero-padding[12byte] + Address[19byte] , 함수의 인자2 [33byte] ) 가 되게되므로 amount 양이 8bit왼쪽으로 이동하게되어 원래 보내려고 하던 양보다 256배 큰수가 된다. 따라서 web3 library 에서 check address 로 주소가 20byte 인지 확인을 해서 개발을 해야한다. https://web3js.readthedocs.io/en/1.0/web3-utils.html