스마트 컨트랙트 보안 도구 - Slither
✏️ Slither
Slither은 스마트 컨트랙트 보안 정적 분석을 위한 오픈 소스 도구로, Solidity와 Vyper와 같은 이더리움 스마트 컨트랙트 언어로 작성된 스마트 컨트랙트의 취약점을 검사하고 보안 문제를 발견하는 데 사용됩니다.
Slither은 다양한 보안 문제를 식별하고, 스마트 컨트랙트의 코드 품질을 분석하여 보다 안전하고 견고한 스마트 컨트랙트를 개발하는 데 도움을 줍니다. 이를 통해 개발자는 스마트 컨트랙트를 배포하기 전에 잠재적인 취약성을 미리 파악하고 조치를 취할 수 있습니다.
🤔 Slither 기능
1️⃣ 정적 분석
스마트 컨트랙트 코드를 분석하여 정적으로 취약점을 식별합니다. 예를 들어, 무한 루프, 잠재적인 오버플로우, 사용자 입력의 제대로 된 검증 여부 등을 확인합니다.
2️⃣ 자동 보안 검사
Slither는 자동으로 스마트 컨트랙트 코드를 검사하고 일반적인 보안 문제를 탐지합니다.
3️⃣ 심볼릭 실행
Slither는 스마트 컨트랙트의 상태를 분석하기 위해 심볼릭 실행을 사용합니다. 이를 통해 더 깊은 수준의 분석과 취약성 탐지가 가능합니다.
💡 심볼릭 실행 (Symbolic Execution)
심볼릭 실행을 이용하여 프로그램의 모든 실행 경로를 탐색하고, 가능한 모든 입력값에 대해 테스트 케이스를 생성합니다. 이렇게 생성된 테스트 케이스를 사용하여 프로그램의 버그를 찾거나 정확성을 검증할 수 있습니다.
4️⃣ 플러그인 시스템
Slither는 플러그인 시스템을 지원하여 사용자가 커스텀 보안 검사를 추가할 수 있습니다.
❗️ Slither 사용 가이드 (Docker)
1️⃣ Slither 설치
$ pip3 install solc-select
$ pip3 install slither-analyzer
2️⃣ solc-select 컴파일 버전 설치 및 설정
$ solc-select install [사용할 버전]
$ solc-select use [사용할 버전]
// 컴파일 버전 확인
$ solc --version
3️⃣ Slither 컨트랙트 실행
$ slither <path_to_your_contract>
$ slither <path_to_your_contract> --solc-remaps @openzeppelin=node_modules/@openzeppelin
4️⃣ 결과 보고서 확인
5️⃣ 스마트 컨트랙트 내부의 기능 상호 작용을 시각화 한 그래프 작성
$ slither <path_to_your_contract> sol --print call-graph
$ slither <path_to_your_contract> sol --print call-graph --solc-remaps @openzeppelin=node_modules/@openzeppelin
👉 시각화된 그래프 예시
INFO:Printers:
Contract Payable
Contract vars: []
Inheritance:: ['ReentrancyGuard', 'Ownable', 'Context']
+-----------------------------+------------+-------------------------------+-----------------------------+-------------+---------------------------------------+------------------------------------------+-----------------------+
| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+-----------------------------+------------+-------------------------------+-----------------------------+-------------+---------------------------------------+------------------------------------------+-----------------------+
| constructor() | internal | [] | ['_NOT_ENTERED'] | ['_status'] | [] | [] | 1 |
| _nonReentrantBefore() | private | [] | ['_ENTERED', '_status'] | ['_status'] | ['require(bool,string)'] | [] | 1 |
| _nonReentrantAfter() | private | [] | ['_NOT_ENTERED'] | ['_status'] | [] | [] | 1 |
| _reentrancyGuardEntered() | internal | [] | ['_ENTERED', '_status'] | [] | [] | [] | 1 |
| constructor() | internal | [] | [] | [] | ['_msgSender', '_transferOwnership'] | [] | 1 |
| owner() | public | [] | ['_owner'] | [] | [] | [] | 1 |
| _checkOwner() | internal | [] | [] | [] | ['_msgSender', 'owner'] | [] | 1 |
| | | | | | ['require(bool,string)'] | | |
| renounceOwnership() | public | ['onlyOwner'] | [] | [] | ['_transferOwnership', 'onlyOwner'] | [] | 1 |
| transferOwnership(address) | public | ['onlyOwner'] | [] | [] | ['_transferOwnership', 'onlyOwner'] | [] | 1 |
| | | | | | ['require(bool,string)'] | | |
| _transferOwnership(address) | internal | [] | ['_owner'] | ['_owner'] | [] | [] | 1 |
| _msgSender() | internal | [] | ['msg.sender'] | [] | [] | [] | 1 |
| _msgData() | internal | [] | ['msg.data'] | [] | [] | [] | 1 |
| receive() | external | [] | ['msg.sender', 'msg.value'] | [] | [] | [] | 1 |
| withdraw(uint256) | external | ['nonReentrant', 'onlyOwner'] | ['msg.sender', 'this'] | [] | ['balance(address)', 'nonReentrant'] | ['address(msg.sender).transfer(amount)'] | 1 |
| | | | | | ['onlyOwner', 'require(bool,string)'] | | |
+-----------------------------+------------+-------------------------------+-----------------------------+-------------+---------------------------------------+------------------------------------------+-----------------------+
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
| nonReentrant() | internal | [] | [] | ['_nonReentrantAfter', '_nonReentrantBefore'] | [] | 1 |
| onlyOwner() | internal | [] | [] | ['_checkOwner'] | [] | 1 |
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
INFO:Printers:
Contract Governance
Contract vars: ['totalVotes', 'eligibleVotersLength', 'eligibleVoters', 'eligibleVotersAddress', 'votes', 'votersType']
Inheritance:: ['Payable', 'ReentrancyGuard', 'Ownable', 'Context']
+----------------------------------------------+------------+------------------------------------------------------------------+----------------------------------------------+---------------------------------------------+-------------------------------------------------+--------------------------------------------------+-----------------------+
| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+----------------------------------------------+------------+------------------------------------------------------------------+----------------------------------------------+---------------------------------------------+-------------------------------------------------+--------------------------------------------------+-----------------------+
| receive() | external | [] | ['msg.sender', 'msg.value'] | [] | [] | [] | 1 |
| withdraw(uint256) | external | ['nonReentrant', 'onlyOwner'] | ['msg.sender', 'this'] | [] | ['balance(address)', 'nonReentrant'] | ['address(msg.sender).transfer(amount)'] | 1 |
| | | | | | ['onlyOwner', 'require(bool,string)'] | | |
| constructor() | internal | [] | ['_NOT_ENTERED'] | ['_status'] | [] | [] | 1 |
| _nonReentrantBefore() | private | [] | ['_ENTERED', '_status'] | ['_status'] | ['require(bool,string)'] | [] | 1 |
| _nonReentrantAfter() | private | [] | ['_NOT_ENTERED'] | ['_status'] | [] | [] | 1 |
| _reentrancyGuardEntered() | internal | [] | ['_ENTERED', '_status'] | [] | [] | [] | 1 |
| constructor() | internal | [] | [] | [] | ['_msgSender', '_transferOwnership'] | [] | 1 |
| owner() | public | [] | ['_owner'] | [] | [] | [] | 1 |
| _checkOwner() | internal | [] | [] | [] | ['_msgSender', 'owner'] | [] | 1 |
| | | | | | ['require(bool,string)'] | | |
| renounceOwnership() | public | ['onlyOwner'] | [] | [] | ['_transferOwnership', 'onlyOwner'] | [] | 1 |
| transferOwnership(address) | public | ['onlyOwner'] | [] | [] | ['_transferOwnership', 'onlyOwner'] | [] | 1 |
| | | | | | ['require(bool,string)'] | | |
| _transferOwnership(address) | internal | [] | ['_owner'] | ['_owner'] | [] | [] | 1 |
| _msgSender() | internal | [] | ['msg.sender'] | [] | [] | [] | 1 |
| _msgData() | internal | [] | ['msg.data'] | [] | [] | [] | 1 |
| isAllowedVoter(address) | public | [] | ['eligibleVoters'] | [] | [] | [] | 1 |
| isEligibleVoters(uint32,address) | public | [] | ['votes'] | [] | [] | [] | 1 |
| addVoter(address,Governance.VoterType) | public | ['onlyOwner'] | ['eligibleVotersLength'] | ['eligibleVoters', 'eligibleVotersAddress'] | ['isAllowedVoter', 'onlyOwner'] | [] | 1 |
| | | | | ['eligibleVotersLength', 'votersType'] | ['require(bool,string)'] | | |
| removeVoter(address) | public | ['onlyOwner'] | ['eligibleVoters', 'eligibleVotersAddress'] | ['eligibleVoters', 'eligibleVotersAddress'] | ['isAllowedVoter', 'onlyOwner'] | [] | 1 |
| | | | ['eligibleVotersLength', 'votersType'] | ['eligibleVotersLength', 'votersType'] | ['require(bool,string)'] | | |
| createVote(uint256,Governance.VoterType) | public | ['onlyOwner'] | ['block.timestamp', 'eligibleVotersAddress'] | ['totalVotes', 'votes'] | ['onlyOwner'] | [] | 2 |
| | | | ['eligibleVotersLength', 'totalVotes'] | | | | |
| | | | ['votes'] | | | | |
| vote(uint32,Governance.VoteChoice) | public | ['isValidVoteId', 'isVotingPeriodExpired', 'onlyEligibleVoters'] | ['votersType', 'votes'] | ['votes'] | ['_msgSender', 'isValidVoteId'] | [] | 8 |
| | | | | | ['isVotingPeriodExpired', 'onlyEligibleVoters'] | | |
| | | | | | ['require(bool,string)'] | | |
| endVote(uint32) | public | ['isValidVoteId', 'onlyOwner'] | ['block.timestamp', 'votes'] | ['votes'] | ['isValidVoteId', 'onlyOwner'] | [] | 1 |
| | | | | | ['require(bool,string)'] | | |
| forceEndVote(uint32) | public | ['isValidVoteId', 'onlyOwner'] | ['block.timestamp', 'votes'] | ['votes'] | ['isValidVoteId', 'onlyOwner'] | [] | 1 |
| hasVoted(uint32,address) | public | ['isValidVoteId'] | ['votes'] | [] | ['isValidVoteId'] | [] | 1 |
| totalVoters(uint32,Governance.VoterType) | public | ['isValidVoteId'] | ['votes'] | [] | ['isValidVoteId'] | [] | 1 |
| getVoteCounts(uint32) | public | ['isValidVoteId'] | ['votes'] | [] | ['isValidVoteId'] | [] | 1 |
| getMostVotedChoice(uint32) | public | [] | ['votes'] | [] | [] | [] | 1 |
| verifyVoteCount(uint32,uint32,uint32,uint32) | public | ['isValidVoteId'] | ['votes'] | [] | ['isValidVoteId'] | [] | 1 |
| getVoteSelectionAndCount(uint32,address) | public | ['isValidVoteId'] | ['votes'] | [] | ['isValidVoteId', 'require(bool,string)'] | [] | 1 |
| transferVoteRewards(uint32) | public | ['isValidVoteId', 'nonReentrant'] | ['votersType', 'votes'] | [] | ['_msgSender', 'getMostVotedChoice'] | ['address(_msgSender()).transfer(rewardAmount)'] | 6 |
| | | | | | ['isValidVoteId', 'nonReentrant'] | | |
| | | | | | ['require(bool,string)'] | | |
| setVotingPeriod(uint32,uint256) | public | ['isValidVoteId', 'onlyOwner'] | ['block.timestamp', 'votes'] | ['votes'] | ['isValidVoteId', 'onlyOwner'] | [] | 1 |
| slitherConstructorVariables() | internal | [] | [] | ['eligibleVotersLength'] | [] | [] | 1 |
+----------------------------------------------+------------+------------------------------------------------------------------+----------------------------------------------+---------------------------------------------+-------------------------------------------------+--------------------------------------------------+-----------------------+
+-------------------------------+------------+------------------------------+-------+-----------------------------------------------+----------------+-----------------------+
| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+-------------------------------+------------+------------------------------+-------+-----------------------------------------------+----------------+-----------------------+
| nonReentrant() | internal | [] | [] | ['_nonReentrantAfter', '_nonReentrantBefore'] | [] | 1 |
| onlyOwner() | internal | [] | [] | ['_checkOwner'] | [] | 1 |
| isValidVoteId(uint32) | internal | ['totalVotes'] | [] | ['require(bool,string)'] | [] | 1 |
| isVotingPeriodExpired(uint32) | internal | ['block.timestamp', 'votes'] | [] | ['require(bool,string)'] | [] | 1 |
| onlyEligibleVoters(uint32) | internal | [] | [] | ['_msgSender', 'isEligibleVoters'] | [] | 1 |
| | | | | ['require(bool,string)'] | | |
+-------------------------------+------------+------------------------------+-------+-----------------------------------------------+----------------+-----------------------+
INFO:Printers:
Contract Ownable
Contract vars: ['_owner']
Inheritance:: ['Context']
+-----------------------------+------------+---------------+----------------+------------+--------------------------------------+----------------+-----------------------+
| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+-----------------------------+------------+---------------+----------------+------------+--------------------------------------+----------------+-----------------------+
| _msgSender() | internal | [] | ['msg.sender'] | [] | [] | [] | 1 |
| _msgData() | internal | [] | ['msg.data'] | [] | [] | [] | 1 |
| constructor() | internal | [] | [] | [] | ['_msgSender', '_transferOwnership'] | [] | 1 |
| owner() | public | [] | ['_owner'] | [] | [] | [] | 1 |
| _checkOwner() | internal | [] | [] | [] | ['_msgSender', 'owner'] | [] | 1 |
| | | | | | ['require(bool,string)'] | | |
| renounceOwnership() | public | ['onlyOwner'] | [] | [] | ['_transferOwnership', 'onlyOwner'] | [] | 1 |
| transferOwnership(address) | public | ['onlyOwner'] | [] | [] | ['_transferOwnership', 'onlyOwner'] | [] | 1 |
| | | | | | ['require(bool,string)'] | | |
| _transferOwnership(address) | internal | [] | ['_owner'] | ['_owner'] | [] | [] | 1 |
+-----------------------------+------------+---------------+----------------+------------+--------------------------------------+----------------+-----------------------+
+-------------+------------+------+-------+-----------------+----------------+-----------------------+
| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+-------------+------------+------+-------+-----------------+----------------+-----------------------+
| onlyOwner() | internal | [] | [] | ['_checkOwner'] | [] | 1 |
+-------------+------------+------+-------+-----------------+----------------+-----------------------+
INFO:Printers:
Contract ReentrancyGuard
Contract vars: ['_NOT_ENTERED', '_ENTERED', '_status']
Inheritance:: []
+---------------------------------------+------------+-----------+-------------------------+------------------------------+--------------------------+----------------+-----------------------+
| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+---------------------------------------+------------+-----------+-------------------------+------------------------------+--------------------------+----------------+-----------------------+
| constructor() | internal | [] | ['_NOT_ENTERED'] | ['_status'] | [] | [] | 1 |
| _nonReentrantBefore() | private | [] | ['_ENTERED', '_status'] | ['_status'] | ['require(bool,string)'] | [] | 1 |
| _nonReentrantAfter() | private | [] | ['_NOT_ENTERED'] | ['_status'] | [] | [] | 1 |
| _reentrancyGuardEntered() | internal | [] | ['_ENTERED', '_status'] | [] | [] | [] | 1 |
| slitherConstructorConstantVariables() | internal | [] | [] | ['_ENTERED', '_NOT_ENTERED'] | [] | [] | 1 |
+---------------------------------------+------------+-----------+-------------------------+------------------------------+--------------------------+----------------+-----------------------+
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
| nonReentrant() | internal | [] | [] | ['_nonReentrantAfter', '_nonReentrantBefore'] | [] | 1 |
+----------------+------------+------+-------+-----------------------------------------------+----------------+-----------------------+
INFO:Printers:
Contract Context
Contract vars: []
Inheritance:: []
+--------------+------------+-----------+----------------+-------+----------------+----------------+-----------------------+
| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+--------------+------------+-----------+----------------+-------+----------------+----------------+-----------------------+
| _msgSender() | internal | [] | ['msg.sender'] | [] | [] | [] | 1 |
| _msgData() | internal | [] | ['msg.data'] | [] | [] | [] | 1 |
+--------------+------------+-----------+----------------+-------+----------------+----------------+-----------------------+
+-----------+------------+------+-------+----------------+----------------+-----------------------+
| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity |
+-----------+------------+------+-------+----------------+----------------+-----------------------+
+-----------+------------+------+-------+----------------+----------------+-----------------------+
6️⃣ 이 외에 프린터 명령어 확인
$ slither –list-printers
📌 Slither 옵션
- solc 경고 비활성화 : --solc-disable-warnings
- 출력 색상화 비활성화 : --disable-color
- 결과를 json 파일로 내보내기 : --json file.json
- 파일 대신 standard output으로 내보내려면 파일 이름을 -로 입력
💡 standard output
standard output은 컴퓨터 프로그램이 실행되는 동안 데이터를 출력하는 표준 방법을 나타냅니다. 일반적으로 이 데이터는 터미널이나 명령 프롬포트 창에 표시됩니다.
따라서 결과를 별도의 파일로 저장하지 않고 사용자의 터미널 창에서 직접 출력할 때 사용합니다.
🔴 감지기 선택
- 모든 감지기를 확인 : --list-detectors
$ slither --list-detectors
- 특정 감지기만 실행 :--detect detector1,detector2
$ slither file.sol --detect arbitrary-send, pragma
- 특정 감지기 제외 : --exclude detector1,detector2
$ slither file.sol --exclude naming-convention, unused-state, suicidal
- 정보용 또는 심각도가 낮은 감지기 제외 : --exclude-informational 또는 --exclude-low
$ slither file.sol --exclude-informational
👉 공식문서 감지기 설명서
https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array
Detector Documentation
Static Analyzer for Solidity and Vyper. Contribute to crytic/slither development by creating an account on GitHub.
github.com
🔴 프린터 선택
- 모든 프린터를 확인 : --list-printers
$ slither --list-printers
- 특정 프린터 실행 : --print printer1,printer2
$ slither file.sol --print inheritance-graph
👉 공식문서 프린터 설명서 바로가기
https://github.com/crytic/slither/wiki/Printer-documentation#loc
Printer documentation
Static Analyzer for Solidity and Vyper. Contribute to crytic/slither development by creating an account on GitHub.
github.com
🔴 경로 필터링
- 특정 경로 관련 결과 제외: --filter-paths path1
$ slither . --filter-paths "openzepellin"
🔴 구성 파일
- 기본적으로 slither.config.json 파일이 사용됩니다. 파일 이름은 --config-file file.config.json 옵션을 통해 변경할 수 있습니다.
- CLI를 통해 전달된 옵션은 구성 파일의 옵션보다 우선됩니다.
- 지원되는 플래그
{
"detectors_to_run": "detector1,detector2", // 실행할 감지기의 목록
"printers_to_run": "printer1,printer2", // 실행할 프린터 목록
"detectors_to_exclude": "detector1,detector2", // 제외할 감지기 목록
"exclude_informational": false, // 정보용 감지기를 제외할지 여부
"exclude_low": false, // 낮은 심각도의 감지기를 제외할지 여부
"exclude_medium": false, // 중간 심각도의 감지기를 제외할지 여부
"exclude_high": false, // 높은 심각도의 감지기를 제외할지 여부
"json": "", // JSON 파일 저장 경로
"disable_color": false, // 출력의 색상화를 비활성화할지 여부
"filter_paths": "(mocks/|test/)", // 특정 경로를 필터링하는 정규식 표현
"legacy_ast": false // 이전 버전의 AST를 사용할지 여부
}
💡 AST (Abstract Syntax Tree)
AST, 즉 추상 구문 트리는 소스 코드의 구문 구조를 나타내는 트리로서 컴파일러나 인터프리터 등의 프로그래밍 언어 처리 도구에서 널리 사용됩니다.