Rust 스마트 컨트랙트 개발일지 (6)
BlockSec
2022-03-29 10:35
本文约2976字,阅读全文需要约12分钟
DoS(서비스 거부) 공격으로도 알려진 서비스 거부 공격은 이러한 유형의 공격으로 사용자가 스마트 계약을 일정 기간(심지어 영구적으로) 정상적으로 사용할 수 없게 만듭니다.

관련 기사:

DoS(서비스 거부) 공격으로도 알려진 서비스 거부 공격은 이러한 유형의 공격으로 사용자가 스마트 계약을 일정 기간(심지어 영구적으로) 정상적으로 사용할 수 없게 만듭니다.

현재 알려진 원인은 대략 다음 두 가지 범주로 나눌 수 있습니다.

  • 계약 논리의 특정 결함. 퍼블릭 함수와 같이 그 구현은 계산 복잡성을 고려하지 않습니다. 사용자가 이 함수를 호출하면 실제 소비되는 가스는 NEAR 퍼블릭 체인 제네시스 블록 구성 파일(genesis_config.json)에 정의된 가스를 초과합니다."max_total_prepaid_gas": 300000000000000`(300TGas), 트랜잭션이 실패합니다.

  • 교차 계약 호출의 일부 경우 계약 실행은 다른 외부 계약의 실행 상태에 따라 달라집니다. 그러나 외부 계약의 실행이 항상 신뢰할 수 있는 것은 아니므로 이 계약의 실행이 외부 계약에 의해 차단되어 정상적으로 작동하지 않을 수 있습니다. 이러한 유형의 문제 발생은 컨트랙트 내 컨트랙트 사용자의 자금이 잠겨 있어 정상적으로 충전하거나 인출할 수 없는 상태로 나타날 수 있습니다.

  • 계약 논리의 결함 외에도 DoS 현상의 원인은 인적 요인에 기인할 수 있습니다. 전형적인 예: 계약 소유자가 개인 키를 잃어 계약에서 일부 only_owner 실행 권한이 있는 기능을 사용할 수 없습니다. 계약의 일부 중요한 시스템 상태 값을 적시에 업데이트할 수 없어 프로젝트에 더 큰 손실을 초래할 수 있습니다.


보조 제목

1. 외부 호출에 의해 변경될 수 있는 데이터 구조에 대한 루프

다음은 계약에 등록된 사용자에게 "배당"을 위한 간단한 스마트 계약이며 해당 상태 데이터는 다음과 같습니다.

사용자는 pub fn register_account() 함수를 호출하여 등록 및 초기화할 수 있습니다.

그 후 계약 관리자는 pub fn distribution_token 기능을 호출하여 시스템의 사용자를 위한 배포를 수행합니다."배당금". "배당" 방법은 self.registered 사용자 배열을 순회하고 교차 계약 호출을 통해 각 사용자에게 지정된 양의 토큰을 보상으로 전송하는 것입니다.

그러나 계약 상태 데이터(self.registered)의 크기는 제한이 없으며 악의적인 사용자가 조작할 수 있으므로 계약 데이터의 크기가 너무 커집니다. 따라서 DISTRIBUTOR 사용자가 이 계약 방식을 호출할 때 소비될 수 있는 가스 요금이 너무 높아 GAS LIMIT를 초과합니다.

다음은 실제 NEAR Localnet에서 컨트랙트를 테스트한 결과이다.

시스템에 등록된 사용자가 많을 때 prepaid_gas 세트는 실제 distribution_token 실행 과정에서 모든 사용자의 전송 작업을 만족시키기에 충분하지 않아 이 트랜잭션이 실패하는 것을 볼 수 있습니다.

권장 솔루션:

Gas Limit의 한계로 인해 컨트랙트 메소드 실행 중에 큰 데이터 구조를 통과하는 것은 권장되지 않습니다(데이터 구조의 크기는 외부 사용자가 조작할 수 있음). 실제로 트래버스해야 하는 경우 데이터 구조의 크기를 제한하고 데이터 구조의 크기가 최대값에 도달할 때 가스 제한 제한에 도달하지 않도록 해야 합니다.

보조 제목

2. 교차 계약 간의 상태 의존성은 계약 차단으로 이어집니다.

컨트랙트가 교차 컨트랙트 호출을 할 때 외부 컨트랙트의 상태에 따라 달라질 수 있습니다.부적절한 의존성으로 인해 컨트랙트가 차단되어 DoS 공격이 발생할 수 있습니다.

스마트 계약이 "입찰"에 사용되는 시나리오를 고려해 보겠습니다.

사용자는 "입찰 계약"에서 pub fn register_account 함수 메소드를 호출하여 계정을 등록하여 후속 입찰 참여를 준비할 수 있습니다.

사용자는 또한 다음과 같은 인터페이스 기능을 통해 현재 시스템에서 현재까지 최고 입찰가로 입찰한 사용자 ID와 자신이 입찰한 가격을 조회할 수 있습니다.

또한 사용자는 다음과 같은 인터페이스 기능을 통해 현재 시스템에서 현재까지 최고 입찰가로 입찰한 사용자 ID와 자신이 입찰한 가격을 조회할 수 있습니다.

입찰 계약이 토큰을 받으면 ft_on_transfer 함수를 통해 다음 입찰 함수를 호출합니다.

이 입찰 함수에서 함수의 실행 로직은 먼저 현재 사용자의 입찰가가 이전 최고 입찰자의 입찰가보다 높은지 여부를 확인합니다. 조건이 충족되면 "입찰 계약"에서 이전 최고 입찰자의 입찰 토큰을 반환하기 위해 self.refund_exe()가 실행됩니다. 그런 다음 지금까지 가장 높은 입찰가로 사용자 ID와 입찰 가격을 업데이트합니다.

실제 상황은 계약의 논리적 정의에 따라 지금까지 가장 높은 입찰가를 가진 사용자의 ID를 교체하기 위해 가장 높은 입찰가를 가진 사용자의 입찰 토큰을 반환해야 합니다.

이 입찰 함수에서 함수의 실행 로직은 먼저 현재 사용자의 입찰가가 이전 최고 입찰자의 입찰가보다 높은지 여부를 확인합니다. 조건이 충족되면 "입찰 계약"에서 이전 최고 입찰자의 입찰 토큰을 반환하기 위해 self.refund_exe()가 실행됩니다. 그런 다음 지금까지 가장 높은 입찰가로 사용자 ID와 입찰 가격을 업데이트합니다.

실제 상황은 계약의 논리적 정의에 따라 지금까지 가장 높은 입찰가를 가진 사용자의 ID를 교체하기 위해 가장 높은 입찰가를 가진 사용자의 입찰 토큰을 반환해야 합니다.

이때 테스트는 "입찰 시스템"의 참여 사용자인 user0, user1 및 user2를 시뮬레이션합니다.

그들은 각각 10,000개의 초기 토큰을 가지고 있습니다. user0은 "입찰 시스템"에서 1000으로 처음 입찰합니다. 이때 쿼리는 current_leader: user0.test.near higher_bid: 1000으로 표시됩니다. 그런 다음 user0은 나머지 9000개의 토큰을 user2에게 즉시 전송하고 토큰 계정을 파기했습니다.

그런 다음 user1이 2000에 입찰하면 시스템은 user0의 이전 입찰 값을 반환할 계획입니다. 그러나 현재 user0의 계정이 더 이상 존재하지 않기 때문에 시스템에 메시지가 표시됩니다."Cannot Refund", 후속 트랜잭션 업데이트 상태를 성공적으로 완료할 수 없습니다.

이 시점에서 두 번째 입찰자는 2000을 입찰하려고 합니다.

  • 해결책:

보조 제목

3. 소유자 개인 키 분실

분산형 스마트 계약 프로젝트에는 부분 중앙 집중화가 종종 존재합니다. 예를 들어 계약 소유자가 있습니다. 일부 계약 기능의 실행은 소유자에 의해서만 실행 가능하도록 설정되며, 이는 계약에서 일부 주요 시스템 변수의 값을 설정하고 변경하는 데 사용됩니다. 이러한 함수를 only_owner 유형 함수라고 부를 수 있습니다.

예를 들어 위의 "dividend" 계약에 정의된 pub fn distribution_token의 경우 이 함수는 only_owner 함수입니다. 계약 소유자가 기능을 수행할 수 없는 경우(개인 키 분실) 자금은 항상 계약에 잠기며 다른 사용자에게 배포할 수 없습니다. 대부분의 경우 only_owner 기능을 사용하여 계약의 모든 트랜잭션을 일시 중지하거나 다시 시작할 수도 있으며 이는 소유자가 해당 기능을 정상적으로 수행하는 것이 중요함을 보여줍니다.

  • 해결책:

위에서 언급한 소유자의 개인 "장애"를 방지하기 위해 여러 계약 소유자를 추가하여 계약을 공동으로 관리할 수 있으며 다중 서명 요청 방법을 사용하여 원래 계약 권한 제어 체계를 대체할 수 있습니다. 계약 거버넌스 효과의 분산화를 실현합니다. 스마트 계약에서 다중 서명 요청 기능의 설계 및 구현은 후속 "스마트 계약 개발 일지"에서 자세히 설명합니다.

BlockSec
作者文库