03 de agosto de 2023
TL; RD
- Ataques de reentrância continuam sendo um desafio. As medidas de defesa existentes se concentram no nível do código-fonte do protocolo e entram em vigor somente antes do tempo de execução.
- A proteção em tempo de execução é um complemento crucial para a segurança DeFi, garantindo que a execução do protocolo esteja alinhada com o design pretendido.
- O design da EVM não suporta a proteção em tempo de execução porque o contrato inteligente não pode acessar todo o contexto durante o tempo de execução.
- A Artela explora um novo padrão de EVM + Extensões, visando aprimorar a camada de execução e eliminar ataques de reentrância.
- A Artela pode obter proteção em tempo real de tempo de execução como uma caixa preta por meio de sua extensão nativa — Aspect.
- Uma demonstração passo a passo de como as Aspects podem ajudar a evitar ataques de reentrância em protocolos como o Curve.
Por que os Ataques de Reentrância Continuam Sendo Um Desafio, Apesar das Medidas de Controle de Risco Existentes
Apesar dos ataques de reentrância serem um problema bem conhecido e do surgimento de inúmeras medidas de controle de risco, continuaram a ocorrer incidentes de segurança envolvendo esses ataques nos últimos dois anos:
- Curve Finance hack (julho de 2023) - mais de 60 milhões de dólares, um bug de reentrância no Vyper, uma linguagem de programação que alimenta partes do protocolo Curve.
- Origin Protocol hack (novembro de 2022) - 7 milhões de dólares, o projeto Stablecoin Origin Dollar (OUSD) sofreu um ataque de reentrância.
- Siren Protocol hack (setembro de 2021) - 3,5 milhões de dólares, pools AMM foram explorados por meio de um ataque de reentrância.
- Hack do Cream Finance (agosto de 2021) - 18,8 milhões de dólares, vulnerabilidade de reentrância permitiu ao explorador o segundo empréstimo.
A ênfase atual na prevenção de ataques de reentrância gira em torno da proteção de contratos inteligentes no nível do código, empregando medidas como a integração do ReentrancyGuard do OpenZeppelin e a realização de auditorias de código para evitar problemas de segurança predefinidos.
Essa abordagem, conhecida como solução “caixa branca” (white-box), visa proteger meticulosamente o aplicativo no nível do código-fonte para minimizar erros ocultos. No entanto, seu principal desafio reside na incapacidade de se defender contra ameaças desconhecidas.
“Traduzir” o design do protocolo para o tempo de execução real revela ser um processo desafiador. Cada etapa apresenta problemas imprevisíveis para os desenvolvedores e o código pode não abranger todos os possíveis cenários. No caso do Curve, podem surgir discrepâncias entre o resultado final do tempo de execução e o design pretendido do protocolo devido a problemas do compilador, mesmo quando a lógica do código-fonte está correta.
Confiar apenas na segurança do protocolo no código-fonte e nos níveis de compilação é inadequado. Mesmo que o código-fonte pareça impecável, as vulnerabilidades podem surgir inesperadamente devido a problemas do compilador.
Precisamos de Proteção no Tempo de Execução
Ao contrário das medidas de controle de risco existentes, que se concentram no nível do código-fonte do protocolo e entram em vigor antes do tempo de execução, a proteção no tempo de execução implica que os desenvolvedores de protocolo escrevam regras de guarda e ações para lidar com resultados imprevistos durante o tempo de execução. Isso facilita a avaliação em tempo real dos resultados da execução no tempo de execução.
A proteção no tempo de execução é fundamental para aprimorar a segurança DeFi, servindo como um complemento vital para as medidas existentes. Ao proteger o protocolo de maneira “caixa preta” (black-box), se reforça a segurança ao garantir que os resultados finais do tempo de execução estejam alinhados com o design pretendido do protocolo, tudo sem envolver-se diretamente na execução real do código.
O desafio de Implementar a Proteção no Tempo de Execução
Infelizmente, o design da EVM não suporta a implementação de proteção no tempo de execução na cadeia (on-chain), uma vez que o contrato inteligente não pode acessar todo o contexto do tempo de execução.
Como esse desafio pode ser superado? Acreditamos que os seguintes pré-requisitos são necessários:
- Um módulo especializado que possa acessar todas as informações dos contratos inteligentes, incluindo todo o contexto da transação.
- Obter autorização de contratos inteligentes permite ao módulo reverter transações conforme necessário.
- Garantir que a funcionalidade do módulo entre em vigor após a execução do contrato inteligente e antes do compromisso de estado.
A EVM está atualmente enfrentando limitações e se esforça para acomodar mais inovações. No paradigma da blockchain modular, a camada de execução deve explorar avanços para além da EVM.
A abordagem inovadora da Artela envolve a combinação da EVM com extensões nativas para alcançar maior avanço.
Introdução à Programação Aspect
Apresentamos a Programação Aspect, um modelo de programação para a blockchain Artela que permite extensões nativas na blockchain.
A Aspect é a extensão programável usada para integrar dinamicamente a funcionalidade personalizada na blockchain no tempo de execução, trabalhando com contratos inteligentes para aprimorar a funcionalidade na cadeia (on-chain).
A característica distintiva da Aspect é a capacidade de acessar as APIs a nível de sistema da camada de base e executar ações designadas em pontos de junção (Join Points) ao longo do ciclo de vida da transação. Os contratos inteligentes podem vincular Aspects especificadas para ativar funcionalidades adicionais. Quando uma transação invoca esses contratos inteligentes, ela interage com as extensões Aspects associadas.
Como a Programação Aspect Obtém Proteção no Tempo de Execução
A Aspect pode registrar o estado de execução de cada chamada de função. Quando uma função de reentrância é chamada durante sua execução, a Aspect a detecta e reverte imediatamente a transação, impedindo que invasores explorem a vulnerabilidade de reentrância. Por meio dessa abordagem, a Aspect elimina com eficácia os ataques de reentrância, garantindo a segurança e a estabilidade dos contratos inteligentes.
Principais atributos da Aspect para implementar proteção no tempo de execução:
- Pontos de junção (Join Points) ao longo do ciclo de vida da transação: a Aspect é um módulo que pode ser adaptado para ser ativado em pontos de junção específicos — pós-execução de contrato inteligente, mas pré-compromisso de estado.
- Acesso abrangente ao contexto da transação: a Aspect pode acessar o contexto completo da transação, incluindo todas as informações da transação (métodos e parâmetros), a pilha de chamadas (todas as chamadas internas do contrato durante a execução), contexto de mudanças de estado e todos os eventos emitidos pela transação.
- Capacidade de chamada do sistema: a Aspect pode fazer chamadas do sistema e, se necessário, iniciar reversões de transações.
- Vinculação e autorização com contratos inteligentes: os contratos inteligentes podem se vincular à Aspect e conceder permissão a ela para se envolver no processamento de transações.
Implementar a Proteção de Reentrância da Aspect
Vamos explorar como a Aspect pode implementar a proteção no tempo de execução na cadeia (on-chain). 👇👇
Podemos implantar um verdadeiro Protocolo de Intenção de Guarda da Aspect em pontos de junção no “preContractCall” e no “postContractCall” para evitar ataques de reentrância.
💡💡
preContractCall: acionado antes da execução da chamada de contrato cruzado.
postContractCall: acionado após a chamada de contrato cruzado ser executada.
No contexto da guarda de reentrância, pretendemos evitar que a reentrância do contrato antes que a chamada termine. Com a Aspect, podemos conseguir isso implementando um código específico.
No ponto de junção preContractCall, continuamos rastreando as pilhas de chamadas do contrato. Se houver qualquer chamada duplicada na pilha de chamadas (o que significa que uma reentrância inesperada está acontecendo em nossas chamadas bloqueadas), a Aspect reverterá essa chamada.
/**
* preContractCall é um ponto de junção que será invocado antes que a chamada do contrato seja executada.
*
* @param ctx contexto do ponto de junção fornecido
* @return resultado da execução da Aspect
*/
preContractCall(ctx: PreContractCallCtx): AspectOutput {
// Obtém o método do contrato atualmente chamado.
let currentCallMethod = utils.praseCallMethod(ctx.currInnerTx!.data);
// Define funções que não são suscetíveis è reentrância.
// - 0xec45ef89: assinatura de add_liquidity
// - 0xe446bfca: assinatura de remove_liquidity
let lockMethods = ["0xec45ef89", "0xe446bfca"];
// Verifica se o método atual está dentro do escopo de funções que não são suscetíveis à reentrância.
if (lockMethods.includes(currentCallMethod)) {
// Recupera a pilha de chamadas do contexto, que se refere a
// todas as chamadas do contrato ao longo do caminho da invocação do método do contrato atual.
let rawCallStack = ctx.getCallStack();
// Cria uma lista encadeada para encapsular os dados brutos da pilha de chamadas.
let callStack = utils.wrapCallStack(rawCallStack);
// Verifica se já existe um método não-reentrante no caminho de chamada atual.
callStack = callStack!.parent;
while (callStack != null) {
let callStackMethod = utils.praseCallMethod(callStack.data);
if (lockMethods.includes(callStackMethod)) {
// Se sim, reverte a transação.
ctx.revert("illegal transaction: reentrancy attack");
}
callStack = callStack.parent;
}
}
return new AspectOutput(true);
}
Implantar o Contrato Curve e Protegê-lo
Para simular o ataque do contrato Curve, escrevemos um contrato simples para reproduzir o processo de maneira mais compreensível. O código do contrato é o seguinte: 👇
event AddLiquidity:
excuted: uint256
event RemoveLiquidity:
excuted: uint256
deployer: address
@external
def __init__():
.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)
Podemos ver que add_liquidity
e remove_liquidity
do contrato acima são guardados pela mesma chave de bloqueio de reentrância: lock
, o que significa que se a guarda de reentrância estiver funcionando corretamente, não podemos entrar novamente na função protegida pelo mesmo bloqueio (por exemplo, chamar add_liquidity
no remove_liquidity
).
Compile o contrato acima com o compilador vyper 0.2.15
, 0.2.16
, ou 0.3.0
(que são as versões que têm um problema conhecido na proteção de reentrância).
Em seguida, podemos implantar o contrato de vítima acima e atacá-lo com o seguinte contrato. 👇
// 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();
}
}
Semelhante ao ataque real, o método attack
deste contrato tentará reentrar add_liquidity
do método remove_liquidity
por meio de sua função fallback
. Se a reentrância realmente acontecer, você observará um evento AddLiquidity
registrado no recibo antes de um evento RemoveLiquidity
.
transaction receipt -> {
"txHash": ...,
"events": [{
"topic": "AddLiquidity",
...
}, {
"topic": "RemoveLiquidity",
...
}]
}
Agora vamos proteger o contrato da vítima com a Aspect. Ao fazer isso, você precisa concluir as seguintes operações primeiro:
- Implante a Aspect;
- Vincule o contrato da vítima com a Aspect.
Se você não estiver familiarizado com as operações da Aspect, confira nosso guia do desenvolvedor aqui para aprender primeiro.
Depois de finalizar as operações acima, vamos chamar o método attack
novamente para verificar se a operação será realizada.
A gravação mostra que a transação de reentrância foi revertida, o que significa que nossa guarda da Aspect está protegendo o contrato da vítima da reentrância.
Conclusão
O recente ataque ao Curve novamente enfatiza que não existe um protocolo completamente 100% seguro. Concentrar-se apenas na segurança do protocolo a nível do código-fonte e da compilação é insuficiente. Mesmo que o código-fonte apareça sem falhas, as vulnerabilidades ainda podem surgir inesperadamente devido a problemas com o compilador.
Para reforçar a segurança DeFi, a proteção no tempo de execução torna-se um complemento crucial. Ao proteger o protocolo de maneira “caixa-preta”, garante-se que a execução do protocolo esteja alinhada com o design pretendido, evitando com eficácia ataques de reentrância no tempo de execução.
Desenvolvemos uma simulação do ataque de reentrância no Curve e criamos um contrato simples para reproduzir o processo de forma mais compreensível. Utilizando a Programação Aspect como uma nova abordagem para habilitar a proteção no tempo de execução na blockchain, demonstramos passo a passo como proteger o contrato da vítima com a Aspect. Nosso objetivo é ajudar a eliminar ataques de reentrância para protocolos DeFi como o Curve, aprimorando a segurança geral no espaço DeFi.
Por meio da Programação Aspect, os desenvolvedores podem aproveitar um espectro de avanços dentro da rede Artela, desde proteção no tempo de execução na cadeia até várias outras inovações, como intenção, JIT e automação na cadeia. Além disso, esse framework universal, enraizado na base da Cosmos SDK, capacita os desenvolvedores a aprimorar suas próprias blockchains, equipados exclusivamente com recursos de extensão nativa.
Siga-nos no Twitter e fique por dentro das novidades da Artela. Saiba mais sobre a Artela em nosso site.
Esse artigo foi escrito pela Artela Network e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.
Top comments (0)