DASP TOP 10
웹에서 보안 진단을 위해 사용되는 OWASP Top 10 개념과 비슷한 DASP Top 10에 대해 간략히 정리한다.
DASP(Decentralized Application Security Project) Top 10은 영국의 NCCGroup에서 정립하였다.
이는 스마트 컨트랙트 관련 취약점 중 Top 10을 의미한다.
NCCGroup에 따르면 "Ethereum은 블록체인 기술에서 새롭고 급성장하는 분야를 만들었지만, 스마트 계약을 둘러싼 보안 문제는 개발에 도전하고, 많은 유망한 프로젝트를 파괴했으며, 스마트 계약에 대한 대중의 신뢰를 떨어뜨렸다. 웹 애플리케이션과 달리 Ethereum은 돈과 직접 연결되며, 이는 보통 재정적 손실을 초래한다. 이러한 엄청난 경제적 손실은 대부분의 분야에서 어려움을 겪었거나 아직 달성되지 않은 보안 감사를 야기했다. 보안계가 암호화와 스마트 계약을 중심으로 조직하고 싶어하기 때문에, 우리는 이러한 문제에 대한 지식을 개발자 커뮤니티에 신속히 전파하기를 희망한다. 이를 달성하기 위해, 모든 스마트 계약 보안 문제와 실제 환경에서 발견된 스마트 계약 취약점 중 상위 10개의 목록을 작성했다. Ethereum은 여전히 빠르게 발전하고 있으므로 새로운 취약성이 발견될 것으로 예상되며 DASP Top 10은 새로운 개발을 반영해야 한다. 우리는 DASP가 Ethereum 스마트 계약의 보안 위치를 개선하는 데 중요한 역할을 하기를 바란다."라고 한다.
[ 출처: https://dasp.co ]
- DASP Top 10
1. Reentrancy
- Reentrancy Attack (재진입성 공격) : 검증 및 신뢰되지 않은 외부 스마트 컨트랙트가 보안에 취약한 함수를 호술하고, 다시 취약성을 가지는 컨트랙트로 진입 하는 것
Ethereum 취약점인 Reentrancy 공격이 처음 발견되었을 때 모두를 놀라게 했다.
그것은 Ethereum의 하드 포크로 이어진 수백만 달러의 강탈 과정에서 처음 공개되었다.
Reentrancy는 최초 실행이 완료되기 전에 외부 계약 호출가 통화 계약에 새로운 호출을 걸 수 있도록 허용된 경우에 발생한다.
함수의 경우, 신뢰할 수 없는 계약에 대한 호출이나 외부 주소에 대한 낮은 수준의 함수의 사용의 결과로 계약 상태가 실행 도중에 변경될 수 있다는 것을 의미한다.
Timeline of discovery:
Date
|
Event
|
Jun 5, 2016
|
|
Jun 9, 2016
|
|
Jun 12, 2016
|
|
Jun 17, 2016
|
|
Aug 24, 2016
|
Real World Impact:
Example:
1. 스마트 계약은 다수의 외부 주소의 잔액을 추적하고, withdraw() 함수로 사용자가 자금을 회수할 수 있도록 한다.
2. 악의적인 스마트 계약은 withdraw() 함수를 사용하여 잔액 전체를 회수한다.
3. 피해자 계약은 악성계약 잔액을 갱신하기 전에 call.value(amount)() 저수준 함수를 실행하여 악성계약에 에테르를 보낸다.
4. 악의적인 계약은 자금을 수수한 다음 피해자 계약의withdraw() 함수를 다시 호출하는 미지급 fallback() 함수가 있다.
5. 이 두 번째 실행은 자금 이체를 유발한다: 기억하라, 악의적인 계약의 잔액은 아직 첫 번째 인출에서 갱신되지 않았다. 결과적으로 악의적인 계약은 두번째로 잔액을 모두 회수하는 데 성공한다.
Code Example:
다음 함수는 reentrancy 공격에 취약한 함수를 포함한다. 로우 레벨 call() 함수가 msg.sender 주소로 에테르를 보내면 취약해지고, 주소가 스마트 컨트랙트인 경우 지불은 거래 가스의 남은 것으로 폴백 기능을 촉발한다:
function withdraw(unit _amount){
require(balances[msg.sender] >= _amount);
msg.sender.call.value(_amount)();
balances[msg.sender] -= _amount;
}
|
Additional Resources:
How Someone Tried to Exploit a Flaw in Our Smart Contract and Steal All of Its Ether
2. Access Control
- 패리티 월렛 라이브러리 계약을 일반 멀티시그 지갑으로 바꾸고 initWallet 함수를 호출해 그 소유자가 되는 것이 가능했다.
접근 제어 문제는 스마트 계약(smart contracts)뿐만 아니라 모든 프로그램에서 공통적이다. 사실 OWASP Top 10의 5번이다. 보통 계약의 기능성은 공공기능이나 외부기능을 통해 접근한다. 보안되지 않은 가시성 설정은 공격자에게 계약의 개인 가치나 논리에 접근할 수 있는 간단한 방법을 제공하지만, 접근 제어 우회는 때때로 더 미묘하다. 이러한 취약성은 계약이 더 이상 사용되지 않는 tx.origin을 사용하여 호출자의 유효성을 검사하고, 긴 요구 사항으로 대규모 권한 부여 논리를 처리하며, 대리 라이브러리 또는 대리 계약에서 대리자 호출을 무모하게 사용할 때 발생할 수 있다.
손실: 15만 ETH(당시 약 3천만 USD)로 추정됨
Real World Impact:
Example:
1. 스마트 계약(smart contracts)은 그것을 초기화하는 주소를 계약의 소유자로 지정한다. 이는 계약자금 회수 능력 등 특권을 부여하기 위한 일반적인 패턴이다.
2. 불행히도 초기화 기능은 이미 호출된 후에도 누구나 호출할 수 있다. 누구든지 계약의 소유자가 되어 그 자금을 가져갈 수 있도록 허용한다.
Code Example:
다음 예에서 계약의 초기화 함수는 해당 기능의 발신자를 소유자로 설정한다. 그러나 이 논리는 계약시공자로부터 분리되어 있으며, 이미 호출되었다는 사실을 추적하지 못하고 있다:
function initContract() public {
owner = msg.sender;
|
Additional Resources:
Fix for Parity multi-sig wallet bug 1
On the Parity wallet multi-sig hack
3. Arithmetic
- 오버플로우 상태는 부정확한 결과를 제공하며, 특히 가능성이 예상되지 않은 경우 프로그램의 신뢰성과 보안을 손상시킬 수 있다. (Jules Dourlens)
정수 오버플로우와 언더플로우는 새로운 취약성은 아니지만, 특히 스마트 계약에서 위험하며, 서명되지 않은 정수가 널리 퍼져 대부분의 개발자는 단순한 int유형(흔히 서명된 정수)에 익숙하다. 만약 오버플로우가 발생하면, 많은 양성 추적 코드패스가 도난이나 서비스 거부의 벡터가 된다.
Real World Impact:
BatchOverflow (multiple tokens)
ProxyOverflow (multiple tokens)
Example:
1. 스마트 계약의 withdraw () 함수를 사용하면 작업 후에 잔액이 플러스(+)로 유지되는 한 계약서에 기부된 에테르를 회수할 수 있다..
2. 공격자가 현재 잔액보다 많은 금액을 인출하려고 한다.
3. withdraw () 함수 검사 결과는 항상 플러스 금액으로, 공격자가 허용한 것보다 더 많이 인출할 수 있다. 그 결과 균형은 언더플러우되고, 그것보다 더 큰 크기의 순서가 된다.
Code Example:
가장 간단한 예는 정수 언더플로우를 확인하지 않는 기능으로, 무한한 양의 토큰을 인출할 수 있다:
function withdraw(uint _amount) {
require(balances[msg.sender] - _amount > 0);
msg.sender.transfer(_amount);
balances[msg.sender] -= _amount;
}
|
두 번째 예(Undhandhood Solidity Coding Conference 중 발견됨)는 배열의 길이가 부호화되지 않은 정수로 표시되므로 쉽게 수행 할 수없는 오류이다:
function popArrayOfThings() {
require(arrayOfThings.length >= 0);
arrayOfThings.length--;
}
|
세 번째 예는 첫 번째 예제의 변형이며, 두 개의 부호 없는 정수에 대한 산술 결과가 부호 없는 정수인 경우:
function votes(uint postId, uint upvote, uint downvotes) {
if (upvote - downvote < 0) {
deletePost(postId)
}
}
|
네 번째 예에는 곧 사용할 수 있는 var 키워드가 포함되어 있다. var는 할당된 값을 포함하는 데 필요한 최소 유형으로 자체 변경되기 때문에 값 0을 보유하는 것이 uint8이 된다. 루프가 255회 이상 반복되도록 되어 있으면 그 숫자에 도달하지 못하고 실행이 가스가 떨어지면 정지한다:
for (var i = 0; i < somethingLarge; i ++) {
// ...
}
|
Additional Resources:
SafeMath to protect from overflows
4. Unchecked Low Level Calls
- 가능하면 낮은 수준의 "호출"을 사용하지 않아야 한다. 반환 값을 제대로 처리하지 않으면 예기치 않은 동작으로 이어질 수 있다. (Remix)
Solidity의 더 깊은 특징 중 하나는 낮은 수준의 함수 인 call (), callcode (), delegatecall () 및 send ()이다. 오류를 설명하는 이들의 행동은 전파(또는 거품이 일지)되지 않고 현재 실행의 전면적인 반전으로 이어지지 않기 때문에 다른 Solidity 기능과 상당히 다르다. 대신, 그들은 false로 설정된 부울 값을 반환하고, 코드는 계속 실행될 것이다. 이것은 개발자들을 놀라게 할 수 있고, 그러한 낮은 수준의 통화의 반환가치를 점검하지 않으면, 실패와 다른 원치 않는 결과로 이어질 수 있다.
"Remember, send can fail!"
Real World Impact:
Code Example:
다음 코드는 send()의 반환 값 확인을 잊어버렸을 때 무엇이 잘못될 수 있는지를 보여주는 예다. 호출을 사용하여 이를 수용하지 않는 스마트 계약에 에테르를 보내는 경우(예: 지불해야 할 예비 함수가 없기 때문에), EVM은 반환 값을 거짓으로 대체한다. 본 예에서는 반환 값을 확인하지 않기 때문에 함수의 계약 상태 변경은 되돌리지 않으며 etherLeft 변수는 결국 잘못된 값을 추적하게 된다:
function withdraw(uint256 _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] -= _amount;
etherLeft -= _amount;
msg.sender.send(_amount);
}
|
Additional Resources:
Scanning Live Ethereum Contracts for the "Unchecked-Send" Bug
5. Denial of Service (DoS)
- 서비스 거부는 이더리움 세계에서는 치명적이다. 다른 유형의 애플리케이션은 결국 복구될 수 있지만 스마트 계약은 이러한 공격 중 하나로 영원히 오프라인 상태가 될 수 있다. 거래의 수취인이 될 때 악의적으로 행동하는 것, 기능 계산에 필요한 가스를 인위적으로 증가시키는 것, 스마트 계약의 민간 구성요소에 접근하기 위해 접근 제어를 남용하는 것, 혼용과 과실 이용 등을 포함한 여러 가지 방법으로 서비스 거부를 초래한다. 이런 종류의 공격은 많은 다른 변종들을 포함하고 있으며 아마도 앞으로 많은 발전을 보게 될 것이다.
손실: 514,874 ETH(당시 약 3억 USD)로 추정됨
Real World Impact:
Example:
1. 경매 계약을 통해 사용자는 다른 자산에 입찰 할 수 있다..
2. 입찰하려면 사용자가 원하는 양의 에테르로 입찰(uint object) 함수를 호출해야 한다.
경매 계약은 대상의 소유자가 입찰을 수락하거나 최초 입찰자가 이를 취소할 때까지 에테르를 에스크로에 보관한다. 이는 경매 계약이 미해결 입찰의 모든 가치를 잔액에 담아야 한다는 것을 의미한다.
3. 경매계약서에는 관리자가 계약에서 자금을 회수할 수 있는 인출(입금액) 기능도 포함되어 있다. 함수가 하드코딩된 주소로 금액을 보내면서 개발자들은 함수를 공개하기로 했다.
4. 공격자는 잠재적인 공격을 보고 그 기능을 호출하여 계약서의 모든 자금을 관리자에게 지시한다. 이것은 에스크로의 약속을 파기하고 보류 중인 모든 입찰을 차단한다.
5. 관리자가 에스크로 처리한 돈을 계약서에 돌려줄 수도 있지만, 공격자는 단순히 자금을 다시 회수하는 것으로 공격을 계속할 수 있다.
Code Example:
다음 예 (이더 킹에서 영감을 얻음)에서 게임 계약의 기능을 사용하면 이전의 계약을 공개적으로 뇌물로 주면 대통령이 될 수 있다. 불행하게도, 전 대통령이 스마트 계약서이고 지불에 대한 복귀를 야기한다면, 권력 이전은 실패하고 악의적인 스마트계약서는 영원히 대통령으로 남아있을 것입니다. 나에게 독재처럼 들린다:
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
}
|
이 두 번째 예에서, 호출자는 다음 함수 호출이 누구에게 보상을 줄 것인지 결정할 수 있다. for 루프에서 값 비싼 명령어로 인해 공격자는 이더리움의 가스 블록 제한으로 인해 반복하기에는 너무 큰 숫자를 도입하여 함수의 작동을 효과적으로 차단할 수 있다:
function selectNextWinners(uint256 _largestWinner) {
for(uint256 i = 0; i < largestWinner, i++) {
// heavy code
}
largestWinner = _largestWinner;
}
|
Additional Resources:
Statement on the Parity multi-sig wallet vulnerability and the Cappasity token crowdsale
6. Bad Randomness
- 이더리움에서는 무작위성이 옳지 않다. Solidity는 예측하기 어려운 값에 액세스 할 수 있는 기능과 변수를 제공하지만 일반적으로 광부 영향을 받는 것보다 더 공개적입니다. 이러한 임의의 출처는 예측할 수 있기 때문에 악의적인 사용자는 일반적으로 이를 복제하여 예측할 수 없는 기능에 의존하는 기능을 공격 할 수 있다
손실: 400 ETH 이상
Real World Impact:
Example:
1. 스마트 컨트랙트는 블록 번호를 게임의 무작위 소스로 사용한다..
2. 공격자는 현재 블록 번호가 승자인지 확인하는 악의적인 계약을 만든다.
만약 그렇다면, 그것은 이기기 위해 첫 번째 스마트 계약을 부른다. 그 통화는 동일한 거래의 일부가 될 것이기 때문에, 두 계약에서 블록 번호는 그대로 유지될 것이다.
3. 공격자는 이길 때까지 악의적 인 계약을 콜 한다.
Code Example:
이 첫 번째 예에서는 개인 시드(Private Seed)를 반복 번호 및 kecak256 해시 함수와 조합하여 호출자가 승리하는지를 결정한다. 시드는 비공개이지만 특정 시점에서 트랜잭션을 통해 설정되어 블록 체인에서 볼 수 있어야 한다:
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);
}
}
|
이 두 번째 예에서는 block.blockhash를 사용하여 난수를 생성한다. blockNumber가 현재 block.number (명백한 이유로)로 설정되어 0으로 설정된 경우이 해시는 알 수 없다. blockNumber가 과거에 256 개 이상의 블록으로 설정된 경우 항상 0이 된다. 마지막으로 너무 오래된 이전 블록 번호로 설정된 경우 다른 스마트 계약은 동일한 번호에 액세스하고 동일한 거래의 일부로 게임 계약을 호출 할 수 있다:
function play() public payable {
require(msg.value >= 1 ether);
if (block.blockhash(blockNumber) % 2 == 0) {
msg.sender.transfer(this.balance);
}
}
|
Additional Resources:
Predicting Random Numbers in Ethereum Smart Contracts
7. Front Running
- 채굴자들은 항상 외부 소유 주소(EOA)를 대신하여 코드를 실행하면 가스 요금을 통해 보상을 받기 때문에, 사용자들은 더 높은 수수료를 지정하여 더 빨리 채굴하도록 할 수 있다. 이더리움 블록체인이 공개돼 있어 타인의 미결 거래 내용을 누구나 볼 수 있다. 주어진 사용자가 퍼즐이나 기타 귀중한 비밀에 대한 해결책을 공개하고 있다면, 악의적인 사용자가 솔루션을 훔치고 더 높은 수수료로 거래를 복사해 원래의 솔루션을 선점할 수 있다는 뜻이다. 스마트 계약 개발자가 신중하지 않으면 이런 상황이 현실적이고 파괴적인 전방 공격으로 이어질 수 있다.
Real World Impact:
Example:
1. 스마트 계약은 RSA 번호를 게시한다. (N = prime1 x prime2)
2. 올바른 prime1과 prime2를 가진 submitSolution () 공용 함수를 호출하면 호출자에게 보상한다.
3. Alice는 RSA 번호를 성공적으로 평가하고 솔루션을 제출한다.
4. 네트워크상의 누군가가 채굴 대기 중인 앨리스의 거래(솔루션 포함)를 보고 더 높은 가스 값으로 제출한다.
5. 두 번째 거래는 높은 유료로 인해 광부들이 먼저 픽업한다. 공격자가 상을 받는다.
Additional Resources:
Predicting random numbers in Ethereum smart contracts
Front-running, Griefing and the Perils of Virtual Settlement
8. Time Manipulation
- 토큰 판매 잠금에서 게임을 위한 특정 시간에 자금 잠금 해제에 이르기까지 계약은 때때로 현재 시간에 의존해야 한다. 이것은 일반적으로 현재 solidity에서 block.timestamp 또는 그 별칭을 통해 수행된다.
그러나 그 가치는 어디에서 오는가? 광부에서! 거래의 광부는 채굴이 발생한 시간을 보고하는 데 여유가 있기 때문에 현명한 계약은 광고 된 시간에 크게 의존하지 않는다. block.timestamp는 # 6에서 논의 된 것처럼 난수 생성에도 가끔 사용된다. #6. Bad Randomness.
Real World Impact:
Example:
1. 게임은 오늘 자정에 첫 번째 선수를 보상해 준다.
2. 악의적인 광부는 게임에 이기려는 시도를 포함시키고 타임스탬프를 자정으로 설정한다.
3. 자정 조금 전에 광부는 결국 그 블록을 채굴하게 된다. 실제 현재 시간이 자정까지(블록에 대해 현재 설정된 타임스탬프) "충분히 닫힘"인 경우, 네트워크의 다른 노드가 블록을 허용하기로 결정한다.
Code Example:
다음 기능은 특정 날짜 이후의 통화 만 허용한다. 광부는 블록의 타임 스탬프에 어느 정도 영향을 줄 수 있기 때문에 향후 블록 타임 스탬프가 설정된 트랜잭션을 포함하는 블록을 채굴할 수 있다. 충분히 가까이 있으면 네트워크에서 수락되며 다른 플레이어가 게임에서 이기려고 시도하기 전에 거래가 광부 에테르를 제공한다:
function play() public {
require(now > 1521763200 && neverPlayed == true);
neverPlayed = false;
msg.sender.transfer(1500 ether);
}
|
Additional Resources:
A survey of attacks on Ethereum smart contracts
Predicting Random Numbers in Ethereum Smart Contracts
Making smart contracts smarter
9. Short Address
- 짧은 주소 공격은 EVM 자체가 잘못 패딩된 인수를 수용하는 부작용이다. 공격자는 특수하게 조작된 주소를 사용하여 코드화 되지 않은 클라이언트가 인수를 트랜잭션에 포함시키기 전에 인코딩을 잘못하도록 함으로써 인수를 이용할 수 있다. EVM 문제인가, 고객 문제인가? 대신 스마트 계약으로 고쳐야 하는가? 모든 사람들이 다른 의견을 가지고 있지만, 사실은 많은 에테르들이 이 이슈의 직접적인 영향을 받을 수 있다는 것이다. 아직 와일드(wild)에서는 이러한 취약성이 악용되지 않았지만, 고객과 이더리움 블록체인의 상호작용에서 발생하는 문제를 잘 보여주는 것이다. 다른 오프체인 문제도 존재한다. 중요한 것은 특정 Javascript 프런트 엔드, 브라우저 플러그인 및 공용 노드에 대한 이더리움 생태계의 깊은 신뢰다. Coindash ICO의 해킹에 악명 높은 오프체인 공격이 사용 되었는데, 이 해킹은 참가자들을 속여 공격자의 주소로 에테르를 보내도록 했다.
Timeline of discovery:
Date
|
Event
|
April 6, 2017
|
Real World Impact:
Example:
1. 교환 API는 수취인 주소와 금액을 받는 거래 기능을 가지고 있다.
2. 그런 다음 API는 패딩된 인수로 스마트 계약 transfer(address _to, uint256 _amount) 기능과 상호 작용한다. 즉, 12 0바이트의 주소(예상된 20바이트 길이)를 32바이트로 확장한다.
3. 밥(0x3bdde1e9fbaef2579dd63e2abbf0be445ab93f00)은 앨리스에게 20개의 토큰을 양도해 달라고 한다. 그는 악의적으로 그녀의 주소를 잘라서 후행의 영점을 제거한다.
4. Alice는 Bob의 19바이트 주소가 짧은 교환 API를 사용한다(0x3bdde1e9fbaef2579dd63e2abbf0be445ab93f).
5. API는 주소를 12 0바이트로 패딩하여 32바이트가 아닌 31바이트가 된다. 다음의 _amount 인수의 1바이트를 효과적으로 도용.
6. 결국 스마트 계약의 코드를 실행하는 EVM은 데이터가 제대로 패딩되지 않았다고 말하고 _amount 인수 끝에 누락된 바이트를 추가할 것이다. 생각보다 256배 많은 토큰을 효과적으로 전송
Additional Resources:
Predicting random numbers in Ethereum smart contracts
Front-running, Griefing and the Perils of Virtual Settlement
10. Unknown Unknowns
Ethereum은 아직 걸음마 단계에 있다.
Smart contracts을 개발하는 데 사용되는 주 언어인 Solidity는 아직 안정된 버전에 이르지 못했고 Ecosystem의 도구로는 여전히 실험적이다.
가장 피해를 주는 smart contract의 취약점들 중 일부는 모두를 놀라게 했고, 똑같이 예상치 못하거나 똑같이 파괴적인 다른 것이 없을 것이라고 믿을 이유가 없다.
투자자들이 복잡하지만 가볍게 평가되는 코드에 거액의 자금을 투입하기로 결정하는 한, 새로운 발견이 계속해서 심각한 결과를 초래할 것이다.
Smart contracts을 공식적으로 검증하는 방법은 아직 성숙하지 않았지만, 현재의 불안정한 상황을 지나는 방법으로 큰 약속을 하는 것처럼 보인다.
새로운 종류의 취약점이 지속적으로 발견됨에 따라 개발자는 계속 발을 내딛어야 하며, 악의적인 사람이 찾기 전에 새로운 도구를 개발해야 한다.
DASP Top 10은 smart contract 개발이 안정 상태와 성숙 상태에 도달 할 때까지 빠르게 발전 할 것이다.
NSA, 소프트웨어 메모리 안전 문제로부터 보호하는 방법에 대한 지침 발표 (0) | 2022.11.14 |
---|