Segurança de Canais IBC: Como Canais Desordenados Habilitam Ataques de Replay Entre Cadeias no Cosmos
O protocolo Inter-Blockchain Communication (IBC) é uma das peças de engenharia mais sofisticadas no espaço blockchain.
Available in English
Segurança de Canais IBC: Como Canais Desordenados Habilitam Ataques de Replay Entre Cadeias no Cosmos
Legal & Ethical Disclaimer
This content is provided for EDUCATIONAL and AUTHORIZED SECURITY TESTING purposes only.
- •Use these techniques on systems you own or have explicit written permission to test
- •Practice in authorized lab environments (VulnHub, HackTheBox, DVWA, etc.)
- •Follow responsible disclosure practices when finding vulnerabilities
- •Use knowledge for defensive security and authorized penetration testing
- •Access systems without explicit authorization
- •Use these techniques for malicious purposes
- •Deploy exploits against production systems you don't own
- •Share working exploits for unpatched vulnerabilities
Legal warning
Unauthorized access to computer systems is illegal in most jurisdictions (e.g. CFAA in the US, Computer Misuse Act in the UK). Violators may face criminal prosecution and civil liability. The author and publisher assume no liability for misuse of this information. By continuing, you agree to use this knowledge ethically and legally.
Hook & Contexto
O protocolo Inter-Blockchain Communication (IBC) é uma das peças de engenharia mais sofisticadas no espaço blockchain. Ele permite que cadeias soberanas troquem dados arbitrários com garantias criptográficas — um problema genuinamente difícil. Mas a solidez criptográfica na camada de transporte não se traduz automaticamente em segurança na camada de aplicação, e nenhum lugar essa tensão é mais visível do que na distinção entre canais ordenados e desordenados.
A maioria das aplicações IBC usa canais desordenados por padrão porque são operacionalmente tolerantes — pacotes podem chegar fora de ordem, retransmissores podem tentar novamente sem coordenação rígida, e a UX se sente mais suave. Essa conveniência operacional vem com um trade-off estrutural: canais desordenados não impõem números de sequência global no nível do canal. Em certos designs de aplicação, particularmente aqueles que rastreiam estado por pacote sem incorporar singularidade suficiente nos dados do pacote em si, isso cria uma janela para replay. Um pacote finalizado — aquele cuja confirmação já foi processada — pode às vezes ser reenviado por um retransmissor malicioso ou contraparte para desencadear uma segunda execução da lógica de negócio da cadeia receptora.
Este artigo disseca esse risco a partir dos primeiros princípios. Construiremos o modelo mental da hierarquia de confiança do IBC, mostraremos exatamente onde a lacuna de aplicação de sequência vive em canais desordenados, percorreremos um cenário de replay de prova de conceito envolvendo drenagem de fundos, e então mapearemos as mitigações que desenvolvedores de aplicações IBC seguras devem estar implementando hoje.
TL;DR
| Conceito | Ponto-Chave |
|---|---|
| Canais ordenados | Impõem sequência estrita de pacotes; replay é estruturalmente impossível |
| Canais desordenados | Sem aplicação de sequência global; camada de aplicação deve se defender |
| Superfície de ataque de replay | Retransmissor malicioso ou comprometido reenvia um pacote já confirmado |
| Causa raiz | Ligação de nonce/sequência faltante nos dados do pacote da aplicação |
| Mitigação primária | Rastreamento de recebimento em nível de aplicação + poda de compromisso de pacote |
| Nível de risco | Alto quando combinado com lógica de transferência de fundos ou mutação de estado |
Fundamentos & Teoria
O Modelo de Confiança do IBC
IBC não é uma ponte no sentido tradicional — é um protocolo para comunicação verificada entre cadeias. Sua segurança é derivada da verificação de cliente leve: a cadeia A mantém um cliente leve da cadeia B, e vice-versa. Cada pacote transmitido inclui um compromisso criptográfico que a cadeia receptora pode verificar contra um cabeçalho de bloco conhecido e confiável. Nenhuma terceira parte confiável é necessária; a confiança flui do consenso de cada cadeia participante.
A abstração de canal fica em cima dessa camada de transporte. Um canal é um pipe nomeado, versionado, ordenado ou desordenado entre dois módulos ligados a porta em duas cadeias. A propriedade de ordenação é definida na criação do canal durante o handshake de quatro etapas (ChanOpenInit → ChanOpenTry → ChanOpenAck → ChanOpenConfirm) e não pode ser alterada após a criação.
O que "Ordenado" Realmente Impõe
Em um canal ordenado, o módulo central do IBC rastreia um nextSequenceSend no lado do envio e um nextSequenceRecv no lado da recepção. Os pacotes devem ser entregues em sequência ascendente estrita. Se o pacote 5 chegar antes do pacote 4, o módulo IBC o rejeitará. Isso é imposto no nível do protocolo — não no nível da aplicação — e torna o replay trivialmente impossível: um pacote com sequência N pode apenas ser recebido uma vez, porque o contador de sequência esperado do receptor avança imediatamente para N+1.
O que "Desordenado" Realmente Garante (e Não Garante)
Canais desordenados substituem o contador de sequência global por um armazenamento de recebimento por pacote. Quando um pacote é recebido com sucesso, o módulo IBC escreve um recebimento em packetReceipt/{channelId}/{sequence}. Tentativas subsequentes de entrega para o mesmo pacote são rejeitadas se o recebimento existe.
Aqui está a nuance crítica: armazenamentos de recebimento de pacote são podados após o processamento da confirmação. Uma vez que a cadeia de envio escreve o compromisso de confirmação e o retransmissor a entrega de volta, o compromisso de pacote original no lado de envio é deletado. Dependendo do caminho de implementação e se a aplicação manipula adequadamente o recebimento, uma janela pode se abrir onde:
- O recebimento foi deletado ou nunca escrito (devido a um bug ou caminho não padrão).
- O compromisso de pacote foi apagado no lado de envio.
- O estado da aplicação da cadeia receptora foi mutado uma vez já.
Uma resubmissão nessa janela passaria pela verificação de recebimento do módulo IBC e re-entraria no manipulador OnRecvPacket da aplicação.
Onde Se Encaixa no Fluxo de Trabalho
Conceitos-Chave em Profundidade
1. Ciclo de Vida de Pacote e Onde os Recebimentos Vivem
Entender o ciclo de vida exato é essencial para identificar a superfície de ataque.
O recebimento na Cadeia B é o portão. Se for escrito corretamente e nunca podado antes que a confirmação seja totalmente processada, o replay é bloqueado no nível do módulo IBC. A vulnerabilidade emerge em casos extremos:
- Módulos de aplicação que implementam lógica de recebimento customizada e esquecem de deixar o módulo IBC escrever o recebimento primeiro.
- Cadeias executando versões não padrão do IBC onde a lógica de poda é agressiva.
- Ambientes de teste usando o framework de teste Go IBC onde
RecvPacketé chamado sem passar pelo caminho completo de commit/prova, o que pode silenciosamente pular escritas de recebimento.
2. O Cenário de Ataque de Replay: Passo a Passo
Considere uma aplicação de transferência de token entre cadeias simplificada executando sobre um canal desordenado onde o desenvolvedor da aplicação cometeu um erro sutil: eles armazenam quantidades recebidas apenas com chave (sender, denom) em vez de (sender, denom, sequence).
Configuração:
- Cadeia A envia pacote com sequência 42: "transferir 1000 ATOM para endereço X na Cadeia B"
- Retransmissor entrega o pacote. Cadeia B credita 1000 ATOM ao endereço X. Recebimento para seq 42 é escrito.
- Retransmissor entrega a confirmação para Cadeia A. Cadeia A deleta seu compromisso.
Ataque:
Um retransmissor malicioso agora constrói um MsgRecvPacket contendo os dados de pacote originais para sequência 42, pareado com uma prova Merkle válida de um bloco histórico na Cadeia A onde o compromisso ainda existia. Se o recebimento da Cadeia B para sequência 42 foi deletado (devido à poda agressiva ou um bug), o módulo IBC não encontrará um recebimento existente e chamará OnRecvPacket novamente. Cadeia B credita outros 1000 ATOM ao endereço X. O atacante efetivamente dobrou seus fundos.
3. Por Que o Framework de Teste Go IBC Pode Mascarar Isso
O pacote ibctesting em cosmos/ibc-go fornece helpers de caminho rápido como path.RelayPacket() que pulam o consenso real de Tendermint e geração de prova. Em um ambiente de teste, isso significa:
// Isso NÃO simula o caminho completo de escrita de recebimento
suite.coordinator.RelayAndAckPendingPackets(path)
Desenvolvedores que apenas testam com o framework podem nunca descobrir que seu manipulador OnRecvPacket é não idempotente, porque a infraestrutura de teste silenciosamente manipula deduplicação que a rede real não garante sob todas as condições. Sempre complemente testes de framework com testes de integração usando nós gaiad reais e hermes.
4. Construindo o PoC com hermes e gaiad
O fluxo de trabalho do PoC não requer explorar um bug de protocolo — é sobre explorar lacunas de lógica de aplicação através de caminhos de protocolo legítimos.
# Inicie duas cadeias locais com ignite CLI
ignite chain serve --config chain_a.yml &
ignite chain serve --config chain_b.yml &
# Configure hermes para conectar ambas as cadeias
hermes config validate
# Crie um canal DESORDENADO entre as duas cadeias
hermes create channel \
--a-chain chain-a \
--b-chain chain-b \
--a-port transfer \
--b-port transfer \
--channel-version ics20-1 \
--order UNORDERED
# Envie um pacote e capture o hash de tx + altura de bloco
gaiad tx ibc-transfer transfer \
channel-0 cosmos1<recipient> 1000uatom \
--from attacker --chain-id chain-a
# Registre a altura de bloco H onde o compromisso existe
# Tente retransmissão com hermes (caminho normal)
hermes start
# Após confirmação, construa manualmente MsgRecvPacket
# com prova ancorada no bloco H (pré-confirmação)
# Envie via gaiad tx broadcast
O passo-chave é capturar o CommitmentProof na altura de bloco H antes do compromisso ser deletado. Esta é uma query de prova Merkle padrão:
gaiad query ibc channel packet-commitment transfer channel-0 42 \
--height <H> \
--prove true \
--chain-id chain-a
Se a camada de aplicação da cadeia receptora não verifica independentemente que este pacote já foi processado, a resubmissão tem sucesso.
5. Defesas de Nível de Aplicação
O mecanismo de recebimento do módulo IBC é a primeira linha de defesa, mas desenvolvedores de aplicação devem adicionar uma segunda linha:
a) Incorpore números de sequência nos dados do pacote. Cada pacote processado por OnRecvPacket deve incluir a sequência do canal na carga útil de nível de aplicação. O manipulador deve verificar se isso corresponde e registrá-lo em um mapa de sequência processada.
b) Implemente rastreamento de recebimento de nível de aplicação. Mantenha uma loja com chave (portId, channelId, sequence) → bool. Escreva nela no início de OnRecvPacket, antes de qualquer mutação de estado. Reverta se a chave já existir.
func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData) error {
// Guarda de idempotência de nível de aplicação
if k.HasPacketProcessed(ctx, packet.DestinationPort, packet.DestinationChannel, packet.Sequence) {
return sdkerrors.Wrap(types.ErrDuplicatePacket, "packet already processed")
}
k.SetPacketProcessed(ctx, packet.DestinationPort, packet.DestinationChannel, packet.Sequence)
// ... resto da lógica de negócio
}
c) Use canais ordenados quando a ordem de entrega importa para semântica de aplicação. Se sua aplicação não pode tolerar replays e requer sequenciamento rigoroso, o custo de canais ordenados (bloqueio em pacotes faltantes) vale a garantia estrutural.
Alternativas & Comparação
| Abordagem | Proteção de Replay | Throughput | Complexidade | Melhor Para |
|---|---|---|---|---|
| Canais ordenados | ✅ Imposto por protocolo | Mais Baixo (bloqueio de head-of-line) | Baixa (protocolo manipula) | Transferências de alto valor, protocolos com estado |
| Desordenado + recebimentos de app | ✅ Imposto por aplicação | Alto | Médio | Mensageria geral, transferências fungíveis |
| Desordenado + nonce em carga útil | ⚠️ Parcial (requer verificação correta) | Alto | Médio-Alto | Protocolos customizados com dados de pacote único |
| Desordenado, sem defesa | ❌ Sem proteção | Alto | Baixa (inseguro) | ⚠️ Nunca em produção |
| Interceptação de middleware | ✅ Defesa em profundidade | Depende do middleware | Alto | Stacks requerendo garantias entre aspectos |
Canais ordenados são a solução estruturalmente mais simples, mas impõem restrições de liveness — se um pacote é perdido, nenhum pacote subsequente pode ser entregue até que seja resolvido. Para aplicações de alto throughput como livros de ordem de DEX ou feeds de oráculo, isso é inaceitável.
Canais desordenados com recebimentos de nível de aplicação são o padrão de produção para transferências de token fungível do ICS-20. A implementação de referência em ibc-go faz isso corretamente; o risco emerge em módulos de aplicação customizados que implementam seu próprio OnRecvPacket sem auditar idempotência.
Middleware IBC (introduzido em ibc-go v3+) permite que lógica de proteção de replay seja fatorada em uma camada reutilizável que envolve módulos de aplicação, o que é arquiteturalmente elegante para projetistas de protocolo construindo em cima de IBC.
Conclusões & Leitura Adicional
Leitura Adicional & Referências
- Especificação IBC — Semântica de Canal & Pacote (ICS-004)
- Fonte ibc-go — lógica de recebimento de pacote
- ICS-20 Transferência de Token Fungível — implementação de referência OnRecvPacket
- Documentação Hermes Relayer — configuração de ordenação de canal
- Informal Systems — Relatórios de Auditoria de Segurança IBC
- Design de Middleware IBC Cosmos SDK
- Trail of Bits — Revisão de Segurança de Ponte Entre Cadeias (metodologia geral)
- Paradigm Research — Desafios em Segurança de Mensageria Entre Cadeias