WEB3DEV

Cover image for Auditoria de Segurança de Contrato Inteligente: Sudoswap v2
Paulo Gio
Paulo Gio

Posted on

Auditoria de Segurança de Contrato Inteligente: Sudoswap v2

Descobrindo um Bug (Quase) Fora do Escopo na Rede Principal

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*4osvA9WbkA45a7f9_-QqAQ.png

A Cyfrin conduziu recentemente uma revisão abrangente de segurança do Sudoswap v2 ao longo de três semanas. Foram encontrados 20 problemas, incluindo três de alto risco e quatro de médio risco. Este artigo explora as descobertas da auditoria e as técnicas usadas para descobri-las.

O Sudoswap é um protocolo de mercado de NFTs que permite uma ampla gama de tipos de tokens. O recente lançamento do sudoAMM v2 introduz as seguintes novas funcionalidades na plataforma:

  • Suporte a royalties na cadeia: A versão atualizada do Sudoswap aplica automaticamente royalties na cadeia para coleções compatíveis com o ERC2981. O recurso estende o suporte para diferentes interfaces de royalties, como Rarible, Foundation, SuperRare, Zora, ArtBlocks e KnownOrigin v2.
  • Verificação de propriedades para pools: Um novo recurso que permite aos pools definir parâmetros de contrato para a verificação na cadeia de propriedades desejadas. Isso possibilita a compra de itens específicos em uma coleção, independentemente do método de verificação utilizado.
  • Estrutura de compartilhamento de receita: Um recurso opcional para LPs e proprietários de projetos que facilita o compartilhamento de receita. Sua intenção é incentivar mais negociações ao fornecer um incentivo de royalties configurado pelo proprietário da coleção.
  • Suporte ao ERC1155: Isso permite a criação de pools para pares ERC1155<>ETH ou ERC1155<>ERC20.
  • Melhoria das operações: Contabilidade de taxas separada, roteador unificado, aprimoramento do rastreamento de eventos e pequenas otimizações de gás.

Para obter informações mais detalhadas sobre esses recursos, consulte o post de introdução e a série Sudo Sundays do cofundador do Sudoswap, Owen.

Descobrindo um Bug (Quase) Fora do Escopo na Rede Principal

O escopo de uma auditoria geralmente é definido como um subconjunto de contratos em um determinado repositório ou ecossistema. Por exemplo, os contratos dentro do escopo podem ser definidos como um conjunto de novos contratos periféricos projetados para interagir com os contratos principais existentes. Esse tipo de auditoria geralmente é desafiador para os auditores sem o contexto mais amplo e, portanto, tempo deve ser gasto entendendo os contratos fora do escopo para entender quais possíveis vetores de ataque podem estar presentes para os contratos dentro do escopo em produção.

Neste caso, o Sudoswap v2 introduziu um novo contrato, VeryFastRouter, que permite lidar com todos os tipos de troca (swap) (por exemplo, ERC721<>ETH, ERC721<>ERC20, ERC1155<>ETH, ERC1155<>ERC20) e um método eficiente para lidar com preenchimentos parciais ao comprar/vender vários itens do mesmo pool. Uma versão do roteador LSSVMRouter anterior, que permite dividir trocas em vários LSSVMPairs para comprar e vender vários NFTs em uma chamada, embora parcialmente dentro do escopo, já foi implantada na rede principal (mainnet) como parte do Sudoswap v1.

Ao revisar o roteador antigo como parte de nossos esforços de coleta de contexto, detectamos um bug na forma como a proteção contra variação era tratada no LSSVMRouter::swapNFTsForSpecificNFTsThroughETH, especificamente que o minOutput fornecido pelo usuário poderia permanecer bloqueado no roteador em vez de ser reembolsado. Confirmou-se que esse bug estava presente na versão do LSSVMRouter implantado na rede principal, que diferia apenas ligeiramente em sua implementação, também incluindo outra função problemática LSSVMRouter::swapNFTsForAnyNFTsThroughETH com o mesmo bug.

A seguir, um trecho mostra o código que foi auditado no nosso dado hash de commit:

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;

contract LSSVMRouter {
    function swapNFTsForSpecificNFTsThroughETH(
        NFTsForSpecificNFTsTrade calldata trade,
        uint256 minOutput,
        address payable ethRecipient,
        address nftRecipient,
        uint256 deadline
    ) external payable checkDeadline(deadline) returns (uint256 outputAmount) {

        // Troca NFTs por ETH
        // minOutput da troca definido para 0 já que estamos fazendo uma verificação de derrapagem agregada
        outputAmount = _swapNFTsForToken(trade.nftToTokenTrades, 0, payable(address(this)));

        // Adiciona valor extra para comprar NFTs
        outputAmount += msg.value;

        // Troca ETH por NFTs específicos
        // custo <= inputValue = outputAmount - minOutput, então outputAmount' = (outputAmount - minOutput - cost) + minOutput >= minOutput
        outputAmount = _swapETHForSpecificNFTs(
            trade.tokenToNFTTrades, outputAmount - minOutput, ethRecipient, nftRecipient
        ) + minOutput;
    }
}
Enter fullscreen mode Exit fullscreen mode
Implementação do LSSVMRouter::swapNFTsForSpecificNFTsThroughETH

Aqui, as transações de NFT para ETH são realizadas antes de tudo para gerar um valor intermediário de saída. Como especificado pelo comentário, minOutput pretende facilitar uma verificação de derrapagem agregada, portanto, outputAmount é incrementado pelo msg.value enviado para as transações de ETH para NFT, completando a troca de NFTs por NFTs específicos utilizando ETH. Usuários que especificam um valor minOutput não zero terão esse valor deduzido do outputAmount que é enviado para a segunda metade da troca, de ETH para NFTs, como o parâmetro inputAmount da função interna LSSVMRouter::_swapETHForSpecificNFTs.

Perceba que a função externa acima retorna o valor total de saída simplesmente adicionando o valor de minOutput ao que é retornado pela função interna. Dado que não há transferência de fundos aqui, agora assume-se a responsabilidade dessa função interna de emitir um reembolso de qualquer ETH não gasto com base nesse parâmetro inputAmount. Vamos nos aprofundar e ver o que acontece durante a segunda metade da troca:

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;

contract LSSVMRouter {
    function _swapETHForSpecificNFTs(
        PairSwapSpecific[] calldata swapList,
        uint256 inputAmount,
        address payable ethRecipient,
        address nftRecipient
    ) internal virtual returns (uint256 remainingValue) {
        remainingValue = inputAmount;

        uint256 pairCost;
        CurveErrorCodes.Error error;

        // Realiza as trocas
        uint256 numSwaps = swapList.length;
        for (uint256 i; i < numSwaps;) {
            // Calcula o custo por troca primeiro para enviar a quantidade exata de ETH, economizando gás ao evitar a necessidade de enviar excesso de ETH de volta
            (error,,, pairCost,,) = swapList[i].pair.getBuyNFTQuote(swapList[i].nftIds[0], swapList[i].nftIds.length);

            // Exige que não ocorram erros
            require(error == CurveErrorCodes.Error.OK, "Erro na curva de bonding");

            // O total de ETH retirado do remetente não pode exceder o inputAmount
            // caso contrário, a dedução de remainingValue falhará
            remainingValue -= swapList[i].pair.swapTokenForSpecificNFTs{value: pairCost}(
                swapList[i].nftIds, remainingValue, nftRecipient, true, msg.sender
            );

            unchecked {
                ++i;
            }
        }

        // Retorna o valor restante ao remetente
        if (remainingValue > 0) {
            ethRecipient.safeTransferETH(remainingValue);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Implementação do LSSVMRouter::_swapETHForSpecificNFTs

Ignorando as variáveis e declarações armazenadas em cache, a maior parte desta função inclui um loop for que executa a troca e um bloco condicional referente à possibilidade de existir algum valor excedente a ser retornado ao remetente. Portanto, como esperado, observamos a lógica de reembolso dentro desta função interna.

No entanto, lembre-se de que inputValue não inclui minOutput, pois esse valor foi deduzido na chamada de função anterior. O valor excedente real do parâmetro de variação minOutput, portanto, não está incluído no cálculo do remainingValue e, portanto, não será incluído na subsequente transferência de ETH.

Se não houver underflows intermediários (devido a um valor suficientemente grande de minOutput), então LSSVMRouter::swapNFTsForSpecificNFTsThroughETH reportará um outputAmount que não reflete verdadeiramente o saldo final do usuário em ETH, pois qualquer excesso de ETH, conforme especificado por minOutput, permanecerá bloqueado no roteador para sempre!

Divulgação, Gravidade e Remediação

A divulgação dessa vulnerabilidade foi feita à equipe do Sudorandom Labs através de um canal de comunicação já estabelecido antes do início da auditoria. Recebemos sua resposta inicial dentro de um minuto, concluindo as discussões após 45 minutos. Um git diff demonstrando o problema através de um teste de fork do Foundry enviado à equipe do Sudorandom Labs pode ser encontrado na descrição completa da descoberta.

Felizmente, após investigações internas iniciais e discussão com a equipe do Sudorandom Labs, foi determinado que essas funções nunca foram realmente chamadas na implantação da rede principal, pois não foram conectadas à interface do Sudoswap. Embora o Sudoswap não use essas funções no cliente, os integradores no nível do contrato podem ter seus fundos potencialmente perdidos. Por isso, a equipe do Sudorandom Labs tentou entrar em contato com aqueles potencialmente afetados.

Para resumir, essa vulnerabilidade resulta no bloqueio de fundos do usuário com alto impacto e probabilidade. Se as funções problemáticas fossem integradas a uma interface de usuário, isso seria avaliado como CRÍTICO, mas dado que as integrações atuais reduziram significativamente a probabilidade, avaliamos a gravidade como ALTA. Considerando que nenhuma interface de usuário foi integrada para interagir com essa função específica, e esta versão do roteador estava prevista para ser descontinuada com o lançamento do Sudoswap v2, a equipe do Sudorandom Labs reconheceu o problema.

Relatório Completo

Nossas descobertas foram documentadas em um relatório abrangente, com as respectivas soluções propostas pela equipe do Sudoswap.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*9Vcvnf7HlyDSIozJsUbraQ.png

Considerações Finais

Ao concluir esta revisão, reiteramos o papel fundamental que as revisões de segurança por tempo desempenham na segurança de contratos inteligentes. Apesar do Sudoswap v2 ter sido previamente auditado em uma revisão de segurança abrangente, que identificou várias vulnerabilidades de segurança significativas, nossa equipe na Cyfrin levantou muitos outros achados. Isso destaca o benefício dos projetos passarem por múltiplas auditorias, pois cada uma traz perspectivas e metodologias únicas. A equipe do Sudoswap prontamente abordou todas as questões, demonstrando seu compromisso com a segurança e estabelecendo um padrão exemplar para a indústria.

A descoberta de um bug no código da rede principal de produção reforça ainda mais o valor de considerar o contexto mais amplo ao auditar contratos inteligentes. Mesmo que essa descoberta estivesse tecnicamente além do escopo de nossa auditoria original, dado que LSSVMRouter seria descontinuado em favor do VeryFastRouter, sua descoberta e resolução ajudaram a prevenir a possibilidade de perda inesperada de fundos do usuário, além de ser nossa primeira divulgação de bug na rede principal como empresa.

É importante apreciar a eficácia do modelo de segurança "Swiss cheese" para obter o máximo de engajamentos de segurança. As revisões de segurança são complexas e não há uma solução mágica.

https://twitter.com/Montyly/status/1680872672635551744

Por essa razão, nós da Cyfrin frequentemente recomendamos que os clientes realizem uma auditoria competitiva após as revisões de segurança privadas. Estamos incrivelmente animados para anunciar o lançamento de nosso novo mercado de auditorias de contratos inteligentes v0.1, CodeHawks. Auditorias competitivas são suportadas no lançamento, mas estamos trabalhando para fazer deste o único local para protocolos e auditores.

Crie seu perfil no CodeHawks agora!

Artigo original publicado por Giovanni Di Siena. Traduzido por Paulinho Giovannini.

Top comments (0)