Reentrancy Attack হল একটি স্মার্ট কন্ট্রাক্ট সুরক্ষা দুর্বলতা যা attackers কে সুযোগ দেয় তাদের নিজস্ব কন্ট্রাক্ট থেকে অন্য কন্ট্রাক্টে বারবার কল করার মাধ্যমে অপ্রত্যাশিত আচরণ তৈরি করতে। এই আক্রমণটি সাধারণত Ether transfer বা value transfer ফাংশনগুলির মধ্যে ঘটে, যেখানে একটি কন্ট্রাক্টের ফাংশন অন্য কন্ট্রাক্টের ফাংশনকে কল করার সময় পুনরায় একই ফাংশনে কল হয়ে যেতে পারে এবং ম্যানিপুলেট করা যেতে পারে।
Reentrancy Attack এর সবচেয়ে বিখ্যাত উদাহরণ হল The DAO attack যা 2016 সালে ঘটে। এই আক্রমণের মাধ্যমে আক্রমণকারীরা কন্ট্রাক্টের Ether বারবার তুলতে সক্ষম হয়েছিল।
1. Reentrancy Attack কী?
Reentrancy Attack ঘটে যখন একটি কন্ট্রাক্ট অন্য কন্ট্রাক্টের ফাংশনে Ether পাঠায় এবং এই ফাংশনটি সেই কন্ট্রাক্টের কাছে আবার একই ফাংশন কল করে। যদি সেই ফাংশনটি কোনো স্টেট পরিবর্তন করার আগে Ether পাঠায়, তবে আক্রমণকারী প্রতিবার কন্ট্রাক্টটি ট্রিগার করার সময় টাকা তুলে নিতে পারে, যা তাদেরকে অবৈধভাবে অর্থ চুরি করতে সাহায্য করে।
Reentrancy Attack এর সাধারণ কার্যপ্রণালী:
- একটি কন্ট্রাক্টে Ether ট্রান্সফার করা হয়।
- ট্রান্সফার করার পর কন্ট্রাক্টের স্টেট পরিবর্তন করা উচিত, কিন্তু যদি স্টেট পরিবর্তন করার আগে ট্রান্সফার করা হয়, তবে আক্রমণকারী তাদের কন্ট্রাক্টের মাধ্যমে পুনরায় সেই ফাংশনকে কল করে এবং আবার Ether তুলে নিতে পারে।
- আক্রমণকারী পুনরায় Ether তুলতে থাকে যতক্ষণ না তার লক্ষ্য পূর্ণ হয়।
সাধারণ ফাংশন উদাহরণ যা Reentrancy আক্রমণের শিকার হতে পারে:
pragma solidity ^0.8.0;
contract Vulnerable {
mapping(address => uint256) public balances;
// সঞ্চয়ের জন্য ফাংশন
function deposit() public payable {
balances[msg.sender] += msg.value;
}
// Ether প্রত্যাহারের জন্য ফাংশন
function withdraw(uint256 _amount) public {
require(balances[msg.sender] >= _amount, "Insufficient balance");
// Ether পাঠানো
payable(msg.sender).transfer(_amount);
// স্টেট পরিবর্তন
balances[msg.sender] -= _amount;
}
}এখানে, withdraw ফাংশনে Ether transfer প্রথমে করা হচ্ছে এবং তারপর balances[msg.sender] পরিবর্তন করা হচ্ছে। আক্রমণকারী যদি একটি কন্ট্রাক্ট তৈরি করে যা এই ফাংশনটি কল করার পর পুনরায় একই ফাংশনে ফিরে আসে, তবে তারা একাধিকবার Ether তুলে নিতে পারবে, কারণ স্টেট পরিবর্তন হওয়ার আগে তারা বারবার ট্রান্সফার করার সুযোগ পাবে।
2. Reentrancy Attack এর প্রতিরোধের পদ্ধতি
Reentrancy Attack প্রতিরোধ করার জন্য কিছু সুরক্ষা ব্যবস্থা এবং কৌশল রয়েছে। এগুলি ব্লকচেইনে অর্থ স্থানান্তর করার সময় স্মার্ট কন্ট্রাক্টের সুরক্ষা নিশ্চিত করতে সহায়ক।
1. Checks-Effects-Interactions Pattern
Checks-Effects-Interactions প্যাটার্নটি ব্যবহার করা হয় যাতে আপনি প্রথমে শর্ত যাচাই (checks), তারপর স্টেট পরিবর্তন (effects), এবং পরে তৃতীয় পক্ষের সাথে ইন্টারঅ্যাকশন (interactions) করেন। এটি নিশ্চিত করে যে Ether ট্রান্সফার বা অন্যান্য বাহ্যিক কল করার আগে সমস্ত স্টেট পরিবর্তন করা হয়েছে এবং আক্রমণকারীরা পুনরায় ফাংশনটি কল করতে পারে না।
কৌশল:
- Checks: প্রথমে সমস্ত শর্ত যাচাই করুন।
- Effects: পরে স্টেট পরিবর্তন করুন।
- Interactions: শেষের দিকে তৃতীয় পক্ষের সাথে ইন্টারঅ্যাকশন করুন (যেমন Ether ট্রান্সফার বা অন্য ফাংশন কল)।
সুরক্ষিত উদাহরণ:
pragma solidity ^0.8.0;
contract Safe {
mapping(address => uint256) public balances;
// সঞ্চয়ের জন্য ফাংশন
function deposit() public payable {
balances[msg.sender] += msg.value;
}
// Ether প্রত্যাহারের জন্য ফাংশন (এটি এখন সুরক্ষিত)
function withdraw(uint256 _amount) public {
require(balances[msg.sender] >= _amount, "Insufficient balance");
// স্টেট পরিবর্তন
balances[msg.sender] -= _amount;
// Ether পাঠানো
payable(msg.sender).transfer(_amount);
}
}এখানে, balances[msg.sender] এর মান প্রথমে পরিবর্তন করা হচ্ছে এবং পরে Ether ট্রান্সফার করা হচ্ছে। এই পদ্ধতিতে আক্রমণকারী পুনরায় ফাংশনটি কল করতে পারবে না, কারণ স্টেট পরিবর্তনের পরে আর Ether ট্রান্সফার করা হয়নি।
2. Reentrancy Guard (একটি সুরক্ষা ফ্ল্যাগ ব্যবহার)
Reentrancy Guard একটি কন্ট্রাক্টের মধ্যে একটি ফ্ল্যাগ ব্যবহার করে প্রতিরোধ করা হয় যাতে একাধিক ফাংশন কলিং একে অপরকে বিপদে ফেলতে না পারে। এই ফ্ল্যাগটি সাধারণত mutex (mutual exclusion) হিসেবে কাজ করে, যা নিশ্চিত করে যে একটি ফাংশন একবারেই একবারই কার্যকর হবে।
উদাহরণ:
pragma solidity ^0.8.0;
contract ReentrancyGuard {
bool private locked;
modifier noReentrancy() {
require(!locked, "No reentrancy allowed");
locked = true;
_;
locked = false;
}
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
// Withdraw with reentrancy guard
function withdraw(uint256 _amount) public noReentrancy {
require(balances[msg.sender] >= _amount, "Insufficient balance");
// Ether transfer
payable(msg.sender).transfer(_amount);
// State update
balances[msg.sender] -= _amount;
}
}এখানে, noReentrancy মডিফায়ারটি একটি locked ফ্ল্যাগ ব্যবহার করে নিশ্চিত করে যে শুধুমাত্র একটি লেনদেন বা ফাংশন একবারে চলতে পারে। এটি প্রতিরোধ করে যে আক্রমণকারী পুনরায় ফাংশনটি কল করতে পারে।
3. Use of Pull Over Push Pattern
আরেকটি সুরক্ষা কৌশল হল Pull over Push প্যাটার্ন ব্যবহার করা। এই প্যাটার্নে, টাকা বা Ether পাঠানোর পরিবর্তে ব্যবহারকারীকে তার নিজস্ব টোকেন বা Ether “খোঁজা” (pull) করার অনুমতি দেয়া হয়, অর্থাৎ ব্যবহাকারী নিজে ট্রান্সফার করতে পারবে।
উদাহরণ:
pragma solidity ^0.8.0;
contract PullOverPush {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
// Pull pattern for withdrawal
function withdraw() public {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");
// State change before Ether transfer
balances[msg.sender] = 0;
// User pulls Ether
payable(msg.sender).transfer(amount);
}
}এখানে, ব্যবহারকারী নিজে তার পরিমাণের Ether তুলে নেবে, এবং এর ফলে Reentrancy Attack এর সম্ভাবনা কমে যাবে, কারণ টাকা বা Ether সরাসরি পাঠানো হচ্ছে না।
সারাংশ
Reentrancy Attack হল একটি দুর্বলতা যা ব্যবহারকারীর অ্যাকাউন্ট থেকে বারবার Ether তুলে নেয়ার সুযোগ দেয়। এই আক্রমণ থেকে প্রতিরোধের জন্য Checks-Effects-Interactions Pattern, Reentrancy Guard, এবং Pull over Push প্যাটার্ন ব্যবহৃত হয়। এই কৌশলগুলো স্মার্ট কন্ট্রাক্টের নিরাপত্তা নিশ্চিত করতে সাহায্য করে এবং আক্রমণকারীদের জন্য স্মার্ট কন্ট্রাক্টকে দুর্বল করে না তোলে।
Read more