1. 변수 저장/할당/삭제:
보조 제목
1. 민감한 정보를 체인에 저장하지 마십시오.
취약점 세부 정보:블록체인 기반 투명성,체인에 배포된 모든 계약 데이터는 투명하고 가시적입니다.
, 비공개 수정 변수의 경우에도 마찬가지입니다. 비공개의 가시성은 기능 및 외부 계약에만 해당되므로 모든 사용자는 체인에서 데이터를 검색하여 이러한 값을 얻을 수 있습니다. 이 경우 온체인 개인 수정에 의해 보장될 것으로 예상되는 모든 기밀성 작업은 안전하지 않습니다.
contract Eocene{
mapping(address => bytes 32) candidate;
uint private seed = 0x 12341 3d;
function select() public{
bytes 32 result = keccak 256(abi.encodePacked(seed));
if(result == candidate[msg.sender]){
payable(msg.sender).transfer( 1 ether);
}
}
}
다음과 같은 단순 추첨 코드가 존재하는 경우:
시드가 프라이빗 변수로 선언된 경우에도 컨트랙트 주소와 시드의 슬롯 위치를 통해 누구나 체인에서 시드 값을 검색하여 해당 값을 계산하여 eth를 얻을 수 있습니다.
확인을 위한 키 값을 컨트랙트에 저장하지 말고, 키 값을 오프체인에 저장하고, 해당 확인 로직만 체인에 구현하십시오.
보조 제목
2. 변수 기본값에 주의하십시오.
취약점 세부 정보:
Solidity에서 변수의 초기 값은 0/false입니다. 이 경우 특정 변수를 기준으로 판단할 때 변수의 초기값의 영향을 고려하지 않으면 해당 보안 문제가 발생할 수 있습니다.
contract Eocene{
mapping(address => bool) unlocked;
uint averageDrop;
address token;
function setAverageDrop() public {
averageDrop = 1000;
}
function drop() public {
if(unlocked[msg.sender] == false){
ERC 20(token).transfer(msg.sender, averageDrop);
}
}
}
다음 에어드롭 잠금 해제 코드를 고려하십시오.
컨트랙트의 원래 의도는 잠금 해제된 모든 주소에 토큰을 배포하는 것이지만 견고하게 모든 변수의 초기 값이 0/false라는 점을 무시합니다. 매핑 방식에서 키는 슬롯과의 스플라이싱에만 사용되며 저장소의 키에 해당하는 주소는 keccak 256을 통해 계산됩니다. 즉, 초기화 여부에 관계없이 모든 주소에 해당하는 저장소가 있습니다. , 초기 값은 일반적으로 0/거짓입니다.
어떠한 경우에도 변수의 기본 값, 특히 매핑 유형에 따른 변수에 대한 비판적 판단을 하지 말고 엄격히 방지하십시오.
보조 제목
3. 더 이상 사용하지 않는 구조체 유형의 값을 삭제합니다.
취약점 세부 정보:
모든 매핑 유형에 대해 값 필드 유형이 구조체이고 해당 값이 더 이상 필요하지 않은 경우 삭제 설정을 사용하여 값을 삭제해야 합니다. 그렇지 않으면 값은 여전히 해당 슬롯에 남아 있습니다.
contract Eocene{
struct Stake{
uint amount;
uint needReceive;
uint startTime;
}
mapping(address => Stake) stakes;
mapping(address => bool) staker;
function getStake() public{
Stake memory _stake = stakes[msg.sender];
(msg.sender).transfer(_stake.needReceive);
staker[msg.sender] = false;
// delete stakes[msg.sender] // need do but don't
}
function calReceive() public{
require(staker[msg.sender],'not staker');
stakes[msg.sender].needReceive = stakes[msg.sender].amount * (block.time - stakes[msg.sender].startTime);
stakes[msg.sender].amount = 0;
}
}
다음 형식의 코드를 고려하십시오.
위 컨트랙트 코드는 담보 금액과 담보 시간을 기준으로 획득한 eth의 양을 계산하는데, 담보가 완료된 후에는 staker[msg.sender]의 값만 false로 설정되고, 그에 해당하는 stakes[msg.sender]는 여전히 존재합니다. 따라서 공격자는 eth를 얻기 위해 제한 없이 getStake() 함수를 호출할 수 있습니다.
수리 조치:물론 위의 코드에는 취약점이 존재하게 만드는 다른 보조적인 문제도 있지만 다음과 같은 점에 유의해야 합니다.그렇지 않으면 값 쌍이 항상 해당 슬롯에 존재합니다.
2. 기능 정의:
보조 제목
1. 표시되어야 하는 선언된 함수의 가시성
취약점 세부 정보:함수의 기본 가시성은 공개입니다.모든 함수에 대해 해당 가시성을 명시적으로 선언해야 합니다.
, 과실로 인한 허점의 존재를 방지하기 위해, 특히 함수가 여러 계층에 중첩되어 기본 함수를 호출하는 경우, 기본 함수가 과실로 인해 잘못 가시성이 부여되는 것을 방지합니다.
contract Eocene{
mapping(address => bool) whitelist;
function _a() {
payable(msg.sender).transfer( 1 ether);
}
function a() public{
require(whitelist[msg.sender],'not in whitelist');
_a();
}
}
취약한 코드의 다음 예를 고려하십시오.
a() 함수는 require를 통해 화이트리스트 주소를 제한하고 통과 후 해당 주소로 돈을 이체합니다. public으로 간주되어 외부에서 직접 호출할 수 있습니다.
모든 함수의 가시성을 명시적으로 선언합니다. 특히 외부에서 직접 호출할 수 없는 함수의 경우 명시적으로 protect 또는 private로 선언해야 합니다.
보조 제목
2. 함수 재진입 공격
취약점 세부 정보:
모든 기능과 마찬가지로 재진입 후 발생할 수 있는 문제를 고려해야 합니다. 여기서 재진입에는 transfer/send/call/staticall과 같은 외부 호출로 인해 발생하는 모든 재진입 문제가 포함됩니다.
contract Fund {
mapping(address => uint) shares;
function withdraw() public {
if (payable(msg.sender).send(shares[msg.sender]))
shares[msg.sender] = 0;
}
}
다음 코드 형식을 고려하십시오.
항소 계약의 경우 msg.sender가 악의적인 경우 msg.sender가 현재 모든 계약의 잔액을 제한 없이 추출할 수 있습니다. 그러나 transfer/send/call/staticall 및 외부 컨트랙트 기능을 호출할 때 재진입 문제가 발생할 수 있음을 알아야 합니다.
계약의 구체적인 구현 세부 사항에 따라 주요 변수의 구현을 먼저 수정할 수 있습니다.예를 들어 항소 계약에서 공유[msg.sender]의 값을 먼저 기록한 다음 전송 작업을 수행할 수 있습니다. 공유[msg.sender]를 0으로 설정한 후. 물론 전역 변수와 데코레이터의 조합을 통해서도 구현할 수 있다.
3. 외부 상호 작용
보조 제목
1. 외부 호출 주소 및 기능 목록 제한
취약점 세부 정보:
외부 기능 호출의 경우 합리적인 상황에서 호출할 계약 주소 및 계약 기능을 제한해야 합니다.
contract Eocene{
function callExt(address _target, bytes calldata data) public{
_target.call(data);
}
function delegateCallExt(address _target, bytes calldata data) public{
_target.delegatecall(data);
}
}
다음 코드 형식을 고려하십시오.
함수 callExt는 함수의 임의의 주소를 호출하는 데 사용되며, 이 경우 재진입 문제가 발생하기 쉽습니다. 이 기능입니다.
다만, delegateCallExt 함수를 호출할 때 아무런 제한이 없는 경우 직접 컨트랙트가 파기되어 전체 컨트랙트 주소 잔고 이전 및 컨트랙트가 파기될 수 있습니다.
외부 계약에 대한 호출의 경우 먼저 주소가 화이트리스트에 의해 제한될 수 있는지 여부를 고려하고 지정된 주소의 함수 이름이 제한될 수 있는지 여부를 추가로 고려합니다.
보조 제목
2. call, send, delegatecall, staticcall 사용시 외부 호출의 판단은 예외에 의존할 뿐만 아니라 반환 값으로 판단해야 함
취약점 세부 정보:
이의제기 기능은 내부 오류로 인해 되돌리기가 발생하지 않고 그냥 되돌리기만 합니다. 사용할 때마다 함수의 반환 값을 사용하여 실행이 성공했는지 여부를 확인해야 합니다.
contract Eocene{
address token; //any token address
function deposit(uint amount) public{
token.call(abi.EncodeWithSignature("transferfrom(address from, address receipt, uint amount)"), msg.sender, address(this), amount);
mint(msg.sender, amount);
}
}
다음 샘플 코드를 고려하십시오.
deposit() 함수에서 계약은 먼저 msg.sender의 지정된 토큰을 현재 주소로 전송하려고 시도하고 전송이 성공한 후 msg.sender를 위해 일부 현재 동전을 주조합니다. 그러나 .call 기능은 실패 시 전체 트랜잭션을 되돌리지 않기 때문에 msg.sender에서 현재 주소로 통화를 전송하는 데 실패하더라도 현재 금액의 통화를 msg.sender로 발행합니다.
call, send, delegatecall, staticcall의 실행 결과 판단은 되돌리기 여부가 아닌 반환 값을 기준으로 해야 합니다.
넷째, 액세스 제어:
보조 제목
1. tx.origin 기반 신원 인증 없음
취약점 세부 정보:tx.origin을 기반으로 인증하지 않음
, tx.origin은 전체 트랜잭션의 개시자이며 계약의 재귀 호출로 변경되지 않습니다.tx.origin을 기반으로 하는 인증은 tx.origin이 msg.sender임을 보장할 수 없습니다. tx.origin 기반 인증도 사용자 계정 보안을 강화합니다.
contract Eocene{
mapping(address=>bool) whitelist;
function freeDeposit() public{
require(whitelist[tx.origin],'not in whitelist');
payable(msg.sender).transfer( 1 ether);
}
}
취약점의 다음 예를 고려하십시오.
화이트리스트의 주소가 어떤 피싱 링크에 의해 겉보기에 무해해 보이는 악의적인 계약 주소 및 기능을 호출하도록 유도되고 악성 주소가 샘플 코드의 freeDeposit 기능을 호출하면 화이트리스트 주소에 속해야 하는 자산이 다음으로 전송됩니다. 악의적인 계약 주소.
수리 조치:tx.origin을 기반으로 하는 인증이 없습니다. 또는 어필 코드의 경우 payable(tx.origin).transfer(1ether)로 변경하면 문제가 발생하지 않습니다., 그러나 판단을 내리기 위해 require(whitelist[msg.sender],'not in whitelist');를 사용하십시오.
보조 제목
2. extcodesize 반환 값으로 eos 계정을 판단하지 마십시오.
취약점 세부 정보:계약 코드의 초기화 단계에서 주소가 계약 주소이더라도 extcodesize의 반환 값은 0이 됩니다.
, 반환 값을 기준으로 판단하면 얻은 결과가 정확하지 않습니다.
contract Eocene{
function withdraw() public{
uint size;
assembly {
size := extcodesize(caller())
}
require(size== 0,"not eos account");
msg.sender.transfer( 1 ether);
}
}
다음 코드 형식을 고려하십시오.
어필 컨트랙트의 철회 기능에서 extcodesize 반환 값을 통해 EOS 계정만이 토큰을 얻을 수 있기를 바라지만 컨트랙트 초기화 시 컨트랙트 주소에 대한 extcodesize 반환 값도 0이라는 점을 무시한다. 결과적으로 판단이 정확하지 않으며 모든 주소가 계약에서 토큰을 얻을 수 있습니다.
언제든지 외부 주소가 계약 주소인지 판단하지 말고 어떤 유형의 계정에서도 계약 코드가 정상적으로 작동하는지 확인하십시오.
5. 산술 연산
보조 제목
1. 수치연산시 오버플로우 문제를 고려
취약점 세부 정보:
오버플로 문제는 계약이 정수 연산을 할 때 발생하는 오버플로 문제를 말합니다. 가장 큰 이유는 어떤 숫자형이든 최대 길이가 있기 때문인데, 두 정수의 연산이 최대값을 초과하면 초과 부분이 잘려서 문제가 발생합니다.
contract Eocene{
mapping(address=>uint) balanceof;
function withdraw(uint amount) public{
payable(msg.sender).transfer(amount);
balanceof[msg.sender] = balanceof[msg.sender]-amount;
require(balanceof[msg.sender] >= 0,'not enough balance');
}
}
다음 코드 형식을 고려하십시오.
어필 함수의 경우 balanceof[msg.sender] < amount인 경우 balanceof 유형은 unsigned integer로 제한되므로 total 계산 결과는 int 유형의 음수 값이 되고 uint 유형으로 변환하면 양수 값이 매우 크면 요구 제한이 무시되고 공격자는 계약 요약에서 토큰을 얼마든지 훔칠 수 있습니다.
수리 조치:, 최종 계산 결과가 오버플로를 일으키지 않도록 합니다.
보조 제목
2. 정수 연산을 수행할 때 int 유형을 신중하게 사용하십시오.
취약점 세부 정보:
정수 유형으로 계산을 수행할 때 이러한 종류의 작업이 필요하지 않은 경우 계산을 위해 uint 유형을 int 유형으로 변환하는 데 주의하십시오. uint 유형의 정수를 int 유형으로 변환할 때 uint 유형에 대한 오버플로의 일부 경우가 int 유형에서 유효하지 않기 때문입니다.
contract Eocene{
int public result;
uint public uresult;
function cal(uint _a, uint _b) public{
result = int(_a)-int(_b);
uresult = uint(result);
}
}
다음 코드 형식을 고려하십시오.
솔리디티 버전 0.8.0 이상으로 컴파일할 때 `cal(0, 1)`을 호출하면 `0-1`이 uint에서 오버플로를 일으키더라도 int 유형 revert의 계산에서 오버플로가 발생하지 않습니다. 0-1의 결과는 int 유형의 범위에 있습니다). 그리고 결과값을 uint형으로 변환하면 실제 uint형 계산이 오버플로된 후의 결과값이 되어 위장한 오버플로 문제가 발생한다.
그러나 여기에서 cal(type(int).min, type(int).max) 를 호출하면 이때 정수 계산도 int 유형의 범위를 벗어나기 때문에 revert 가 계속 트리거된다는 점에 유의해야 합니다.
수리 조치:진행 중. 정수 연산 자체가 오버플로되어야 하는 경우 uncheck를 사용하여 uint 유형 연산 구현을 래핑하는 것이 좋습니다.
보조 제목
3. 정밀도를 잃을 수 있는 모든 작업에서 확장하여 허용할 수 없는 정밀도 손실을 방지합니다.
취약점 세부 정보:
정수 연산을 할 때 정밀도 손실로 인해 발생할 수 있는 문제를 고려하여 정밀도를 확장합니다.
contract Eocene {
uint totalsupply;
mapping(address=>uint) balancesof;
uint BasePrice = 1 e 16;
function mint() public payable {
uint tokens = msg.value/BasePrice;
balancesof[msg.sender] += tokens;
totalsupply += tokens;
}
}
양식의 다음 코드를 고려하십시오.
항소 계약을 고려하여 획득해야 하는 토큰의 수는 mint에서 msg.value/basePrice로 계산되지만 `/` 계산의 정확도로 인해 msg.value가 1e 16 미만일 때 모든 부분이 잠깁니다. 계약 과정에서 이것은 eth의 낭비로 이어질 뿐만 아니라 사용자 경험에도 상당히 나쁩니다.
정밀도가 누락될 수 있는 정수 계산의 경우 먼저 정수를 `* 1 eN`만큼 확장합니다(N은 필수 정밀도 크기임).
6. 무작위 값:
보조 제목
1. 추측 가능한/운영되는 온체인 데이터를 난수 시드로 사용하지 마십시오.
취약점 세부 정보:
블록체인의 특수성으로 인해 체인에는 진정한 임의의 값이 없으며 체인의 모든 데이터는 임의의 값 또는 난수 시드로 사용되어서는 안되며 오프체인에서 임의의 값을 얻는 것을 고려하십시오.
contract Eocene{
function winner(bytes 32 value) public payable{
require(msg.value > 0.5 ether,"not enough value");
if(value == keccak 256(abi.encodePacked(block.timestamp))){
msg.sender.transfer( 1 ether);
}
}
}
코드 예제는 다음과 같습니다.
항소 계약의 경우 현재 블록 시간 태그를 사용하여 임의 값을 계산하고 이를 사용자가 제출한 임의 값과 비교하고 동일한 임의 값으로 사용자에게 보상합니다. 시간을 기준으로 임의의 상황인 것 같지만 사실 keccak 256(abi.encodePacked(block.timestamp))을 사용하는 모든 사용자는 계약 호출을 통해 값을 계산하고 이를 위너 함수의 계약 코드로 보낼 수 있습니다. eth를 얻습니다. 또한 block.timestamp의 값은 채굴자에 의해 악의적으로 변조될 수 있으며 반드시 공정하지 않다는 점도 이해해야 합니다.
수리 조치:, 오프라인 무작위 값을 얻기 위해 chainlink 사용을 고려하십시오.
세븐, DOS:
보조 제목
1. 상태변수 배열 전체를 메모리 변수에 복사하는 작업을 금지합니다.
취약점 세부 정보:
함수의 사용 가능한 메모리 크기에 대한 Solidity의 제한은 스토리지(0x ffffffffffffffff)보다 훨씬 낮습니다. 동적 배열을 전체적으로 메모리에 복사하는 모든 동작은 사용 가능한 메모리 크기를 초과하여 되돌려질 수 있습니다.
contract Eocene{
uint[] id;
function pop(uint amount) public{
require(amount>0,'not valid amount');
uint[] memory _id=id; // this may be revert because of memory space limit
for(uint i= 0;i<_id.length;i++)
{
if(amount==_id[i]){
id[i] = 0;
}
}
}
function push(uint amount) public{
require(amount>0,'not valid amount');
id.push(amount);
}
}
다음 코드 형식을 고려하십시오.
위 코드에서 `uint[] memory _id=id;`는 저장소에 있는 `uint[] id;`의 변수 값을 메모리에 넣고 푸시 함수는 `uint[] id에 값을 삽입할 수 있습니다. ;`, 그리고 Solidity는 메모리 공간에 제한이 있기 때문에 `uint[] id;`의 길이가 `(0x ffffffffffffffff-0x 40)/0x 20-1`을 초과하면 과도한 메모리 사용이 발생하고 되돌아갑니다. 이는 본 컨트랙트의 팝 기능이 절대 성공적으로 실행될 수 없거나 `uint[] memory _id=id;` 연산을 가진 어떤 기능도 성공적으로 실행될 수 없음을 의미합니다.
수리 조치:언제든지 가변 동적 배열을 메모리에 복사하지 마십시오.메모리 사용량이 이 값을 초과하는 함수는 성공적으로 실행할 수 없습니다.。
보조 제목
2. 모든 for 루프에서 루프 판단은 외부 수정 가능 변수를 기반으로 할 수 없습니다.
취약점 세부 정보:
임의의 for 루프의 판단이 외부 수정 가능 변수를 기반으로 하는 경우 외부 수정 가능 변수가 너무 크고 가스 소비가 너무 많은 문제가 있을 수 있습니다. DOS 공격은 각 계약 호출자가 감당할 수 있을 만큼 가스 소비가 충분히 높을 때 발생합니다.
contract Eocene{
uint[] id;
function pop(uint amount) public{
require(amount>0,'not valid amount');
for(uint i= 0;i
{
if(amount==id[i]){
id[i] = 0;
}
}
}
function push(uint amount) public{
require(amount>0,'not valid amount');
id.push(amount);
}
}
다음 코드를 고려하십시오.
여기서 스토리지에서 메모리로 배열을 복사하는 작업을 삭제하지만 이 코드의 또 다른 문제는 for 루프가 `uint[] id;`의 길이를 기반으로 하고 id의 길이는 늘릴 수만 있지만 늘릴 수는 없다는 것입니다. 즉, pop() 함수가 소비하는 가스가 점점 더 커진다는 의미이며, 가스가 너무 커서 pop 함수를 실행하여 허용할 수 있는 최대 가스 소비를 초과할 경우 소수의 사람들이 pop을 실행하게 됩니다. , DOS 공격이 실현됩니다.
수리 조치:, 모든 루프 작업은 dos 문제의 존재를 방지하기 위해 실행의 최대 길이를 판단할 수 있어야 합니다.
보조 제목
3. 루프에서 try/catch를 사용하여 결정되지 않은 예외를 포착합니다.
취약점 세부 정보:
contract Eocene{
address[] candidates;
mapping(address=>uint) balanceof;
function claim() public{
for(uint i= 0;i
{
address candidate = candidates[i];
require(balanceof[candidate]>0,'no balance');
payable(candidate).transfer(balanceof[candidate]);
}
}
}
루프 내부의 외부 주소로 인해 발생할 수 있는 되돌리기가 있는 경우 되돌리기 캡처를 고려해야 합니다. 그렇지 않으면 내부 루프 실행이 실패하면 이전의 모든 가스 소비가 의미가 없습니다. 그러나 루프 내부의 실행 실패를 외부 주소로 제어할 수 있는 경우 가능한 예외를 포착하기 위한 try/catch가 없으면 루프 판단이 완전히 완료되지 않아 DOS 공격을 실현할 수 있습니다.
이 코드는 for 루프를 사용하여 각 후보에게 돈을 이체하지만 폴백 또는 수신 기능에서 후보가 직접 되돌려질 때 루프가 성공적으로 실행되지 않아 DOS 공격을 실현한다는 점을 고려하지 않습니다.
어떤 for 루프에서든 외부 호출이 있고 호출이 되돌릴지 여부를 판단할 수 없는 경우 revert로 인한 DOS 공격을 방지하기 위해 try/catch를 사용하여 예외를 보충해야 합니다.。
8. 상위 버전 컴파일러 사용:
보조 제목
1. 0.8.17 이상의 컴파일러를 사용하여 계약 컴파일
첫 번째 레벨 제목
- Solidity
회사 소개
At Eocene Research, we provide the insights of intentions and security behind everything you know or don't know of blockchain, and empower every individual and organization to answer complex questions we hadn't even dreamed of back then.
