Ласкаво просимо до посібника для початківців з аудиту розумних контрактів! Один із найкращих способів розпочати роботу з аудитом смарт-контрактів — це спробувати розглянути кілька поширених типів уразливостей смарт-контрактів.
Було б корисно, якщо ви вже маєте базове розуміння мови програмування Ethereum Solidity. Ми розглянемо деякі коди, написані програмістами Noob solidity.
Повторний вхід
Реальний світовий сценарій:
Уявіть, що у вас 50 цукерок. У вас є неслухняна маленька сестричка, якій ви дозволили забрати у вас лише 2 шоколадки в будь-який момент. Ви також не хочете давати їй більше 10 шоколадок на день, побоюючись, що вона отримає карієс. Щоб переконатися в цьому, щовечора ви підраховуєте, скільки шоколадок залишилося у вас. Як ви думаєте, це спрацює? Або вас зламала б ваша молодша сестра?
На жаль, не вийде! Ваша сестра з’ясовує, що ви не знаєте, скільки у вас шоколаду, поки не настане вечір. Тож вже наступного дня ваша молодша сестричка відвідує вас 6 разів до вечора і кожен раз бере 2 шоколадки! Це те, що ми називаємо атакою повторного входу.
Тут ви оновлюєте кількість шоколадок, які ви маєте ввечері, замість того, щоб оновлювати підрахунок щоразу, коли ваша сестра бере у вас 2 цукерки. Це також відбувається зі смарт-контрактом. Смарт-контракт передбачає певний баланс, тоді як зловмисник фактично зайнятий вилученням певної кількості криптовалют із контракту кілька разів.
Приклад реального коду:
Цей код належить до смарт-контракту під назвою Безбанківський. Будь-хто може вилучити ефір з контракту Unbanked, якщо залишок msg.sender (тобто абонента withdraw
function ) більше або дорівнює сумі, яку потрібно зняти.
function withdraw(uint _amount) { require(balances[msg.sender] >= _amount); msg.sender.call.value(_amount)(); balances[msg.sender] -= _amount;
}
Зверніть увагу, що є ключове слово call, яке використовується для відправки необхідної кількості ефіру на msg.sender
. Зловмисник може скористатися цим, створивши контракт під назвою Thief, в якому він викликає функцію вилучення в fallback()
функція. А fallback()
функція в Solidity — це спеціальна функція, яка виконується, коли ефір надсилається в смарт-контракт.
Це означає, що зловмисник здатний рекурсивно викликати функцію вилучення. Таким чином, до оновлення смарт-контракту залишки msg.sender
в останньому рядку коду зловмисник вже кілька разів вилучив ефір. Цього можна уникнути, якщо баланс оновити перед використанням ключового слова call, таким чином дотримуючись a перевірки-ефекти-взаємодії рисунок.
Вплив:
Команда перша в історії атака повторного входу трапилося в 2016 році на DAO (децентралізована автономна організація), що призвело до злому приблизно в 50 мільйонів доларів. Щоб скасувати цей хак, спільнота Ethereum розділила блокчейн Ethereum, що породило ETC (Ethereum Classic) і ETH (Ethereum).
Арифметичні переповнення та недопоповнення
Реальний світовий сценарій:
Давайте пограємо в роздуми. Він складається з крутящегося колеса, і переможець визначається на основі найбільшої кількості, яку він зможе зібрати під час обертання колеса. Колесо позначене по всьому від 256 до -256.
Правила гри полягають у тому, що покажчик для всіх гравців стоїть на 0 на початку кожного оберту. А гравцеві дозволяється обертатися тільки в бік від’ємних чисел. Як ви виграєте цю гру?
Гарною стратегією, щоб виграти цю гру кожен раз, було б крутити колесо з такою силою, щоб колесо оберталося до -256, а потім поверталося до 256 за один раз. Це можливо, тому що 256 приходить відразу після -256 на колесі. Це те, що ми називаємо арифметичним недостатнім потоком. А арифметичне переповнення якраз навпаки.
Приклад реального коду:
An недолив або перелив відбувається, коли арифметична операція досягає свого мінімуму або максимуму.
function withdraw(uint _amount) public { require(balances[msg.sender] - _amount > 0); address payable to = payable(msg.sender); to.transfer(_amount); balances[msg.sender] -= _amount;
}
Команда _amount
Параметр функції вилучення є цілим числом без знака. Значення відображення залишків (яке подібне до словника в Python або пари ключ-значення в C++ або Java) також є цілим числом без знака.
mapping(address => uint256) public balances
Необхідна виписка перевіряє, чи зберігаються залишки msg.sender
позитивно чи ні. Але це твердження завжди буде вірним, навіть якщо сума перевищує залишки msg.sender
. Це тому, що обидва balances
та _amount
змінні мають тип беззнакового цілого, і їх арифметичний результат (після заповнення) також буде цілим без знака!
І, як ви пам’ятаєте, ціле число без знака завжди додатне. Це означає, що зловмисник може вилучити необмежену кількість ефіру зі смарт-контракту! Ви можете знайти докладний приклад і код реалізації цієї вразливості тут.
Ще одна важлива річ, яку слід зазначити, це те, що арифметична операція між, скажімо, двома цілими числами без знака також є цілим числом без знака. Це може бути небезпечно, якщо це не враховувати в смарт-контрактах, оскільки це може призвести до небажаних порушень безпеки!
function votes(uint postId, uint upvote, uint downvotes) { if (upvote - downvote < 0) { deletePost(postId) }
}
Як ви могли помітити у наведеному вище прикладі, оператор if є досить безглуздим як upvote - downvote
завжди буде позитивним. І публікація буде видалена, навіть якщо downvotes
більше upvotes
. Щоб уникнути таких атак, рекомендується використовувати версію компілятора Solidity, більшу ніж 0.8.0.
Вплив:
Назвали монету Монета PoWH був запущений у 2017 році. Хоча це була гра Ponzi, сама вона була зламана через помилку арифметичного переповнення, що призвело до втрати приблизно 866 ETH або 950,000 XNUMX доларів США на той час. Ви можете прочитати про це детально тут.
Необхідно прочитати: Уроки атаки на Tinyman, найбільший DEX на Algorand
Атака відмови в обслуговуванні
Реальний світовий сценарій:
Уявіть, що ви перебуваєте в технічному університеті Bitcoin. Начебто все добре, крім того, що є загальний обідній стіл для всіх. І, на жаль, мало людей з іншого класу, яким завжди вдається зайняти обідній стіл раніше за когось із вашого класу.
У практичному сценарії вони відмовляють кожному в необхідних послугах, що призводить до втрати дорогоцінного часу. Це те, що ми називаємо «атакою відмови в обслуговуванні».
Приклад реального коду:
У грі назвали Король Ефіру, будь-хто може стати королем. Але правило, щоб стати королем, полягає в тому, що людина повинна внести більше ефіру, ніж нинішній король. Це можна зробити, зателефонувавши claimThrone()
функція контракту King of Ether, за якою особа посилає ефір безпосередньо попередньому королю і стає новим королем.
function claimThrone() external payable { require(msg.value > balance, "Need to pay more to become the king"); (bool sent, ) = king.call{value: balance}(""); require(sent, "Failed to send Ether"); balance = msg.value; king = msg.sender; }
Як ви могли здогадатися, цей код уразливий до DoS-атаки, але як? Для цього вам потрібно розуміти, що в Ethereum є два типи адрес: перша адреса зовнішньої власний обліковий запис або просто адреса гаманця, а другий – це адреса договору. Тепер ефір можна надсилати з будь-якого з цих типів адрес.
Якщо цей ефір буде надіслано за адресою контракту, тоді контракт стане королем. Але припустимо, що цей новий контракт не має a fallback()
функція, яка необхідна, якщо контракт хоче прийняти ефір. Потім, якщо прийде нова людина і спробує зателефонувати claimThrone()
функція, вона завжди вийде з ладу!
Зауважте, що це також відбувається частково тому, що claimThrone()
функція явно перевіряє, чи була передача ефіру успішною чи ні в другому необхідному операторі. Ви можете знайти повний код і здійснити на нього DoS-атаку тут.
Код також може бути вразливим до DoS-атаки, якщо код містить цикл над масивом великих розмірів. Це відбувається тому, що межа газу в таких випадках може бути перевищено. Ви можете прочитати про це тут.
Вплив:
Гра під назвою Урядовий, яка, очевидно, була схемою Понці, застрягла з 1100 ефіром, тому що для обробки виплати була потрібна велика кількість газу.
Небезпечна випадковість
Реальний світовий сценарій:
Колись був чоловік на ім’я Хескі, якого завжди супроводжувала мавпа Пескі. Хескі проводив лотереї і отримував непогані прибутки. Одного разу Аліса помітила, що Хескі пильно дивився на свою мавпу Пескі. Потім вона побачила, як він щось написав на аркуші паперу і запечатав це в конверт. Зацікавившись, вона вирішила продовжити розслідування.
Пізніше того ж вечора Аліса побачила, що переможець лотереї визначається публічно відкриваючи запечатаний конверт. Поспостерігаючи за ним кілька днів, Аліса зрозуміла, що Хескі визначив виграшний номер у лотереї, дивлячись на жести Пескі (наприклад, якщо мавпа почухала голову, Хескі записав 10)! Тепер Аліса мала формулу, щоб виграти кожну лотерею, і їй просто потрібно було купити лотерейний квиток з правильним номером!
Хескі припускав, що його «випадковий» спосіб визначення переможця в лотереї ніколи не може бути з’ясований, але він справді був невірним.
Приклад реального коду:
У цьому прикладі випадкове число генерується на основі хешу комбінації номера блоку та його часової позначки. цей хеш потім призначається змінній відповіді. Тепер кожен, хто вгадає це (здавалося б) випадкове число, отримує 1 ефір. Ви думаєте, що це неможливо зламати?
function guess(uint _guess) public { uint answer = uint( keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) ); if (_guess == answer) { (bool sent, ) = msg.sender.call{value: 1 ether}(""); require(sent, "Failed to send Ether"); } }
Ні! Зловмисник все ще може вгадати це випадкове число, просто скопіювавши код, щоб створити значення, присвоєне змінній відповіді, і передати ту саму змінну відповіді до guess()
функція!
guessTheRandomNumber.guess(answer);
Ви можете знайти повний код тут. Щоб уникнути цієї атаки, рекомендується використовувати перевірювані випадкові функції, наприклад Chainlink VRF.
Вплив:
Близько 400 ETH було втрачено через атаку на Розумні мільярди лотереї контракт. Дивно, але навіть сама контрактна лотерея була схемою Понці (ой!).
Маніпулювання часом
Реальний світовий сценарій:
Сатоші любить їсти печиво. Він любить усі види печива, які готує його мама. Але його мати дуже сувора і вважає, що їсти занадто багато печива не корисно для нього. Тому його мама закріпила правило, що печиво він отримає лише о 8:XNUMX.
Того самого дня о 7:45 Сатоші біжить до матері і просить печиво. Його мати запитує: «Котра година?»
“Всього 8 годин!” – відповідає він.
"Гаразд. Тоді візьми печиво з моєї шафи».
Таким чином, Сатоші зміг успішно маніпулювати часом на 15 хвилин, щоб отримати своє печиво! Який голодний хлопчина!
Приклад реального коду:
Поміткою часу блоку можна маніпулювати приблизно 15 секунд: шахтарем. Таким чином, майнер може встановити сприятливий час і включити свою транзакцію в той самий блок, який він майнить. Функція play()
належить до ігрового контракту під назвою G-Dot.
function play() public { require(now > 1640392200 && neverPlayed == true); neverPlayed = false; msg.sender.transfer(1500 ether);
}
Цей контракт винагороджує 1500 ефірів гравцеві, який першим викличе функцію відтворення. Але, як ви бачите, функція відтворення може бути викликана лише в тому випадку, якщо зараз або block.timestamp транзакції, яка містить виклик до play()
функція, більша за час епохи 1640392200.
Майнер може легко маніпулювати цією міткою часу та включити свою транзакцію виклику play()
функціонувати в одному блоці так, що він сам є першим гравцем. Таким чином, майнер виграє гру!
Вплив:
block.timestamp використовувався для генерування випадкових чисел у Урядові і, таким чином, був уразливим до атак маніпулювання часом.
Зверніться до QuillAudits
QuillAudits — це безпечна платформа аудиту смарт-контрактів, розроблена QuillHash
Технології.
Це аудиторська платформа, яка ретельно аналізує та перевіряє смарт-контракти для перевірки вразливостей безпеки шляхом ефективного ручного перегляду за допомогою інструментів статичного та динамічного аналізу, газоаналізаторів, а також симуляторів. Крім того, процес аудиту також включає розширене модульне тестування, а також структурний аналіз.
Ми проводимо як аудит смарт-контрактів, так і тести на проникнення, щоб знайти потенціал
вразливості безпеки, які можуть зашкодити цілісності платформи.
Якщо вам потрібна допомога в аудиті смарт-контрактів, не соромтеся звертатися до наших експертів тут!
Щоб бути в курсі нашої роботи, приєднуйтесь до нашої спільноти:-
Twitter | LinkedIn | Facebook | Telegram
Повідомлення Посібник для початківців з аудиту розумних контрактів: частина 1 вперше з'явився на Блог Quillhash.
Джерело: https://blog.quillhash.com/2022/01/19/beginners-guide-to-smart-contract-auditing-part-1/
- "
- &
- 000
- 2016
- 7
- МЕНЮ
- рахунки
- адреса
- ВСІ
- вже
- хоча
- аналіз
- аудит
- автономний
- початок
- КРАЩЕ
- Біткойн
- blockchain
- Помилка
- купити
- call
- випадків
- Перевірки
- classic
- код
- Монета
- поєднання
- загальний
- співтовариство
- містить
- контракт
- контрактів
- печиво
- може
- створення
- крипто
- Поточний
- DAO
- день
- Децентралізований
- Відмова в обслуговуванні
- деталь
- Dex
- вниз
- легко
- є
- ETH
- Ефір
- Ефіріума
- Блокчейн Ethereum
- Ethereum Класичний
- приклад
- Експлуатувати
- кінець
- Перший
- Безкоштовна
- функція
- гра
- Games
- ГАЗ
- породжувати
- GitHub
- буде
- добре
- керівництво
- зламати
- хакі
- мішанина
- голова
- тут
- Як
- HTTPS
- дослідити
- IT
- Java
- приєднатися
- стрибати
- King
- мова
- великий
- Лінія
- Довго
- шукати
- лотерея
- людина
- мільйона
- мати
- номера
- організація
- Папір
- Викрійки
- Платити
- Люди
- частина
- платформа
- Play
- гравець
- Понці
- Схема Понци
- влада
- процес
- Програмісти
- Програмування
- громадськість
- зворотний
- огляд
- Нагороди
- Правила
- Satoshi
- безпеку
- комплект
- розумний
- розумний контракт
- Спритні контракти
- So
- солідність
- що в сім'ї щось
- Спін
- розкол
- почалася
- Заява
- Стратегія
- успішний
- Успішно
- технології
- Тести
- через
- час
- інструменти
- угода
- небанківський
- університет
- Updates
- значення
- Уразливості
- вразливість
- Вразливий
- Wallet
- Що
- Колесо
- ВООЗ
- виграти
- Work
- лист