스마트 계약 감사에 대한 초보자 가이드에 오신 것을 환영합니다! 스마트 계약 감사를 시작하는 가장 좋은 방법 중 하나는 스마트 계약에서 몇 가지 일반적인 유형의 취약점을 살펴보는 것입니다.
이더리움의 솔리디티 프로그래밍 언어에 대한 기본적인 이해가 이미 있다면 도움이 될 것입니다. Noob 솔리디티 프로그래머가 작성한 코드 중 일부를 살펴보겠습니다.
재진입 공격
실제 시나리오:
초콜릿이 50개 있다고 상상해 보세요. 당신에게는 한 번에 초콜릿 2개만 가져가도록 허용한 장난꾸러기 여동생이 있습니다. 당신은 또한 그녀가 충치에 걸릴까봐 하루에 10개 이상의 초콜릿을 그녀에게 주고 싶지 않습니다. 이를 보장하기 위해 매일 저녁 자신에게 남은 초콜릿 수를 세어 봅니다. 이것이 효과가 있다고 생각합니까? 아니면 여동생에게 해킹을 당할까요?
불행히도, 그것은 작동하지 않습니다! 당신의 여동생은 당신이 저녁이 될 때까지 당신에게 초콜릿이 몇 개나 있는지 모른다는 것을 알아차립니다. 그래서 바로 다음 날, 동생이 저녁 전에 6번 방문해서 매번 초콜릿 2개를 먹습니다! 이것을 재진입 공격이라고 합니다.
여기서는 여동생이 당신에게서 초콜릿 2개를 가져갈 때마다 개수를 업데이트하는 대신 저녁에 가지고 있는 초콜렛 개수를 업데이트합니다. 이것은 스마트 계약에서도 발생합니다. 스마트 계약은 공격자가 실제로 계약에서 일정량의 암호화폐를 여러 번 인출하느라 바쁜 동안 특정 균형을 가정합니다.
실제 코드 예:
이 코드는 스마트 계약에 속합니다. Unbanked. msg.sender의 잔고가 있는 한 누구나 Unbanked 계약에서 이더를 인출할 수 있습니다. withdraw
기능 )이 인출 요청 금액보다 크거나 같습니다.
function withdraw(uint _amount) { require(balances[msg.sender] >= _amount); msg.sender.call.value(_amount)(); balances[msg.sender] -= _amount;
}
필요한 양의 ether를 보내는 데 사용되는 call 키워드가 있습니다. msg.sender
. 공격자는 Thief라는 계약을 만들어 이를 악용할 수 있습니다. 여기서 그는 철수 기능을 호출합니다. fallback()
함수. ㅏ fallback()
솔리디티의 함수는 이더가 스마트 계약으로 보내질 때 실행되는 특별한 함수입니다.
즉, 공격자가 재귀적으로 철회 함수를 호출합니다.. 따라서 스마트 계약이 업데이트되기 전에 msg.sender
코드의 마지막 줄에서 공격자는 이미 이더를 여러 번 철회했습니다. call 키워드를 사용하기 전에 잔액을 업데이트하면 이를 피할 수 있습니다. 체크 효과 상호 작용 패턴입니다.
충격:
XNUMXD덴탈의 최초의 재진입 공격 2016년에 DAO(Decentralized Autonomous Organization)에서 발생하여 약 50천만 달러의 해킹이 발생했습니다. 이 해킹을 되돌리기 위해 이더리움 커뮤니티는 ETC(Ethereum Classic)와 ETH(Ethereum)를 탄생시킨 이더리움 블록체인을 분할했습니다.
산술 오버플로 및 언더플로
실제 시나리오:
생각 게임을 해보자. 물레방아로 구성되어 있으며, 물레를 돌릴 때 얻을 수 있는 가장 큰 수에 따라 승자가 결정됩니다. 휠은 256에서 -256까지 전체에 표시됩니다.
게임의 규칙은 모든 플레이어에 대한 포인터가 각 스핀의 시작 부분에서 0에 있다는 것입니다. 그리고 플레이어는 음수 방향으로만 회전할 수 있습니다. 이 게임에서 어떻게 이길까요?
매번 이 게임에서 승리하는 좋은 전략은 바퀴가 -256까지 회전한 다음 한 번에 256까지 회전하는 힘으로 바퀴를 돌리는 것입니다. 이것은 휠에서 -256 바로 뒤에 256이 오기 때문에 가능합니다. 이것을 우리는 산술 언더플로라고 부릅니다. 그리고 산술 오버플로는 이와 반대의 경우도 마찬가지입니다.
실제 코드 예:
An 언더플로 또는 오버플로 산술 연산이 최소 또는 최대에 도달할 때 발생합니다.
function withdraw(uint _amount) public { require(balances[msg.sender] - _amount > 0); address payable to = payable(msg.sender); to.transfer(_amount); balances[msg.sender] -= _amount;
}
XNUMXD덴탈의 _amount
철회 기능의 매개변수는 부호 없는 정수입니다. (python의 사전이나 C++ 또는 Java의 키-값 쌍과 같은) 잔액 매핑의 값도 부호 없는 정수입니다.
mapping(address => uint256) public balances
필수 명세서는 다음의 잔액을 확인합니다. msg.sender
긍정적이든 아니든. 그러나이 진술은 금액이 잔액보다 크더라도 항상 사실입니다. msg.sender
. 그 이유는 둘 다 balances
과 _amount
변수의 유형은 부호 없는 정수이고 해당 산술 결과(언더플로 후)도 부호 없는 정수가 됩니다!
그리고 기억하시겠지만, 부호 없는 정수는 항상 양수입니다. 이것은 공격자가 스마트 계약에서 무제한 양의 Ether를 인출할 수 있음을 의미합니다! 이 취약점에 대한 자세한 예제 및 구현 코드를 찾을 수 있습니다. 여기에서 지금 확인해 보세요..
여기서 주목해야 할 또 다른 중요한 점은 두 개의 부호 없는 정수 사이의 산술 연산도 부호 없는 정수라는 것입니다. 스마트 계약에서 이를 간과하면 원치 않는 보안 침해가 발생할 수 있으므로 위험할 수 있습니다!
function votes(uint postId, uint upvote, uint downvotes) { if (upvote - downvote < 0) { deletePost(postId) }
}
위의 예에서 알 수 있듯이 if 문은 다음과 같이 매우 무의미합니다. upvote - downvote
항상 긍정적일 것입니다. 그리고 게시물은 다음과 같은 경우에도 삭제됩니다. downvotes
보다 큰 upvotes
. 이러한 공격을 피하기 위해 Solidity 컴파일러 버전보다 높은 버전을 사용하는 것이 좋습니다. 0.8.0.
충격:
이라는 동전 PoW 코인 Ponzi 게임이었지만, 산술 오버플로 버그로 인해 그 자체가 해킹되어 당시 약 2017 ETH 또는 $866의 손실을 입었습니다. 이에 대해 자세히 읽을 수 있습니다. 여기에서 지금 확인해 보세요..
읽기해야합니다 : Algorand의 가장 큰 DEX인 Tinyman에 대한 공격의 교훈
서비스 거부 공격
실제 시나리오:
당신이 Bitcoin Tech University에 있다고 상상해보십시오. 모두를 위한 공용 식탁이 있는 것 외에는 모든 것이 괜찮아 보입니다. 그리고 불행히도, 항상 당신의 반에서 누구보다 먼저 식탁을 차지할 수 있는 다른 반의 사람들은 거의 없습니다.
실제 시나리오에서 그들은 모든 사람에게 필수적인 서비스를 거부하여 귀중한 시간을 낭비하고 있습니다. 이를 '서비스 거부 공격'이라고 합니다.
실제 코드 예:
라는 게임에서 에테르의 왕, 누구나 왕이 될 수 있습니다. 그러나 왕이 되는 규칙은 현재의 왕보다 더 많은 에테르를 예치해야 한다는 것입니다. 이것은 호출하여 수행할 수 있습니다 claimThrone()
그 사람이 이전 왕에게 직접 에테르를 보내고 새로운 왕이 되는 에테르 왕 계약의 기능.
function claimThrone() external payable { require(msg.value > balance, "Need to pay more to become the king"); (bool sent, ) = king.call{value: balance}(""); require(sent, "Failed to send Ether"); balance = msg.value; king = msg.sender; }
짐작할 수 있듯이 이 코드는 DoS 공격에 취약하지만 어떻게? 이를 위해 이더리움에는 두 가지 유형의 주소가 있음을 이해해야 합니다. 외부의 주소 소유 계정 또는 단순히 지갑 주소, 두 번째는 계약 주소. 이제 이러한 주소 유형 중 하나에서 에테르를 보낼 수 있습니다.
이 경우 컨트랙트 주소로 ether를 보내면 컨트랙트가 왕이 됩니다. 그러나 이 새 계약에 fallback()
계약이 ether를 수락하기를 원할 때 필요한 기능입니다. 그런 다음 새로운 사람이 와서 전화를 시도하면 claimThrone()
함수는 항상 실패합니다!
이것은 부분적으로 다음과 같은 이유로 발생합니다. claimThrone()
함수는 두 번째 필수 명령문에서 ether 전송이 성공했는지 여부를 명시적으로 확인합니다. 전체 코드를 찾아 DoS 공격을 수행할 수 있습니다. 여기에서 지금 확인해 보세요..
코드에 큰 크기의 배열에 루프가 있는 경우 코드가 DoS 공격에 취약할 수도 있습니다. 이것은 때문에 발생합니다 가스 한도 이러한 경우 초과될 수 있습니다. 당신은 그것에 대해 읽을 수 있습니다 여기에서 지금 확인해 보세요..
충격:
라는 게임 정부 멘탈분명히 Ponzi 계획이었던 , 지불을 처리하는 데 많은 양의 가스가 필요했기 때문에 1100 에테르에 갇혔습니다.
불안정한 무작위성
실제 시나리오:
옛날에 원숭이 Pesky와 항상 동행하는 Hesky라는 남자가 있었습니다. Hesky는 복권 게임을 하고 좋은 수익을 올렸습니다. 어느 날 Alice는 Hesky가 원숭이 Pesky를 빤히 쳐다보는 것을 알아차렸습니다. 그런 다음 그녀는 그가 종이에 무언가를 쓰고 있는 것을 보고 봉투에 봉인했습니다. 궁금해서 그녀는 더 조사하기로 결정했습니다.
그날 저녁 늦게 앨리스는 봉인된 봉투를 공개적으로 열어서 복권 당첨자가 결정되는 것을 보았습니다. 며칠간 그를 지켜본 앨리스는 페스키의 몸짓(예를 들어 원숭이가 머리를 긁적이면 Hesky가 10을 적었다)을 보고 Hesky가 로또 번호를 결정한 것을 알아냈습니다! 이제 Alice는 각 복권에 당첨되는 공식을 가지고 있었고 올바른 번호의 복권을 구매해야 했습니다!
Hesky는 복권 당첨자를 결정하는 그의 "무작위" 방식은 결코 알아낼 수 없다고 가정했지만 실제로는 틀렸습니다.
실제 코드 예:
이 예에서 블록 번호와 블록 타임스탬프 조합의 해시를 기반으로 난수가 생성됩니다. 그런 다음 이 해시가 응답 변수에 할당됩니다. 이제 이 (겉보기에) 임의의 숫자를 추측하는 사람은 1 Ether를 받습니다. 이것이 해킹 불가능하다고 생각하십니까?
function guess(uint _guess) public { uint answer = uint( keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) ); if (_guess == answer) { (bool sent, ) = msg.sender.call{value: 1 ether}(""); require(sent, "Failed to send Ether"); } }
아니요! 공격자는 응답 변수에 할당된 값을 생성하기 위해 코드를 복사하여 붙여넣고 동일한 응답 변수를 전달함으로써 여전히 이 난수를 추측할 수 있습니다. guess()
함수!
guessTheRandomNumber.guess(answer);
전체 코드를 찾을 수 있습니다 여기에서 지금 확인해 보세요.. 이 공격을 피하려면 다음과 같은 검증 가능한 랜덤 함수를 사용하는 것이 좋습니다. 체인 링크 VRF.
충격:
에 대한 공격으로 약 400 ETH가 손실되었습니다. 스마트 빌리언즈 복권 계약. 놀랍게도, 계약 복권 자체도 폰지 사기였습니다(아야!).
시간 조작
실제 시나리오:
사토시는 쿠키 먹는 것을 좋아합니다. 그는 그의 어머니가 만드는 모든 종류의 쿠키를 좋아합니다. 그러나 그의 어머니는 매우 엄격하고 쿠키를 너무 많이 먹는 것은 그에게 좋지 않다고 생각합니다. 그래서 그의 어머니는 그가 쿠키를 저녁 8시에만 받을 수 있도록 규칙을 정했습니다.
그날 저녁 7시 45분, 사토시는 엄마에게 달려가 쿠키를 달라고 한다. 그의 어머니는 "지금 몇시입니까?"라고 묻는다.
“지금은 8시입니다!” - 그가 대답한다.
"괜찮아. 그런 다음 내 찬장에서 쿠키를 가져오세요.”
따라서 Satoshi는 쿠키를 얻을 수 있도록 15분으로 시간을 성공적으로 조작할 수 있었습니다! 쿠키에 굶주린 친구!
실제 코드 예:
블록의 타임스탬프는 다음과 같이 조작할 수 있습니다. 15 초 광부에 의해. 이러한 방식으로 광부는 유리한 타임스탬프를 설정하고 자신이 채굴하는 동일한 블록에 자신의 거래를 포함할 수 있습니다. 함수 play()
G-Dot이라는 게임 계약에 속합니다.
function play() public { require(now > 1640392200 && neverPlayed == true); neverPlayed = false; msg.sender.transfer(1500 ether);
}
이 계약은 플레이 기능을 가장 먼저 호출한 플레이어에게 1500 이더를 보상합니다. 하지만 보시다시피 play 함수는 now 또는 block.timestamp에 대한 호출이 포함된 트랜잭션의 경우에만 호출할 수 있습니다. play()
기능은 다음보다 큽니다. 에포크 시간 1640392200.
광부는 이 타임스탬프를 쉽게 조작하고 호출하는 트랜잭션을 포함할 수 있습니다. play()
자신이 첫 번째 플레이어가 되도록 같은 블록에서 기능합니다. 이런 식으로 광부가 게임에서 승리하는 것이 보장됩니다!
충격:
block.timestamp는 난수를 생성하는 데 사용되었습니다. 정부 따라서 시간 조작 공격에 취약했습니다.
QuillAudits에 연락
QuillAudits는 다음이 설계한 안전한 스마트 계약 감사 플랫폼입니다. 퀼해시
기술.
정적 및 동적 분석 도구, 가스 분석기 및 assimulator를 통한 효과적인 수동 검토를 통해 보안 취약점을 확인하기 위해 스마트 계약을 엄격하게 분석 및 검증하는 감사 플랫폼입니다. 또한 감사 프로세스에는 구조 분석뿐만 아니라 광범위한 단위 테스트도 포함됩니다.
우리는 스마트 계약 감사와 침투 테스트를 모두 수행하여 잠재력을 찾습니다.
플랫폼의 무결성을 해칠 수 있는 보안 취약점.
스마트 계약 감사에 도움이 필요하면 언제든지 전문가에게 문의하세요. 여기에!
우리의 작업에 대한 최신 정보를 얻으려면 커뮤니티에 가입하십시오:-
포스트 스마트 계약 감사 초보자 가이드: 1부 첫 번째 등장 Quillhash 블로그.
출처: https://blog.quillhash.com/2022/01/19/beginners-guide-to-smart-contract-auditing-part-1/
- "
- &
- 000
- 2016
- 7
- 소개
- 계정
- 주소
- All
- 이미
- 이기는하지만
- 분석
- 회계 감사
- 자발적인
- 처음
- BEST
- 비트코인
- blockchain
- 곤충
- 사기
- 전화
- 가지 경우
- 확인하는 것이 좋다.
- 고전적인
- 암호
- 동전
- 결합
- 공통의
- 커뮤니티
- 이 포함되어 있습니다
- 계약
- 계약
- 쿠키
- 수
- 만들기
- 암호화는
- Current
- DAO
- 일
- 분산 된
- 서비스 거부
- 세부 묘사
- 덱스
- 아래 (down)
- 용이하게
- 먹다
- ETH
- 에테르
- 이더리움
- 에테 리움 블록 체인
- Ethereum Classic
- 예
- 공적
- 페이스북
- 끝
- 먼저,
- 무료
- 기능
- 경기
- Games
- 가스
- 생성
- GitHub의
- 가는
- 좋은
- 안내
- 마구 자르기
- 해킹
- 해시
- 머리
- 여기에서 지금 확인해 보세요.
- 방법
- HTTPS
- 조사
- IT
- 자바
- 어울리다
- 도약
- 왕
- 언어
- 넓은
- 라인
- 링크드인
- 긴
- 찾고
- 추첨
- 사람
- 백만
- 어머니
- 숫자
- 조직
- 서
- 무늬
- 지불
- 사람들
- 조각
- 플랫폼
- 연극
- 플레이어
- ponzi
- 폰 지사 기법
- 힘
- 방법
- 프로그래머
- 프로그램 작성
- 공개
- 레딧
- 역
- 리뷰
- 보상
- 규칙
- 사토시
- 보안
- 세트
- 스마트 한
- 똑똑한 계약
- 스마트 계약
- So
- solidity
- 무언가
- 회전
- 분열
- 시작
- 성명서
- 전략
- 성공한
- 성공적으로
- 기술
- 테스트
- 을 통하여
- 시간
- 검색을
- 거래
- unbanked
- 대학
- 업데이트
- 가치
- 취약점
- 취약점
- 취약
- 지갑
- 뭐
- 바퀴
- 누구
- 승리
- 작업
- 쓰기