스마트 계약 개발 필수 읽기: 무시할 수 없는 10가지 Solidity 보안 문제
편집자 주: 이 기사의 출처는등련공동체, 승인을 받아 Odaily에서 재인쇄했습니다.
편집자 주: 이 기사의 출처는
등련공동체
등련공동체
, 승인을 받아 Odaily에서 재인쇄했습니다.
2018년 우리(CheckMarx)는 Solidity[1]로 작성된 스마트 계약을 중심으로 스마트 계약 보안 상태에 대한 예비 연구를 수행했습니다. 당시 공개된 컨트랙트 소스 코드를 기반으로 상위 10개의 스마트 컨트랙트 보안 문제를 정리했습니다. 2년 후, 연구를 업데이트하고 스마트 계약 보안이 어떻게 진행되었는지 평가할 때입니다.
기타 주의할 사항
보안 문제 순위가 있는 것은 좋지만 일부 세부 정보가 순위 목록과 일치하지 않기 때문에 흥미로운 세부 정보가 있는 경향이 있습니다. 상위 10개 문제를 더 깊이 파고들기 전에 주목해야 할 원래 연구의 몇 가지 하이라이트를 설명할 필요가 있습니다.
2018년의 상위 2개 이슈는 외부 계약 서비스 거부와 재진입이었습니다. 그러나 이제 이러한 문제가 완화되었습니다(그러나 여전히 우려할 만한 원인임). 재진입에 대한 자세한 내용은 연구 블로그인 스마트 계약에 대한 보안 관점[2]에서 확인할 수 있습니다.
번역가 주: 사실 DeFi 애플리케이션(예: 플래시 론)의 결합으로 인해 심각한 재진입 공격이 많이 발생했습니다.
이제 Solidity v0.6.x가 출시되고[3] 많은 주요 변경 사항이 도입되었습니다[4]. 그러나 스캔된 스마트 계약의 50%는 Solidity v0.5.0 컴파일러에 대한 준비조차 되어 있지 않습니다. 또 다른 30%의 스마트 계약은 오래된 구문(예: sha3, throw, 상수 등 사용)을 사용하고 계약의 83%는 지정된 컴파일러 버전에서 사양 문제(pragma)를 가지고 있습니다.
번역가의 주: Solidity 0.6은 의미론적으로 더 명확합니다(예: 0.6 버전은 상속 측면에서 업그레이드됨[5]). 컴파일러가 제 시간에 문제를 찾고 코드를 더 안전하게 만드는 데 도움이 됩니다.
가시성 문제[6]는 2018년 상위 10위권에, 올해도 상위 10위 안에 나타나지 않았지만, 가시성 문제가 48% 증가한 것은 주목할 만하다.
아래 표는 2018년과 2020년 상위 10개 FAQ 목록 간의 변경 사항을 비교합니다. 이러한 문제는 심각도와 확산 정도에 따라 정렬됩니다.
1. 확인되지 않은 외부 통화
if(!addr.send(1)) {
revert()
}
확인되지 않은 외부 호출은 2018 Solidity 상위 10대 보안 문제 목록에서 세 번째로 가장 일반적인 문제입니다. 이제 처음 두 가지 문제가 해결되면서 확인되지 않은 외부 호출이 2020 업데이트 목록에서 가장 일반적인 문제입니다.
Solidity의 기본 호출 메서드(예: address.call())는 예외를 throw하지 않습니다. 대신 오류가 발생하면 false가 반환됩니다.
계약을 사용하여 ExternalContract.doSomething()을 호출하는 경우 doSomething()이 예외를 throw하면 예외가 계속 "버블링"되고 전파됩니다.
실패한 사례는 반환 값을 확인하여 명시적으로 처리해야 합니다. addr.send()를 사용한 다음 에테르 전송이 좋은 예이며 다른 외부 호출에도 유효합니다.
for(uint256 i=0; i< elements.length; i++) {
// do something
}
2. 고비용 사이클
High Cost Loops는 Solidity Security 목록에서 4위에서 2위로 이동했습니다. 이 문제의 영향을 받는 스마트 계약의 수는 거의 30% 증가했습니다.
Ethereum의 계산에는 지불이 필요하다는 것을 누구나 알고 있습니다. 따라서 작업을 완료하는 데 필요한 계산량을 줄이는 것은 최적화 문제(효율성)뿐만 아니라 비용 문제이기도 합니다.
루핑은 비용이 많이 드는 작업입니다. 다음은 좋은 예입니다. 배열에 포함된 요소가 많을수록 루프를 완료하는 데 더 많은 반복이 필요합니다. 결국 무한 루프는 사용 가능한 모든 가스를 소모합니다.
공격자가 요소 배열의 길이에 영향을 미칠 수 있는 경우 위의 코드는 서비스 거부를 유발합니다(실행이 루프를 벗어날 수 없음). 스캔한 스마트 계약에서 계약의 8%가 배열 길이 조작 문제가 있는 것으로 나타났습니다.
3. 압도적인 소유주
function calculateBonus(uint amount) returns (uint) {
return amount/DELIMITER*BONUS;
}
이는 Soldiity의 상위 10개 보안 문제에서 새롭게 등장하는 문제로 계약의 약 16%에 영향을 미치며 일부 계약은 소유자(Owner)와 밀접하게 관련되어 있으며 일부 기능은 다음 예제와 같이 소유자 주소로만 호출할 수 있습니다.
계약 소유자만 doSomething() 및 doSomethingElse() 함수를 호출할 수 있습니다. 전자는 onlyOwner 데코레이터를 사용하고 후자는 이를 명시적으로 구현합니다. 이는 심각한 위험을 초래합니다. 소유자의 개인 키가 손상되면 공격자가 계약을 제어할 수 있습니다.
4. 산술 정밀도 문제
function transferTo(address dest, uint amount) {
require(tx.origin == owner) {
dest.transfer(amount);
}
}
Solidity의 데이터 유형은 256비트 가상 머신(EVM[7])을 사용하기 때문에 다소 복잡합니다. Solidity는 부동 소수점 산술을 제공하지 않으며 32바이트 미만의 데이터 유형은 동일한 32바이트 슬롯에 압축됩니다. 이를 염두에 두고 다음과 같은 프로그램 정밀도 문제를 예상해야 합니다.
위의 예에서 볼 수 있듯이 곱하기 전에 수행되는 나누기는 큰 반올림 오류가 발생할 수 있습니다.
5. tx.origin에 의존
for (uint i = border; i >= 0; i--) {
ans += i;
}
스마트 계약은 인증을 위해 tx.origin에 의존해서는 안 됩니다. 악의적인 계약이 중간자 공격을 수행하고 모든 자금을 고갈시킬 수 있기 때문입니다. 대신 msg.sender를 사용하는 것이 좋습니다.<、>Tx Origin 공격에 대한 자세한 설명은 Solidity의 문서[8]에서 확인할 수 있습니다. 간단히 말해서 tx.origin은 항상 컨트랙트 호출 체인의 초기 개시자 계정인 반면 msg.sender는 직접 호출자를 나타냅니다. 체인의 마지막 계약이 인증을 위해 tx.origin에 의존하는 경우 체인 중간에서 계약을 호출하면 호출된 계약에서 자금을 빼낼 수 있습니다. 호출.
6. 오버플로(오버플로/언더플로)
Solidity의 256비트 가상 머신에는 오버플로 및 언더플로 문제가 있습니다(번역자 주: 결과가 값 범위를 초과하므로 오버플로라고 함) 여기 [9]에는 특정 분석이 있습니다. 무한 루프로 이어질 수 있으므로 개발자는 for 루프 조건에서 uint 데이터 유형을 사용할 때 특별히 주의해야 합니다.
위의 예에서 i의 값이 0일 때 다음 값은 2^256 -1이므로 조건이 항상 참이 됩니다. 개발자는 사용하려고 노력해야 합니다.
, != 및 == 비교.
for (var i = 0; i < elements.length; i++) {
// to something
}
7. 안전하지 않은 유형 추론
이 문제는 Solidity의 상위 10개 보안 문제 목록에서 두 단계 위로 이동했으며 이제 이전보다 17% 이상 더 많은 스마트 계약에 영향을 미칩니다.
Solidity는 유형 추론을 지원하지만 이상한 동작이 있습니다. 예를 들어 리터럴 0은 일반적으로 예상되는 정수 대신 바이트 유형으로 유추됩니다.
아래 예에서 i의 유형은 uint8로 유추됩니다. i의 값을 uint8로 저장할 수 있으면 충분하기 때문입니다. 그러나 요소 배열에 256개 이상의 요소가 포함된 경우 다음 코드가 오버플로됩니다.
예기치 않은 동작 및/또는 오류를 방지하려면 데이터 유형을 명시적으로 선언하는 것이 좋습니다.
if(!addr.send(1)) {
revert()
}
번역자 주: var 정의 변수는 Solidity 0.6에서 제거되었습니다(Solidity 0.6 이후에는 유형 추론이 없음). 새 컴파일러를 사용하면 문제가 되지 않습니다.
8. 잘못된 이체
이 문제는 Solidity의 상위 10개 보안 문제 목록에서 6위에서 8위로 떨어졌으며 현재 스마트 계약의 1% 미만에 영향을 미칩니다.
for (uint i = 0; i < users.lenghth; i++) {
users[i].transfer(amount);
}
계약 간에 이더를 전송하는 방법에는 여러 가지가 있습니다. 공식적인 권장 사항은 addr.transfer(x) 함수를 사용하는 것이지만 여전히 send() 함수를 사용하는 스마트 계약을 찾았습니다.
addr.transfer(x)는 전송이 실패하면 자동으로 예외를 발생시켜 확인되지 않은 첫 번째 외부 호출 문제를 다시 완화합니다.
9. 주기 내 전송
if (timeHasCome == block.timestamp) {
winner.transfer(amount);
}
루프 본문에서 에테르 전송을 할 때 전송 중 하나가 실패하면(예: 계약이 수신할 수 없음) 전체 트랜잭션이 롤백됩니다.<、>이 예에서 공격자는 이 동작을 악용하여 서비스 거부 공격을 수행하여 다른 사용자가 에테르를 받지 못하도록 할 수 있습니다.
2018년에는 타임스탬프 종속성 문제가 5위를 차지했으며 스마트 계약이 서로 다른 시간에 여러 노드에서 실행된다는 점을 기억하는 것이 중요합니다. EVM(Ethereum Virtual Machine)은 시계 시간을 제공하지 않으며 일반적으로 타임스탬프를 얻기 위해 사용되는 now 변수( block.timestamp 의 별칭)는 실제로 채굴자가 조작할 수 있는 환경 변수입니다.
요약하다
채굴자는 현재 환경 변수를 조작할 수 있으므로 부등식>에서만 사용할 수 있습니다.
= 및 <=는 해당 값을 사용합니다.
요약하다
소스 링크:securityboulevard.com


