‘이더리움 댑 개발’ 세미나 17.1 보안 – Ethereum Smart Contract Security Best Practices

Ethereum Smart Contract Best Practices의 내용을 정리합니다.

  • 이 문서는 솔리디티 프로그래머들에게 보안에 대한 기반 지식을 제공하기 위해 작성한 것으로 ConsenSys Diligence에 의해 관리됩니다.
  • 다음과 같은 주제들을 다룹니다.
    • General Philosophy
      • describes the smart contract security mindset
    • Smart Contract Recommendations
      • contains examples of good code patterns
    • Known Attacks
      • describes the different classes of vulnerabilities to avoid
    • Software Engineering
      • outlines some architectural and design approaches for risk mitigation
    • Documentation and Procedures
      • outlines best practices for documenting your system for other developers and auditors
    • Security Tools
      • lists tools for improving code quality, and detecting vulnerabilities
    • Tokens
      • outlines best practices specifically related to Tokens.
    • Bug Bounties
      • List of bug bounties in the ecosystem.

General Philosophy

이더리움과 복잡한 블록체인 프로그램들은 새롭고 매우 실험적이기 때문에 보안에 있어서도 지속적인 변화는 당연한 것으로 받아들여야 합니다. 새로운 버그나 보안 위험이 발견될 것이고, 새로운 베스트 프랙티스가 개발될 것입니다. 그런 관점에서 아래 제시된 보안 프랙티스들 또한 단지 스마트 컨트랙트 개발자로서 할 필요가 있는 보안 작업의 시작일 뿐입니다.

스마트 컨트랙트 프로그램은 익숙한 것들이 아닌 다른 공학적인 마인드셋을 요구합니다. 실패 비용이 높을 수 있고 변경이 어려울 수 있습니다. 어떤 면에서는 웹이나 모바일 개발 보다는 하드웨어 프로그램이나 금융 서비스 프로그램에 가깝습니다. 그러므로 알려진 취약성에 대한 방어로는 충분하지 않고 개발에 대한 새로운 철학을 배울 필요가 있습니다.

  • 돈(돈과 같은 가치)을 다루고 있고 누구에게나 열려 있기 때문에 돈을 훔치려는 공격은 필연적입니다. 따라서 보안이 중요하고 개발에 있어 보수적이고 방어적이어야 합니다. 생명을 다루거나(심장 제어) 안전을 다루는(원자력이나 우주항공) 소프트웨어를 개발한다는 마음가짐이 필요합니다.

 

Prepare for failure

실패에 대해 준비하는 자세를 가져야 합니다.

소프트웨어 개발은 완벽을 추구하더라도 에러가 있을 수 밖에 없기 때문에 실패에 대비해야 합니다. 버그나 취약성이 발생했을 때 그 영향력을 최소화할 수 있도록 해야 합니다.

  • 문제가 생기면 멈출 수 있어야 합니다.
    • 주식 시장의 서킷브레이크가 좋은 예입니다.
  • 위험을 관리할 수 있어야 합니다.
    • 위험에 처할 수 있는 자산 수량을 제한해야 합니다.
  • 버그를 고치고 기능을 개선할 수 있도록 효과적인 업그레이드 패스가 있어야 합니다.
    • 블록체인의 고유한 특성을 해치지 않아야 합니다.

 

Rollout carefully

주의 깊게 출시를 준비해야 합니다.

  • 철저하게 컨트랙트를 테스트해야 합니다.
    • 새로운 공격 유형이 발견되면 바로 테스트를 추가해야 합니다.
  • 알파 테스트넷을 릴리즈할 때 버그 바운티를 제공하도록 합니다.
  • 각 단계에서 테스팅하고 사용이 증가됨에 따라 단계별로 출시하도록 합니다.

 

Keep contracts simple

컨트랙트는 단순하게 유지해야 합니다.

  • 컨트랙트 로직은 단순하도록(simple) 합니다.
  • 모듈화를 통해 컨트랙트와 함수를 작게 유지합니다.
  • 검증된 도구나 코드를 사용합니다.
  • 성능보다 명확성을 추구합니다.
  • 탈중앙화가 요구되는 부분에 대해서만 블록체인을 사용합니다.

 

Stay up to date

최신 상태를 유지하도록 합니다.

  • 새로운 버그가 발견되면 바로 컨트랙트를 체크합니다.
  • 도구와 라이브러리를 최신 버전으로 업그레이드 합니다.
  • 유용한 새로운 보안 기술을 채택합니다.

 

Be aware of blockchain properties

블록체인 특성에 주의하도록 합니다.

  • 외부 컨트랙트 함수를 호출할 때 특별히 주의를 기울입니다.
    • 악의 적인 코드를 실행할 수도 있고 제어 흐름을 변경할 수도 있습니다.
  • public 함수는 이름 그대로 public이라는 점을 알아야 합니다.
    • 악의적으로 임의적인 순서로 호출될 수도 있습니다.
  • private으로 상태 변수가 선언되었다고 해서 데이터를 숨기는 것은 아닙니다.
    • private은 코드 접근성에 대한 것이지 데이터를 암호화하는 것은 아닙니다.
      • 공개 블록체인에서는 누구나 데이터 값을 읽을 수 있습니다.
  • 가스 비용과 블록 가스 한도에도 주의를 기울여야 합니다.
  • 블록체인에서 타임스탬프가 부정확하다는 것을 알아야 합니다.
    • 채굴자는 몇 초 정도 내에서 트랜잭션의 실행 시간에 영향을 줄 수 있습니다.
  • 블록체인에서 난수 생성이 쉽지 않음을 알아야 합니다.

 

Fundamental Tradeoffs

스마트 컨트랙트 시스템의 구조나 보안을 평가할 때 균형을 맞춰야할 근본적인 트레이드오프들이 있습니다.

소프트웨어 공학 측면에서 이상적인 스마트 컨트랙트 시스템은 모듈화되고 재사용 가능하고 업그레이드 가능한 것일 수 있습니다.  하지만 경우에 따라서는 이러한 조건이 이상적이지 않을 수 있습니다.

Rigid versus Upgradeable

소프트웨어 공학 관점에서 Upgradeable을 추구하는 것은 당연한 것입니다. 하지만 스마트 컨트랙트를 작성할 때는 그렇지 않을 수 있습니다.

변경하지 않는 것이 중요한 합의라면 스마트 컨트랙트는 업그레이드할 수 없어야 합니다.

토큰 세일을 위한 컨트랙트와 같이 특정 기간 동안에만 유효한 매우 제한된 기능을 가진 컨트랙트라면 변경가능성은 중요하지 않습니다.

Monolithic versus Modular

소프트웨어 공학 관점에서 보면 모듈화를 추구하는 것은 당연한 것입니다. 하지만 보안이 중요한 스마트 컨트랙트에서는 그렇지 않을 수 있습니다.

모듈화가 된다는 것은 다수의 컨트랙트들에 로직이 분산된다는 것으로 코드 검토 효율성을 떨어뜨릴 수 있습니다. 특히 간단하고 짧은 생애주기를 갖는 컨트랙트라면 모듈화는 중요한 고려요소가 아닙니다. 물론 크고 복잡한 생애주기가 긴 스마트 컨트랙트의 경우라면 모듈화는 중요합니다.

Duplication versus Reuse

소프트웨어 공학 관점에서 보면 중복은 악의 축이고 재사용을 추구하는 것은 당연한 것입니다.

솔리디티에서 컨트랙트 코드를 재사용하는 방법은 다양합니다.

자신이 소유한 이전에 배포된 검증된 컨트랙트를 재사용하는 것은 안전하게 코드를 재사용하는 방법입니다. 자신이 작성한 코드가 아닌 경우 배포된 컨트랙트를 재사용하는 것은 위험할 수 있습니다.

오픈제플린의 검증된 컨트랙트 코드를 재사용하는 방법도 있습니다.

Secure Development Recommendations

Protocol specific recommendations

External Calls
  • 모든 외부 컨트랙트 함수 호출은 잠재적인 보안 위험으로 다루어야 합니다.
    • 대응 방법이 없다면 사용하지 않도록 합니다.
    • 외부 호출 부분에서 신뢰할 수 없는 부분을 이름으로 표시합니다.
      • When interacting with external contracts, name your variables, methods, and contract interfaces in a way that makes it clear that interacting with them is potentially unsafe. This applies to your own functions that call external contracts.
    • 외부 호출 후에 상태를 변경하지 않도록 합니다.
      • 재진입 위험이 있습니다.
        • checks-effects-interactions 패턴을 사용합니다.

 

Don’t use transfer() or send()
  • transfer()와 send()는 수신자에게 정확히 2,300 가스를 전달합니다.
    • 재진입을 막을 수 있습니다.
      • 가스 비용이 변하지 않을 것이라는 가정이 깔려있는 해결책입니다. EIP 1884가 이스탄불 하드포크에 적용되었는데 여기서 SLOAD의 가스 비용을 증가시키는 바람에 컨트랙트의 fallback함수의 가스비가 2300이 넘어버리는 문제가 발생했습니다.
  • call()을 사용하도록 합니다.
    • 재진입 위험을 다루어야 합니다.

 

Handle errors in external calls
  • call(), callcode(), delegatecall(), send()와 같은 저수준 함수들은 예외를 던지지 않고 예외에 대해 false를 리턴합니다.
  • 리턴 값을 체크해야 합니다.

 

Favor pull over push for external calls
  • 외부 호출은 의도적이든 그렇지 않든 실패할 수 있기 때문에 반복문에서 외부 호출하는 것은 위험합니다.
    • 외부 호출이 개별적인 트랜잭션 처리가 되도록 합니다.

 

Don’t delegatecall to untrusted code
  • delegatecall은 호출된 컨트랙트가 호출한 컨트랙트의 컨텍스트에서 실행되기 때문에 호출된 컨트랙트가 호출한 컨트랙트의 상태를 변경할 수 있는 위험이 있습니다.
    • delegatecall은 신뢰할 수 있는 컨트랙트로 제한하고, 사용자가 공급하는 주소는 결코 사용하지 않도록 합니다.
    • 가능한 상태를 변경하지 않는 라이브러리 함수 호출로 delegatecall 사용을 제한합니다.

 

Remember that Ether can be forcibly sent to an account
  • 컨트랙트 잔고를 엄격하게 검사해야 한다면 주의를 기울여야 합니다.
  • 공격자들은 막을 수 없는 방법으로 이더를 강제적으로 보낼 수 있습니다. revert를 수행하는 fallback 함수를 사용한다고 하더라도.
    • 공격자는 컨트랙트를 생성하고 아주 작은 이더를 컨트랙트에 보낸 후, 특정 주소를 인자로 해서 selfdestruct를 호출합니다. 어떤 코드도 실행되지 않고 해당 주소로 컨트랙트 소멸에 대한 인센티브가 전달되게 됩니다. 심지어 컨트랙트 주소는 배포 전에도 알 수 있기 때문에 배포 전에 컨트랙트로 이더를 보낼 수도 있습니다.

 

Remember that on-chain data is public
  • If you are building an application where privacy is an issue, make sure you avoid requiring users to publish information too early.
  • The best strategy is to use commitment schemes with separate phases.
    • first commit using the hash of the values and in a later phase revealing the values.
      • 예)
        • In rock paper scissors, require both players to submit a hash of their intended move first, then require both players to submit their move; if the submitted move does not match the hash throw it out.
        • In an auction, require players to submit a hash of their bid value in an initial phase (along with a deposit greater than their bid value), and then submit their auction bid value in the second phase.
        • When developing an application that depends on a random number generator, the order should always be (1) players submit moves, (2) random number generated, (3) players paid out. The method by which random numbers are generated is itself an area of active research; current best-in-class solutions include Bitcoin block headers (verified through http://btcrelay.org), hash-commit-reveal schemes (ie. one party generates a number, publishes its hash to “commit” to the value, and then reveals the value later) and RANDAO. As Ethereum is a deterministic protocol, no variable within the protocol could be used as an unpredictable random number. Also be aware that miners are in some extent in control of the block.blockhash() value.

 

Beware of the possibility that some participants may “drop offline” and not return
  • Do not make refund or claim processes dependent on a specific party performing a particular action with no other way of getting the funds out.
    • For example, in a rock-paper-scissors game, one common mistake is to not make a payout until both players submit their moves; however, a malicious player can “grief” the other by simply never submitting their move – in fact, if a player sees the other player’s revealed move and determines that they lost, they have no reason to submit their own move at all. This issue may also arise in the context of state channel settlement. When such situations are an issue, (1) provide a way of circumventing non-participating participants, perhaps through a time limit, and (2) consider adding an additional economic incentive for participants to submit information in all of the situations in which they are supposed to do so.

 

Beware of negation of the most negative signed integer
  • Solidity provides several types to work with signed integers. Like in most programming languages, in Solidity a signed integer with N bits can represent values from -2^(N-1) to 2^(N-1)-1. This means that there is no positive equivalent for the MIN_INT. Negation is implemented as finding the two’s complement of a number, so the negation of the most negative number will result in the same number.
  • This is true for all signed integer types in Solidity (int8, int16, …, int256).
  • One way to handle this is to check the value of a variable before negation and throw if it’s equal to the MIN_INT. Another option is to make sure that the most negative number will never be achieved by using a type with a higher capacity (e.g. int32 instead of int16).

 

Solidity specific recommendations

Enforce invariants with assert()
  • assert는 불변식을 나타냅니다. 불변식은 컨트랙트의 생애주기 동안 항상 참이어야 하는 조건입니다. assert() 함수는 이러한 조건을 체크하고 조건이 거짓이면 예외를 던집니다.

 

Use assert(), require(), revert() properly
  • assert 함수는 내부 에러를 테스트하거나 불변식을 체크하기 위해 사용합니다.
  • require  함수는 선행 조건이나 외부 컨트랙트 호출에 대한 리턴 값을 체크하는데 사용합니다.

 

Use modifiers only for checks
  • The code inside a modifier is usually executed before the function body, so any state changes or external calls will violate the Checks-Effects-Interactions pattern. Moreover, these statements may also remain unnoticed by the developer, as the code for modifier may be far from the function declaration. For example, an external call in modifier can lead to the reentrancy attack:

 

Beware rounding with integer division
  • All integer division rounds down to the nearest integer.
    • If you need more precision, consider using a multiplier, or store both the numerator and denominator.

 

Keep fallback functions simple, Check data length in fallback functions
  • fallback 함수는 2,300가스 만을 사용할 수 있기 때문에 이벤트를 로그할 수 있는 정도만을 할 수 있습니다.
  • fallback 함수를 호출했다는 것은 data가 없다는 것을 의미합니다.
    • require(msg.data.length == 0);

 

Explicitly mark payable functions and state variables
Explicitly mark visibility in functions and state variables
Lock pragmas to specific compiler version
  • pragma solidity 0.4.25;
  • pragma solidity =0.4.25;

 

Use events to monitor contract activity
  • 배포된 후에 컨트랙트의 활동을 모니터링하는 방법을 갖는 것은 유용합니다.
    • 이렇게 할 수 있는 한 가지 방법은 컨트랙트의 모든 트랜잭션을 조사하는 것입니다.
      • 인자들만 보여줄 뿐 상태 변수 값의 변화를 보여주지 않습니다.
      • 컨트랙트들 사이의 메시지 호출은 블록체인에 기록되지 않습니다.
    • 이벤트를 사용하는 것이 더 나은 방법입니다.
      • 사용자 인터페이스에서 트리거 함수를 위해 사용될 수도 있습니다.

 

Prefer constructs/aliases
  • selfdestruct (over suicide)
  • keccak256 (over sha3)
  • Patterns like require(msg.sender.send(1 ether)) can also be simplified to using transfer(), as in msg.sender.transfer(1 ether).

 

Be aware that ‘Built-ins’ can be shadowed
  • It is currently possible to shadow built-in globals in Solidity. This allows contracts to override the functionality of built-ins such as msg and revert(). Although this is intended, it can mislead users of a contract as to the contract’s true behavior.

 

Avoid using tx.origin
  • Never use tx.origin for authorization, another contract can have a method which will call your contract (where the user has some funds for instance) and your contract will authorize that transaction as your address is in tx.origin.
  • You should use msg.sender for authorization (if another contract calls your contract msg.sender will be the address of the contract and not the address of the user who called the contract).

 

 

Timestamp Dependence
  • Be aware that the timestamp of the block can be manipulated by a miner.
  • Popular Ethereum protocol implementations Geth and Parity both reject blocks with timestamp more than 15 seconds in future.
  • Avoid using block.number as a timestamp.
  • If the scale of your time-dependent event can vary by 15 seconds and maintain integrity, it is safe to use a block.timestamp.

 

Multiple Inheritance Caution
Use interface type instead of the address for type safety
  • When a function takes a contract address as an argument, it is better to pass an interface or contract type rather than raw address. If the function is called elsewhere within the source code, the compiler it will provide additional type safety guarantees.

 

 

Avoid using extcodesize to check for Externally Owned Accounts
  • The idea is straight forward: if an address contains code, it’s not an EOA but a contract account. However, a contract does not have source code available during construction.
  • If your goal is to prevent other contracts from being able to call your contract, the extcodesize check is probably sufficient.

 

Known Attacks

Reentrancy

배포된 다른 컨트랙트 함수를 호출한다는 것은 호출된 컨트랙트에게 제어를 넘긴다는 것을 의미합니다. 함수의 중간에 호출했다면 호출되기 전에 이미 실행된 부분이 있고, 호출 후에 실행될 부분이 있게 됩니다.

  • On June 17th 2016, The DAO was hacked and 3.6 million Ether ($50 Million) were stolen using the first reentrancy attack.

 

제어를 넘겨 받은 컨트랙트가 호출한 함수를 다시 호출하는 것을 재진입이라고 합니다. 재진입은 호출 함수가 어떻게 로직을 작성했느냐에 따라 악용될 위험이 있습니다. 재진입은 단일 함수와 관련해서 이루어질 수도 있고, 같은 상태 변수를 다루는 다수의 함수와 관련해서 이루어질 수도 있습니다.

  • Since reentrancy can occur across multiple functions, and even multiple contracts, any solution aimed at preventing reentrancy with a single function will not be sufficient.

재진입 위험을 제거하기 위해서는

  • 모든 내부적인 작업(상태 변경과 같은)을 먼저 마친 후 외부 함수를 호출하도록 합니다.
  • 외부 함수 호출 부분과 관련해서 안전하지 않음을 표시하도록(untrusted 접미어를 사용해서)  합니다.
  • 뮤텍스를 사용는 방법을 사용할 수도 있습니다.
    • 상태를 잠가서 재진입을 막습니다.
    • 다수의 컨트랙트가 협력해야 할 경우 복잡해질 수 있습니다.
      • 상태가 잠긴채로 남아 있을 수 있습니다.

 

Front-Running

트랜잭션이 노드에 전송되면 새로운 블록체인에 포함되기 전까지 메모리 풀에 들어가게 됩니다. 채굴자는 새로운 블록체인에 포함할 트랜잭션을 가스비 기준으로 선택합니다. 블록 마다 가스 한도가 있고 가스 한도 내에서 트랜잭션을 포함할 수 있습니다. 누구나 메모리 풀에 있는 트랜잭션 정보에 접근할 수 있습니다.

이러한 특징으로 인해 공격자는 트랜잭션 실행 순서를 조정할 수 있습니다.

  • 정보를 그대로 가져다가 높은 가스비를 설정해서 먼저 블록체인에 포함될 수 있도록 할 수 있습니다.
  • 가스비 설정을 통해 트랜잭션의 앞과 뒤에 공격을 위한 트랜잭션을 배치하도록 할 수 있습니다.
  • 높은 가스비와 해당 트랜잭션을 포함하면 다른 트랜잭션을 포함할 가스 한도가 남지 않게 해서 다른 트랜잭션들이 특정 기간 동안 블록에 포함되지 않도록 할 수 있습니다.

 

front-running을 막는 최선의 방법은 front-running으로 얻을 수 있는 것이 없도록 하는 것입니다.

  • 트랜잭션 순서나 시간이 중요한 요소가 되지 않도록 합니다.
    • For example, in markets, it would be better to implement batch auctions (this also protects against high frequency trading concerns). Another is way to use a pre-commit scheme (“I’m going to submit the details later”). A third option is to mitigate the cost of front-running by specifying a maximum or minimum acceptable price range on a trade, thereby limiting price slippage.
  • Another approach is to limit the visibility of the transactions, this can be done using a “commit and reveal” scheme. A simple implementation is to store the keccak256 hash of the data in the first transaction, then reveal the data and verify it against the hash in the second transaction. However note that the transaction itself, leaks the intention and possibly the value of the collateralization. There are enhanced commit and reveal schemes that are more secure, however require more transactions to function, e.g. submarine sends.

 

Timestamp Dependence

블록의 타임스탬프는 채굴자들에게 어느 정도의 시간 오차를 허용하고 있기 때문에 타임스탬프를 사용할 때는 주의를 기울여야 합니다.

  • 난수 생성을 위해 타임스탬프를 사용하지 않도록 합니다. 채굴자에 의해 오용될 수 있습니다.
  • block.number를 타임스탬프로 사용하지 않도록 합니다.
    • It is possible to estimate a time delta using the block.number property and average block time, however this is not future proof as block times may change (such as fork reorganisations and the difficulty bomb). In a sale spanning days, the 15-second rule allows one to achieve a more reliable estimate of time.

Integer Overflow and Underflow

정수의 오버플로어와 언더플로어에 주의를 기울여야 합니다.

이 문제는 아주 일반적이고 공통적으로 발생하는 것이기 때문에 일찍 부터 해결책이 제시되어 왔습니다. 가장 간단한 해결책은 오픈제플린의 SafeMath와 같은 라이브러리를 사용하는 것입니다.

 

DoS with (Unexpected) revert

외부 컨트랙트 함수를 호출하고, 순서가 중요한 로직을 포함한 함수에서 발생할 수 있는 문제입니다.

공격자는 의도적으로 예외를 발생시켜서 자신을 제외한 어떠한 트랜잭션도 해당 함수 실행에 실패하도록 합니다.

  • If attacker bids using a smart contract which has a fallback function that reverts any payment, the attacker can win any auction. When it tries to refund the old leader, it reverts if the refund fails. This means that a malicious bidder can become the leader while making sure that any refunds to their address will always fail. In this way, they can prevent anyone else from calling the bid() function, and stay the leader forever.

 

This is especially relevant for payments, where it is better to let users withdraw funds rather than push funds to them automatically. (This also reduces the chance of problems with the gas limit.) Avoid combining multiple ether transfers in a single transaction.

 

DoS with Block Gas Limit

위에서 다룬 것처럼 push 방식을 사용해서 반복적인 처리가 필요한 경우 가스 한도를 이용한 공격에 노출될 수 있습니다.

If you absolutely must loop over an array of unknown size, then you should plan for it to potentially take multiple blocks, and therefore require multiple transactions. You will need to keep track of how far you’ve gone, and be able to resume from that point.

 

Insufficient gas griefing

This attack may be possible on a contract which accepts generic data and uses it to make a call another contract (a ‘sub-call’) via the low level address.call() function, as is often the case with multisignature and transaction relayer contracts.

  • When a contract makes a sub-call to another contract, the EVM limits the gas forwarded to to 63/64 of the remaining gas.

An attacker can use this to censor transactions, causing them to fail by sending them with a low amount of gas. This attack is a form of “griefing”: It doesn’t directly benefit the attacker, but causes grief for the victim. A dedicated attacker, willing to consistently spend a small amount of gas could theoretically censor all transactions this way, if they were the first to submit them to Relayer.

One way to address this is to implement logic requiring forwarders to provide enough gas to finish the subcall. If the miner tried to conduct the attack in this scenario, the require statement would fail and the inner call would revert. A user can specify a minimum gasLimit along with the other data (in this example, typically the _gasLimit value would be verified by a signature, but that is ommitted for simplicity in this case).

Another solution is to permit only trusted accounts to relay the transaction.

 

Forcibly Sending Ether to a Contract

강제적으로 컨트랙트에 이더를 보낼 수 있다는 방법이 있다는 것을 알아야 합니다.

  • The selfdestruct contract method allows a user to specify a beneficiary to send any excess ether. selfdestruct does not trigger a contract’s fallback function.
  • It is also possible to precompute a contract’s address and send Ether to that address before deploying the contract.

 

This is an important consideration when placing important logic in the fallback function or making calculations based on a contract’s balance.

Other Vulnerabilities

The Smart Contract Weakness Classification Registry offers a complete and up-to-date catalogue of known smart contract vulnerabilities and anti-patterns along with real-world examples. Browsing the registry is a good way of keeping up-to-date with the latest attacks.

Software Engineering Techniques

스마트 컨트랙트는 실패 비용이 크기 때문에 알려진 위험에 대처하는 것만으로는 충분하지 않습니다. “실패를 준비하는” 접근이 필요합니다.

실패를 원천적으로 막을 수는 없더라도 실패가 발생했을 때의 비용을 최소화 하도록 설계해야 합니다.

  • Note: There’s always a risk when you add a new component to your system. A badly designed fail-safe could itself become a vulnerability – as can the interaction between a number of well designed fail-safes. Be thoughtful about each technique you use in your contracts, and consider carefully how they work together to create a robust system.

 

Upgrading Broken Contracts

합의에 의해 변경 가능한 컨트랙트와 그렇지 않은 컨트랙트를 구분해야 합니다. 변경할 수 없다고 합의된 컨트랙트에 대해서는 더 주의가 필요합니다.

변경 가능한 컨트랙트 작성은 공통적이고 일반화된 것으로 직접 작성하기 보다는 오픈제플린 컨트랙트와 같이 검증된 컨트랙트를 활용하도록 합니다.

 

Circuit Breakers (Pause contract functionality)

증권 시장에서의 서킷 브레이크 처럼 비상 시에 컨트랙트 기능을 일시 중지할 수 있도록 작성합니다. 컨트랙트의 특정 기능은 비상 시에만 기능하도록 작성해 두기도 합니다.

 

Speed Bumps (Delay contract actions)

Speed bumps slow down actions, so that if malicious actions occur, there is time to recover. For example, The DAO required 27 days between a successful request to split the DAO and the ability to do so. This ensured the funds were kept within the contract, increasing the likelihood of recovery. In the case of the DAO, there was no effective action that could be taken during the time given by the speed bump, but in combination with our other techniques, they can be quite effective.

 

Rate Limiting

Rate limiting halts or requires approval for substantial changes. For example, a depositor may only be allowed to withdraw a certain amount or percentage of total deposits over a certain time period (e.g., max 100 ether over 1 day) – additional withdrawals in that time period may fail or require some sort of special approval. Or the rate limit could be at the contract level, with only a certain amount of tokens issued by the contract over a time period.

 

Contract Rollout

Contracts should have a substantial and prolonged testing period – before substantial money is put at risk.

At minimum, you should:

  • Have a full test suite with 100% test coverage (or close to it)
  • Deploy on your own testnet
  • Deploy on the public testnet with substantial testing and bug bounties
  • Exhaustive testing should allow various players to interact with the contract at volume
  • Deploy on the mainnet in beta, with limits to the amount at risk
Automatic deprecation

During testing, you can force an automatic deprecation by preventing any actions, after a certain time period. For example, an alpha contract may work for several weeks and then automatically shut down all actions, except for the final withdrawal.

Restrict amount of Ether per user / contract

In the early stages, you can restrict the amount of Ether for any user (or for the entire contract) – reducing the risk.

 

Bug Bounty Programs

Some tips for running bounty programs:

  • Decide which currency bounties will be distributed in (BTC and/or ETH)
  • Decide on an estimated total budget for bounty rewards
  • From the budget, determine three tiers of rewards:
    • smallest reward you are willing to give out
    • highest reward that’s usually awardable
    • an extra range to be awarded in case of very severe vulnerabilities
  • Determine who the bounty judges are (3 may be ideal typically)
  • Lead developer should probably be one of the bounty judges
  • When a bug report is received, the lead developer, with advice from judges, should evaluate the severity of the bug
  • Work at this stage should be in a private repo, and the issue filed on Github
  • If it’s a bug that should be fixed, in the private repo, a developer should write a test case, which should fail and thus confirm the bug
  • Developer should implement the fix and ensure the test now passes; writing additional tests as needed
  • Show the bounty hunter the fix; merge the fix back to the public repo is one way
  • Determine if bounty hunter has any other feedback about the fix
  • Bounty judges determine the size of the reward, based on their evaluation of both the likelihood and impact of the bug.
  • Keep bounty participants informed throughout the process, and then strive to avoid delays in sending them their reward

For an example of the three tiers of rewards, see Ethereum’s Bounty Program:

  • The value of rewards paid out will vary depending on severity of impact. Rewards for minor ‘harmless’ bugs start at 0.05 BTC. Major bugs, for example leading to consensus issues, will be rewarded up to 5 BTC. Much higher rewards are possible (up to 25 BTC) in case of very severe vulnerabilities.

Token Implementation Best Practice

Be aware of front running attacks on EIP-20

The EIP-20 token’s approve() function creates the potential for an approved spender to spend more than the intended amount. A front running attack can be used, enabling an approved spender to call transferFrom() both before and after the call to approve() is processed. More details are available on the EIP, and in this document.

Prevent transferring tokens to the 0x0 address

At the time of writing, the “zero” address (0x0000000000000000000000000000000000000000) holds tokens with a value of more than 80$ million.

Prevent transferring tokens to the contract address

Consider also preventing the transfer of tokens to the same address of the smart contract.

Documentation and Procedures

When launching a contract that will have substantial funds or is required to be mission critical, it is important to include proper documentation. Some documentation related to security includes:

Specifications and Rollout Plans

  • Specs, diagrams, state machines, models, and other documentation that helps auditors, reviewers, and the community understand what the system is intended to do.
  • Many bugs can be found just from the specifications, and they are the least costly to fix.
  • Rollout plans that include details listed here, and target dates.

 

Status

  • Where current code is deployed
  • Compiler version, flags used, and steps for verifying the deployed bytecode matches the source code
  • Compiler versions and flags that will be used for the different phases of rollout.
  • Current status of deployed code (including outstanding issues, performance stats, etc.)

 

Known Issues

  • Key risks with contract
  • e.g., You can lose all your money, hacker can vote for certain outcomes
  • All known bugs/limitations
  • Potential attacks and mitigants
  • Potential conflicts of interest (e.g., will be using yourself, like Slock.it did with the DAO)

 

History

  • Testing (including usage stats, discovered bugs, length of testing)
  • People who have reviewed code (and their key feedback)

 

Procedures

  • Action plan in case a bug is discovered (e.g., emergency options, public notification process, etc.)
  • Wind down process if something goes wrong (e.g., funders will get percentage of your balance before attack, from remaining funds)
  • Responsible disclosure policy (e.g., where to report bugs found, the rules of any bug bounty program)
  • Recourse in case of failure (e.g., insurance, penalty fund, no recourse)

 

Contact Information

  • Who to contact with issues
  • Names of programmers and/or other important parties
  • Chat room where questions can be asked

Security Tools

Visualization

  • Solidity Visual Auditor – This extension contributes security centric syntax and semantic highlighting, a detailed class outline and advanced Solidity code insights to Visual Studio Code
  • Sūrya – Utility tool for smart contract systems, offering a number of visual outputs and information about the contracts’ structure. Also supports querying the function call graph.
  • Solgraph – Generates a DOT graph that visualizes function control flow of a Solidity contract and highlights potential security vulnerabilities.
  • EVM Lab – Rich tool package to interact with the EVM. Includes a VM, Etherchain API, and a trace-viewer.
  • ethereum-graph-debugger – A graphical EVM debugger. Displays the entire program control flow graph.
  • Piet – Web application helping understand smart contract architectures. Offers graphical representation and inspection of smart contracts as well as a markdown documentation generator.

 

Static and Dynamic Analysis

  • MythX – MythX is a professional-grade cloud service that uses symbolic analysis and input fuzzing to detect common security bugs and verify the correctness of smart contract code. Using MythX requires an API key from mythx.io.
  • Mythril – The Swiss army knife for smart contract security.
  • Slither – Static analysis framework with detectors for many common Solidity issues. It has taint and value tracking capabilities and is written in Python.
  • Contract-Library – Decompiler and security analysis tool for all deployed contracts.
  • Echidna – The only available fuzzer for Ethereum software. Uses property testing to generate malicious inputs that break smart contracts.
  • Manticore – Dynamic binary analysis tool with EVM support.
  • Oyente – Analyze Ethereum code to find common vulnerabilities, based on this paper.
  • Securify – Fully automated online static analyzer for smart contracts, providing a security report based on vulnerability patterns.
  • SmartCheck – Static analysis of Solidity source code for security vulnerabilities and best practices.
  • Octopus – Security Analysis tool for Blockchain Smart Contracts with support of EVM and (e)WASM.
  • sFuzz – Efficient fuzzer inspired from AFL to find common vulnerabilities.
  • Vertigo – Mutation Testing for Ethereum Smart Contracts.

 

Weakness OSSClassifcation & Test Cases

  • SWC-registry – SWC definitions and a large repository of crafted and real-world samples of vulnerable smart contracts.
  • SWC Pages – The SWC-registry repo published on Github Pages

 

Test Coverage

 

Linters and Formatters

Linters improve code quality by enforcing rules for style and composition, making code easier to read and review.

  • Ethlint – Yet another Solidity linting.
  • Solhint – A linter for Solidity that provides both Security and Style Guide validations.
  • Prettier + Solidity Plugin – Prettier enforces basic style conventions in your code.

Bug Bounty Programs

The following are ongoing bug bounty programs, either focused on, or including smart contracts in their scope. Issues and PRs are welcome to add new bounties, or remove those which are no longer active.

About the Author
(주)뉴테크프라임 대표 김현남입니다. 저에 대해 좀 더 알기를 원하시는 분은 아래 링크를 참조하세요. http://www.umlcert.com/kimhn/

Leave a Reply

*