Uncle Bogdan
8 min readJan 13, 2024

Усуньте атаки типу “повторний вхід” за допомогою вбудованого захисту під час виконання

TL; DR

Атаки на повторний вхід залишаються серйозною проблемою. Існуючі заходи захисту зосереджені на рівні вихідного коду протоколу і вступають в дію тільки до початку виконання.

Захист під час виконання є важливим доповненням до безпеки DeFi, гарантуючи, що виконання протоколу відповідає його задуманому дизайну.

Дизайн EVM не підтримує захист під час виконання, оскільки смарт-контракт не може отримати доступ до всього контексту під час виконання.

Artela досліджує новий патерн EVM + Розширення, спрямований на покращення рівня виконання та усунення атак на повторний вхід.

Артела може досягти захисту в режимі реального часу за принципом “чорного ящика” за допомогою власного розширення — Aspect.

Покрокова демонстрація того, як Aspect може допомогти запобігти атакам на вхід в систему для таких протоколів, як Curve.

Чому повторні атаки залишаються проблемою, незважаючи на існуючі заходи контролю ризиків

Незважаючи на те, що атаки повторного входу є загальновідомою проблемою і поява численних заходів контролю ризиків, інциденти безпеки, пов’язані з такими атаками, продовжували траплятися протягом останніх двох років:

Злом Curve Finance (липень 2023 року) — понад 60 мільйонів доларів, баг реентерації у Vyper, мові програмування, на якій працюють частини протоколу Curve.

Злом протоколу Origin (листопад 2022 року) — $7 млн, стейблкоїн-проект Origin Dollar (OUSD) зазнав атаки на реентерацію.

Злом протоколу Siren (вересень 2021 року) — $3,5 млн, пули AMM були використані через атаку на реентерацію.

Злом Cream Finance (серпень 2021) — $18,8 млн, реентераційна уразливість дозволила зловмиснику отримати другу позику.

В даний час основна увага приділяється захисту смарт-контрактів на рівні коду, використовуючи такі заходи, як інтеграція ReentrancyGuard від OpenZeppelin і проведення аудиту коду, щоб уникнути заздалегідь визначених проблем з безпекою.

Цей підхід, відомий як рішення “білої скриньки”, спрямований на ретельний захист програми на рівні вихідного коду, щоб мінімізувати приховані помилки. Однак його основна проблема полягає в нездатності захиститися від невідомих загроз.

“Переклад” дизайну протоколу в реальний час виконання виявляється складним процесом. На кожному кроці розробники стикаються з непередбачуваними проблемами, а код може не охоплювати всі потенційні сценарії. У випадку з Curve можуть виникнути розбіжності між кінцевим результатом виконання та запланованим дизайном протоколу через проблеми компілятора, навіть якщо вихідний логічний код є правильним.

Покладатися лише на безпеку протоколу на рівні вихідного коду та компіляції недостатньо. Навіть якщо вихідний код здається бездоганним, вразливості можуть виникнути несподівано через проблеми компілятора.

Нам потрібен захист під час виконання

На відміну від існуючих заходів контролю ризиків, які зосереджені на рівні вихідного коду протоколу і починають діяти до виконання, захист під час виконання передбачає написання розробниками протоколів правил захисту та дій для обробки непередбачуваних результатів під час виконання. Це полегшує оцінку результатів виконання в режимі реального часу.

Захист під час виконання має вирішальне значення для підвищення безпеки DeFi, слугуючи життєво важливим доповненням до існуючих заходів. Захищаючи протокол за принципом “чорного ящика”, він підвищує безпеку, гарантуючи, що кінцеві результати виконання відповідають запланованому дизайну протоколу, і все це без безпосередньої участі у фактичному виконанні коду.

Складність реалізації захисту під час виконання

На жаль, дизайн EVM не підтримує реалізацію захисту під час виконання ланцюжка, оскільки смарт-контракт не може отримати доступ до всього контексту виконання.

Як можна подолати цей виклик? Ми вважаємо, що для цього необхідні наступні передумови:

  1. Спеціалізований модуль, який може отримати доступ до всієї інформації про смарт-контракти, включаючи весь контекст транзакції.
  2. Отримання дозволу від смарт-контрактів дозволяє модулю відкликати транзакції за потреби.
  3. Забезпечення функціональності модуля вступає в силу після підписання смарт-контракту і до взяття державою зобов’язань.

Наразі EVM стикається з обмеженнями і намагається пристосуватися до подальших інновацій. У парадигмі модульного блокчейну рівень виконання повинен досліджувати прориви за межами EVM.

Інноваційний підхід Artela передбачає поєднання EVM з нативними розширеннями для досягнення більшого прогресу.

Вступ до аспектного програмування

Ми представляємо Aspect Programming — модель програмування для блокчейну Artela, яка дозволяє створювати власні розширення на блокчейні.

Aspect — це програмне розширення, яке використовується для динамічної інтеграції користувацького функціоналу в блокчейн під час виконання, працюючи зі смарт-контрактами для покращення функціональності в ланцюжку.

Відмінною рисою Aspect є можливість доступу до системних API базового рівня і виконання визначених дій в точках приєднання протягом усього життєвого циклу транзакції. Смарт-контракти можуть прив’язувати певні Аспекти для активації додаткової функціональності. Коли транзакція викликає ці смарт-контракти, вона взаємодіє з відповідними Аспектами.

Як Аспектне програмування забезпечує захист під час виконання

Aspect може записувати стан виконання кожного виклику функції. Коли під час виконання викликається функція, що повертається в систему, Aspect виявляє це і негайно повертає транзакцію, не даючи зловмисникам скористатися вразливістю повторного входу в систему. Завдяки такому підходу Aspect ефективно усуває атаки на повторний вхід, забезпечуючи безпеку і стабільність смарт-контрактів.

Ключові атрибути Aspect для реалізації захисту під час виконання:

  1. Точки приєднання протягом усього життєвого циклу транзакції: Aspect — це модуль, який можна налаштувати для активації в певних точках приєднання — після виконання смарт-контракту, але до взяття зобов’язань перед державою.
  2. Повний доступ до контексту транзакції: Aspect може отримати доступ до повного контексту транзакції, включаючи всю інформацію про транзакцію (методи та параметри), стек викликів (всі внутрішні виклики контракту під час виконання), контекст змін стану та всі події, що генеруються транзакцією.
  3. Можливість системних викликів: Aspect може здійснювати системні виклики і, за необхідності, ініціювати відміни транзакцій.
  4. Прив’язка та авторизація за допомогою смарт-контрактів: Смарт-контракти можуть прив’язуватись до Aspect та надавати дозвіл на участь Aspect в обробці транзакцій.

Впровадити захист від повторного входу Аспект

Давайте розглянемо, як Aspect може реалізувати захист під час виконання на ланцюжку. 👇👇

Ми можемо розгорнути фактичний аспект захисту намірів протоколу в точках приєднання “до виклику контракту” і “після виклику контракту”, щоб запобігти атакам на повторний вхід.

💡💡

preContractCall: Спрацьовує перед виконанням крос-контрактного дзвінка.

postContractCall: Спрацьовує після виконання крос-контрактного виклику.

У контексті захисту від повторного входу ми прагнемо запобігти повторному входу в контракт до завершення виклику. За допомогою Aspect ми можемо досягти цього, реалізувавши спеціальний код.

У точці приєднання preContractCall ми відстежуємо стек викликів контракту. Якщо в стеку викликів є дубльований виклик (що означає, що в наших заблокованих викликах відбувається неочікуваний повторний вхід), Аспект поверне цей виклик.

  /**
* preContractCall is a join-point which will be invoked before the contract call is executed.
*
* @param ctx context of the given join-point
* @return result of Aspect execution
*/
preContractCall(ctx: PreContractCallCtx): AspectOutput {
// Get the method of currently called contract.
let currentCallMethod = utils.praseCallMethod(ctx.currInnerTx!.data);

// Define functions that are not susceptible to reentrancy.
// - 0xec45ef89: sig of add_liquidity
// - 0xe446bfca: sig of remove_liquidity
let lockMethods = ["0xec45ef89", "0xe446bfca"];

// Verify if the current method is within the scope of functions that are not susceptible to reentrancy.
if (lockMethods.includes(currentCallMethod)) {
// Retrieve the call stack from the context, which refers to
// all contract calls along the path of the current contract method invocation.
let rawCallStack = ctx.getCallStack();

// Create a linked list to encapsulate the raw data of the call stack.
let callStack = utils.wrapCallStack(rawCallStack);

// Check if there already exists a non-reentrant method on the current call path.
callStack = callStack!.parent;
while (callStack != null) {
let callStackMethod = utils.praseCallMethod(callStack.data);
if (lockMethods.includes(callStackMethod)) {
// If yes, revert the transaction.
ctx.revert("illegal transaction: reentrancy attack");
}
callStack = callStack.parent;
}
}
return new AspectOutput(true);
}

Розгортайте контракт Curve та захищайте його

Щоб змоделювати атаку Curve, ми написали простий контракт, який відтворює процес у більш зрозумілий спосіб. Код контракту виглядає наступним чином 👇.

event AddLiquidity:
excuted: uint256

event RemoveLiquidity:
excuted: uint256

deployer: address

@external
def __init__():
self.deployer = msg.sender

@external
@view
def isOwner(user: address) -> bool:
return user == self.deployer

@external
@nonreentrant('lock')
def add_liquidity():
log AddLiquidity(1)

@external
@nonreentrant('lock')
def remove_liquidity():
raw_call(msg.sender, b"")
log RemoveLiquidity(1)

Ми бачимо, що add_liquidity і remove_liquidity вищезгаданого контракту захищені одним і тим же реентерабельним замком key: lock, а це означає, що якщо реентерабельний захист працює належним чином, ми не можемо повторно увійти у функцію, захищену тим самим замком (наприклад, викликати add_liquidity в remove_liquidity).

Скомпілюйте наведений вище контракт за допомогою компілятора vyper 0.2.15, 0.2.16 або 0.3.0 (це версії, які мають відому проблему з захистом від повторного входу).

Тоді ми можемо розгорнути вищезгаданий контракт з жертвою і атакувати його наступним контрактом. 👇

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;

interface CurveContract {
event AddLiquidity(uint256 executed);
event RemoveLiquidity(uint256 executed);

function add_liquidity() external;
function remove_liquidity() external;
}

contract Attack {
CurveContract public curve;

constructor(address _curveContract) {
curve = CurveContract(_curveContract);
}

function attack() external payable {
curve.remove_liquidity();
}

fallback() external {
curve.add_liquidity();
}
}

Подібно до реальної атаки, метод атаки цього контракту намагатиметься повторно ввійти в метод add_liquidity з методу remove_liquidity через свою резервну функцію. Якщо повторний вхід дійсно відбудеться, ви побачите подію AddLiquidity, зареєстровану в чеку перед подією RemoveLiquidity.

transaction receipt -> {
"txHash": ...,
"events": [{
"topic": "AddLiquidity",
...
}, {
"topic": "RemoveLiquidity",
...
}]
}

Тепер давайте захистимо контракт жертви з Аспектом. Для цього вам потрібно спочатку виконати наступні операції:

  1. Розгорнути аспект
  2. Зв’яжіть договір з Аспектом

Якщо ви не знайомі з операціями Aspect, спершу ознайомтеся з нашим посібником для розробників тут.

Після завершення вищевказаних операцій, давайте знову викличемо метод атаки, щоб перевірити, чи пройде операція.

Запис показує, що транзакцію повторного входу було скасовано, а це означає, що наш Аспект захисту захищає контракт жертви від повторного входу.

Висновок

Нещодавня атака на Curve ще раз підкреслює, що не існує повністю 100% безпечного протоколу. Зосередження уваги виключно на безпеці протоколу на рівні вихідного коду та компіляції є недостатнім. Навіть якщо вихідний код виглядає бездоганно, вразливості все одно можуть виникнути несподівано через проблеми з компілятором.

Для посилення безпеки DeFi захист під час виконання стає важливим доповненням. Захищаючи протокол за принципом “чорного ящика”, він гарантує, що виконання протоколу відповідає його запланованому дизайну, ефективно запобігаючи атакам повторного входу під час виконання.

Ми розробили симуляцію атаки Curve reentrancy і створили простий контракт, щоб відтворити процес у більш зрозумілий спосіб. Використовуючи аспектне програмування як новий підхід для захисту під час виконання в блокчейні, ми крок за кроком демонструємо, як захистити контракт жертви за допомогою Aspect. Ми прагнемо допомогти усунути атаки на повторний вхід для DeFi-протоколів, таких як Curve, підвищуючи загальну безпеку в DeFi-просторі.

Завдяки Аспектному програмуванню розробники можуть використовувати цілий спектр досягнень мережі Artela, починаючи від захисту під час виконання ланцюжка і закінчуючи різноманітними інноваціями, такими як наміри, JIT та автоматизація ланцюжка. Крім того, цей універсальний фреймворк, що лежить в основі Cosmos SDK, дає можливість розробникам вдосконалювати власні блокчейни, унікально оснащені власними можливостями розширення.

Підписуйтесь на нас у Твіттері та будьте в курсі подій “Артела”. Дізнайтеся більше про Артелу на нашому сайті.