✔️ 솔리디티에서 외부 호출을 수행하는 함수 중, call
및 send
함수는 호출 성공 또는 실패 여부에 따라 true
, false
를 반환한다.
✔️ 여기서 주의할 점은 함수 호출에 실패하는 경우, 단순히 false
를 반환하기만 할 뿐 함수를 실행한 트랜잭션으로 돌아가는 것이 아니다.
✔️ 보통의 개발자는 외부 호출을 실패하게 되면 원래의 상태로 되돌아간다고 생각한다.
✅ 위 컨트랙트는 winner
가 winAmount
만큼의 이더를 받는 컨트랙트이다.
✅ 14행에서 send
함수를 실행했을 때 트랜잭션에 실패해서 false
를 반환하더라도 다음행에서 payedOut
변수의 값이 true
로 설정된다.
✅ 이렇게 payedOut
의 값이 true
가 된다면 withdraeLeftOver
함수를 통해 컨트랙트의 잔액(상금)을 출금할 수 있게 된다.
✅ transfer
함수는 트랜잭션이 실패할 경우 롤백되므로 send
함수보다는 transfer
함수를 사용하는 것이 좋다.
✔️ 레이스 컨디션(race condition) : 경쟁 조건을 의미하며 사용자들이 코드의 실행을 경쟁한다는 의미
✔️ 재진입성은 레이스 컨디션의 한 종류이다.
✅ 이더리움 노드는 트랜잭션 풀에서 트랜잭션을 선택해서 블록을 생성한다.
✅ 채굴자가 작업 증명 알고리즘을 통해 해시 값을 찾고 해당 트랜잭션이 포함된 블록을 채굴해야만 트랜잭션은 유효한 것으로 간주된다.
✅ 채굴자는 트랜잭션 풀에서 블록에 포함시킬 트랜잭션을 임의로 선택할 수 있다. (수수료 순 등...)
✅ 이때 공격자는 트랜잭션 풀에서 채굴자들이 푸는 문제에 대한 답, 논스 값을 포함하는 트랜잭션 정보를 얻을 수 있다.
✅ 논스 값을 찾은 채굴자의 권한을 취소하거나 트랜잭션 상태를 공격자가 변경할 수 있다.
✅ 특정 해시값을 도출하는 solution
을 찾으면 그 값을 찾은 사람은 1000이더를 받을 수 있는 컨트랙트이다.
✅ 만약 위 컨트랙트에서 solution
값이 Ethereum! 이라면 이 값을 solve
함수의 파라미터로 전달해서 이더를 받는다.
✅ 공격자는 바로 이 솔루션을 트랜잭션 풀에서 볼 수 있다.
✅ solution을 본 공격자는 해당 트랜잭션 보다 가스 가격이 높은 동일한 트랜잭션을 호출하면, 이 가스 값이 높은 트랜잭션이 채굴자에 의해 선택될 가능성이 높으므로 먼저 블록에 포함될 것이다.
✅ 공격자의 해당 트랜잭션이 블록에 포함되면 원래 문제를 풀었던 사람은 이더를 받지 못하고 공격자가 1000이더를 받게 된다.
gasPrice
를 수정하는)사용자와 (블록에서 트랜잭션을 재정렬할 수 있는)채굴자이다.gasPrice
를 높여서 트랜잭션 우선순위를 갖는 것을 방지한다. 이 방법은 채굴자의 공격은 막을 수 없다.사용자가 일정 기간 또는 영구적으로 컨트랙트를 실행할 수 없게 만드는 공격
컨트랙트가 동작하지 않게 할 수 있는 다양한 방법
인위적으로 부풀려질 수 있는 배열을 사용해서 배열 크기르 크게 만들면 for
루프를 실행하는데 요구되는 가스가 가스 한도를 초과하게 된다. 그렇게 되면 함수가 원하는대로 동작하지 않게 만들 수 있다.
컨트랙트의 기능을 사용하기 위해서 소유자의 작업이 필요한 경우, 권한을 가진 사용자가 개인키를 잃게 되면 컨트랙트를 사용할 수 없게 된다.
이더를 보내야 되는 조건을 갖는 경우에, 사용자가 이더의 수신을 허용하지 않는 컨트랙트를 만들 수 있다. 이런 컨트랙트는 외부 호출을 실패하게 하거나 차단해서 컨트랙트가 새로운 상태를 달성하지 못하게 한다.
루프의 조건에 외부 사용자가 인위적으로 조작할 수 있는 변수를 사용하면 안된다.
require(msg.sender == owner || now > unlockTime)
시간-잠금 해결책을 이 경우에도 사용할 수 있다. 외부 호출이 필요한 경우에서 실패하거나 호출이 일어나지 않은 경우에 특정 시간 이후 자동으로 상태를 진행시킬 수 있도록 컨트랙트를 작성하는 방법이 있다.
채굴자는 블록의 타임스탬프를 조정할 수 있는데, 이로 인해서 스마트 컨트랙트에서 블록 타임스탬프를 잘못 사용하면 문제가 발생할 수 있다.
✅ 채굴자는 필요한 경우 block.Timestamp
와 그 별칭인 now
를 조작할 수 있다.
✅ 컨트랙트의 잔액을 받는 조건을 달성하기 위해 채굴자는 타임스탬프를 조정할 수 있다.
블록 타임스탬프를 컨트랙트에서 특정 조건 체크를 위한 요소로 사용하면 안된다.
하지만 시간과 관련된 조건이 필요한 경우가 있다. 예를 들어 컨트랙트가 해지되는 시간, 만기일 적용과 같은 시간 관련 조건이 있을 경우에는 블록의 타임스탬프를 사용하지 말고 block.number
과 평균 블록 시간을 사용해서 설정하는 것이 좋다.
✅ 이더리움은 10초당 하나의 블록이 생성되고 일주일이면 대략 60,480개의 블록이 생성된다.
✅ 블록 번호는 블록 타임스탬프와는 다르게 채굴자가 쉽게 조작할 수 없기 때문에 블록의 생성 시간과 블록 넘버를 사용하면 안전한 컨트랙트를 작성할 수 있다.
✔️ 생성자(constructor)는 컨트랙트를 생성할 때 호출되는 값으로, 컨트랙트를 초기화할 때 종종 중요하고 권한을 필요로 하는 작업을 수행하는 특수 함수
✔️ 솔리디티 v0.4.22 이전 버전에서는 생성자를 constructor
로 정의하는 것이 아니라 컨트랙트와 이름이 같은 함수를 생성자로 정의했다.
✔️ 이러한 경우에 컨트랙트 이름이 변경되면 원래의 생성자는 생성자의 역할을 하지 못하고 일반 함수가 된다. 이 점이 취약점이 될 수 있다.
컨트랙트의 이름이 수정되거나 생성자의 이름을 잘못 지정하면 생성자는 일반 함수처럼 작동하게 된다.
✅ 해당 코드를 작성할 때 개발자는 생성자를 통해서 owner
변수를 컨트랙트 소유자로 설정하는 것을 목적으로 했다.
✅ 하지만 생성자 함수를 보면 이름이 잘못되어 생성자가 아닌 일반 함수로서 작동한다.
✅ 따라서 컨트랙트의 소유자가 아닌 일반 사용자가 해당 함수를 호출해서 자신이 컨트랙트의 소유자가 되고, withdraw
함수 호출을 통해 컨트랙트의 잔액을 자신의 계좌로 송금할 수 있다.
✅ 솔리디티 컴파일러 v0.4.22 이후부터는 constructor
키워드를 사용하므로 위와 같은 문제를 방지할 수 있다.
EVM은 스토리지(storage)와 메모리(memory)에 변수, 데이터를 저장한다.
✔️ 스토리지(storage) : 블록체인 상에 영구적으로 저장되는 변수
✔️ 메모리(memory) : 임시적으로 저장되는 변수. 컨트랙트 함수에 대한 외부 호출들이 일어나는 사이에 지워질 수 있다.
솔리디티는 보통 상태 변수(함수 외부에 선언되는 변수)는 storage로 초기화하고, 함수 내에 선언된 변수는 memory로 자동으로 초기화한다.
변수를 부적절하게 초기화하면 취약한 컨트랙트를 생성할 수 있다.
✅ 로컬 스토리지 변수를 초기화하지 않으면 컨트랙트의 다른 스토리지 변숫값이 포함될 수 있다. 이 부분이 취약점을 유발하거나 악용될 수 있다.
✅ 솔리디티 컴파일러는 초기화되지 않은 스토리지 변수에 대한 경고를 보여준다.
✅ 스마트 컨트랙트를 작성할 때 memory 혹은 storage 지정자를 명시적으로 사용하는 것이 좋다.
솔리디티에서 고정소수점, 부동소수점은 아직 완벽하게 지원하지 않는다. 솔리디티 버전 0.5.10 참고
✅ 솔리디티에서는 정수 데이터 타입을 사용해서 부동소수점 표현을 해야한다.
✅ 10행, 16행의 나누기 연산 같은 경우에 정수 데이터 타입으로 연산을 하므로 버려지는 값이 존재한다.(정확한 연산 불가)
✅ 나누기 연산을 할 때 더 큰 수로 나누는 방법을 사용한다. (분수에서 분자에 더 큰 수를 사용한다.)
✅ 연산 순서를 조정한다. (위의 예시에서는 곱셈 후 나눗셈 수행을 한다면 더 정확한 결과를 얻을 수 있다.)
✅ 숫자에 대해 임의의 정밀도를 정의할 때, 높은 정밀도로 변환하고 수학 연산을 수행하고 출력에 필요한 정밀도로 다시 변환한다. (일반적으로 가스사용에 최적인 uint256
을 사용한다.
✔️ 글로벌 변수 tx.origin
: 트랜잭션의 발신자를 나타낸다. msg.sender
와 다른 점은 전체 호출을 가로지르고 원래 호출을 보낸 계정의 주소를 나타낸다.
tx.origin
변수를 사용하여 사용자에게 권한을 부여하는 컨트랙트는 일반적으로 사용자에게 컨트랙트 인증 작업을 수행하도록 속이는 공격에 취약하다.
✅ withdrawAll
함수는 tx.origin
을 사용해 함수 사용을 승인한다. 즉 컨트랙트의 소유자만 해당 함수를 실행할 수 있다.
✅ 공격자는 공격 대상자(Phishable
컨트랙트의 소유자)에게 컨트랙트를 위장해서 AttackContract
를 호출하게 한다.
✅ 공격 대상자(피해자)가 AttackContract
컨트랙트에 트랜잭션을 보내면 다른 함수가 없기 때문에 자동으로 폴백함수가 호출된다.
✅ withdrawAll
함수의 파라미터는 attacker
로 공격자의 주소이며, 이 컨트랙트에 트랜잭션을 보낸 tx.origin
은 공격 대상자, 즉 Phishable
컨트랙트의 소유주를 의미하기 때문에 withdrawAll
함수를 실행할 수 있게 되고 컨트랙트의 잔액을 공격자의 주소로 넘겨주게 된다.
✅ 스마트 컨트랙트에서 권한을 위해 트랜잭션을 보낸 계정의 주소를 사용할 때는 tx.origin
을 사용하면 안된다.