░░░ ░░░ ▀▄
▄ ▀▀▄ ░░░ ■ ▒ ░░░
▄ ▒ ▄▄ ▒ ▄▀ ▒ ▄ ▀▄▄
▄▄ ▄ ▄█ █ ▄▀ ▄█▀▀▀ ▀▀ ▄
▀▄▀█▄▄▄▄▄▄██▀ ▀▄▀███▄▄▄ ▄▄█▄▄▄ █▄▄▄ ▄▀▄▄▀ ▄▄▄▄▄ ▀▄▀ ▒ ▄▄▄▄▀
▀▀▄▄ █▄▀▀▄ ▄▀ ▄▄ ▀▄ ▄▀▄▀▀▀▀▄ ▀▄▀▀▄██▀█ █▀▌ ▐▄ ▄█▀▀▀▀▄▄
█▀ ▄▄ ▒ ░░ █ █▀ ▄▄▀ ■▀ ▀▄ ▒ ▀▄▀▄▀ █ ▀▄ ██▄ ▓ ▄█ ▄▀▀ ██▀▄
▄ ▒ ██ ▄ ▓▓ ▒ █▄ ██ █ ██ █▄ ▀ ▀▒ ██ ▐▌██ █ ▒ ▄█ ▓ ██ ▄
▀ ▒ ▀█ ▄ ██ █ ░░ ▒ ▓▓ █▄ ██ ▓▓ █ ░█ ▄ ▄ ██ ▒ ▀▄ ▀
▀▄ ▓ ▓█ ▀▀█▀▄▄▄▄▀ ▒▒ ░░▀ ▒▒ ░ ▒▒ ░░ ▐▌ ██ █ ▒ ▀▀ ▄▄▄▄▄ ▄ ▄▀
░█▀ █ █▓ ▒ ▓ ▀▀▄ ▄▄▄▄▄▓▓▀▄█ ░▓ ▒ ▓░▐ █▓ ▓ ▌▓ ░▒░ ▄▀██ ▄▄▄▀▀ ▀█░
▄╬▄ ▓ ▓▒ █ █▓ ▀▒▄ ▀░▀▀ █▓ ▓█ █ █▓ ▓█ ▐▌ ▓▓ ▓▓█ ▄▀█▓ ▄█ ▒ ▄╬▄
, ▌█▌ , ▓ ░▀ ░ ▓█ ▀ ▀░░▄▄ ░▓ █▓ █ ▒█▐ ▌▓ █ ▌▒ █▓█ ░█ ▒ █▓ █ , ▐█▐ ,
▄ ▄ ═▌█▌═ ▄ ▄ ░░▀▀▀▀ ██ ░ █▒ ▀▄ ▀▓░░░▄▄ █░ ▓░ ▓ ▀█▐ █░▐▌ ▌█ ░░░ ▀░ ▓ ░█ ▀ ▄ ▄ ═▐█▐═ ▄ ▄
▀▀█▓▄▓█▓█▀▀ ▀▄ ▀░░░░ ▒ ▀▄▓▀▒ █ █▓▄▀▀▀░▄▀░░█ █ ░░█▀▀▓█ █ ░▓█ ▀▄▓█ ▓ ░ ▄▄ ▀▀█▓█▓▄▓█▀▀
▀▓▄% ▀ ▀▄ ▀░░▀ ▄ ▄▀ ▄▀ █▀ ▀█ ▓ ░▀▄▄ ▀ ▄▄▀ ▒▀ █▄ ▀█ █ ▓▄▀ ▀ %▄▓▀
▒ •▀▒ ▀▄ ▄▄▀ ▒ ▄▀ ▀▄ ▓ ▄▀ ▀ ▄▄ ▀ ▄▀ ■▄ ░ ▀ ▒▀• ▒
▄▀▀▀▀▄▄▄█▄▄▒ ■ ░ ▒ ▀█ ■ ▄▀ ▀▄░ ▒▄▄█▄▄▄▀▀▀▀▄
░ ░ •▀ ▀ ▄▄▀ ▒ ▀ ▀• ░ ░
█ ▒▄▀ [ Arte por L.Ayres ] ▀▄▒ █
█ ▒ ▒ █
█ █ █ █
█ ▒ Como drenar empresas Web3 ▒ █
█▒▒ Caue ▒▒█
█ ▒ ▒ █
█ █ Já se perguntou o porque dos bug bounties de Web3/blockchain serem tão altos? █ █
█ ▒ Bom, isso é porque, diferente de uma empresa do mercado tradicional, toda a sua ▒ █
█▒▒ segurança está baseada em recursos tecnológicos e não há bloqueios centralizados ▒▒█
█ ▒ que bloqueiem hacks. ▒ █
█ █ █ █
█ ▒ Por exemplo, se uma conta de banco de uma empresa for invadida, é claro que o ▒ █
█▒▒ hacker conseguiria fazer um bom estrago, mas por questões de segurança do banco, ▒▒█
█ ▒ ele não conseguiria roubar todo o dinheiro do caixa da empresa. No caso da ▒ █
█ █ blockchain, se o hacker consegue acesso à private key de uma conta é game over, █ █
█▒▒ ¡ todo o esforço que a empresa colocou para crescer vai pro /dev/null. Além disso, ▒▒█
█ ▒,█▄ sabemos que a bolha Web3 movimenta muito dinheiro e é atrativa para hackers por ▒ █
█ █▀▀° conta do certo nível de anonimidade que é possível de obter. █ █
█▒▒ ▒▒█
█ ▒ Mas como que é possível acessar os fundos de uma empresa se hoje em dia existe ▒ █
█ █ tecnologia o suficiente para proteger chaves privadas de forma praticamente █ █
█ ▒ inviolável (Exemplo: Trezor, Ledger, …)? É tudo phishing? ▒ █
█▒▒ ▒▒█
█ ▒ Negativo, falhas de lógica no código da operação da empresa são a principal ▒ █
█ █ forma que os protocolos/dApps são hackeados. Apesar de existirem wallets para █ █
█ ▒ armazenar chaves privadas de forma segura, esses produtos tiram uma das ▒ █
█▒▒ vantagens da blockchain, que é a transferência de dinheiro de forma programática ▒▒█
█ ▒ sem intermediação. Isso implica que, para que a operação de algumas empresas ▒ █
█ █ funcione corretamente, eles precisam armazenar essas chaves em um local █ █
█ ▒ acessível pelo código da aplicação web para que seja possível interagir com a ▒ █
█▒▒ blockchain. Outra opção é não utilizar aplicações web e colocar toda a lógica do ¡ ▒▒█
█ ▒ produto em um smart contract, que é o que a maioria faz. ▄█,▒ █
█ █ °▀▀█ █
█▒▒ Portanto, existem 2 formas que conseguimos interagir (de forma indireta) com os ▒▒█
█ ▒ fundos da empresa na blockchain: ▒ █
█ █ █ █
█ ▒ 1. Aplicações Web e Chaves de Custódia: Aplicações que realizam operações em ▒ █
█▒▒ nome do usuário ou da própria empresa (necessitando acesso programático a uma ▒▒█
█ ▒ private key custodiada). ▒ █
█ █ 2. Smart Contracts: Colocando toda a lógica do produto diretamente na █ █
█ ▒ blockchain, permitindo transferências de valor e interações programáticas sem ▒ █
█▒▒ intermediários. ▒▒█
█ ▒ ▒ █
█ █ Como a maioria dos dApps (Descentralized Apps) e protocolos usam smart contracts █ █
█ ▒ como sua lógica e custódia de tokens principal, é lá onde se deve encontrar uma ▒ █
█▒▒ ¡ falha de lógica para ser possível drenar os fundos da conta. Porém, normalmente, ▒▒█
█ ▒,█▄ os smart contracts são auditados várias vezes e por várias empresas diferentes, ▒ █
█ █▀▀° portanto é bem difícil encontrar bugs críticos lá. Entretanto, existem outros █ █
█▒▒ ataques que podem ser feitos em aplicações web específicas que também podem ▒▒█
█ ▒ levar à LoF (Loss of Funds). Nesse artigo vou dar alguns exemplos de falhas de ▒ █
█ █ lógica que já encontrei em aplicações reais de blockchain que poderiam levar à █ █
█ ▒ drains. ▒ █
█▒▒ ▒▒█
█ ▒ Situação 1 - Off-chain transaction sponsorship ▒ █
█ █ █ █
█ ▒ Para entender essa vulnerabilidade, primeiro é preciso entender o básico de como ▒ █
█▒▒ uma transação (tx) na blockchain funciona. Embora cada chain tenha suas ▒▒█
█ ▒ especificidades, a maioria delas possui uma taxa de transação chamada de “gas ▒ █
█ █ fee”, que normalmente deve ser paga com a moeda nativa da blockchain (ex: na █ █
█ ▒ rede Solana, se deve pagar o gas fee com SOL). Algumas blockchains também ▒ █
█▒▒ possuem uma feature de “sponsorship”, que permite que uma “sponsor account” ¡ ▒▒█
█ ▒ pague o gas fee por uma transação que não foi iniciada por ele. Para que isso ▄█,▒ █
█ █ aconteça, o sponsor deve ser especificado na transação e também deve ser um °▀▀█ █
█▒▒ signer dessa transação (assinar a tx com a sua chave privada). ▒▒█
█ ▒ ▒ █
█ █ Em solana, o sponsor é sempre o primeiro signer da transação (`signatures[0] == █ █
█ ▒ sponsor`) [1]: ▒ █
█▒▒ ▒▒█
█ ▒ pub struct Transaction { ▒ █
█ █ #[wasm_bindgen(skip)] █ █
█ ▒ #[serde(with = "short_vec")] ▒ █
█▒▒ pub signatures: Vec<Signature>, ▒▒█
█ ▒ #[wasm_bindgen(skip)] ▒ █
█ █ pub message: Message, █ █
█ ▒ } ▒ █
█▒▒ ¡ ▒▒█
█ ▒,█▄ ▒ █
█ █▀▀° Não é tão incomum que algumas aplicações utilizem da feature de sponsorship para █ █
█▒▒ diminuir a barreira de entrada e captar mais usuários (por exemplo: a transação ▒▒█
█ ▒ para criação de novas contas é sponsored pela aplicação - custo 0 para o ▒ █
█ █ usuário). Normalmente essa feature está disponível por uma API HTTP, onde o █ █
█ ▒ usuário envia uma transação, a API valida se é uma transação válida e segura e, ▒ █
█▒▒ caso seja, a transação é assinada pela chave privada da sponsor account e é ▒▒█
█ ▒ devolvida ao usuário. ▒ █
█ █ █ █
█ ▒ // isso é um pseudocódigo ▒ █
█▒▒ app.post('/sponsor', (req, res) => { ▒▒█
█ ▒ isValid = await validateTransaction(req.tx); ▒ █
█ █ █ █
█ ▒ if (isValid){ ▒ █
█▒▒ signedTx = keypair.signTransaction(req.tx) ¡ ▒▒█
█ ▒ res.json(signedTx) // a transação é serializada com todas as informações ▄█,▒ █
█ █ (incluindo as assinaturas) °▀▀█ █
█▒▒ } ▒▒█
█ ▒ else { ▒ █
█ █ res.status(400).end() █ █
█ ▒ } ▒ █
█▒▒ }); ▒▒█
█ ▒ ▒ █
█ █ █ █
█ ▒ Sabendo como funciona, já é possível pensar nos ataques possíveis. ▒ █
█▒▒ ▒▒█
█ ▒ Ataque A - Gastando todos os tokens da sponsor account - DoS ▒ █
█ █ █ █
█ ▒ Apesar dos gas fees geralmente serem valores baixos (depende da blockchain, mas ▒ █
█▒▒ ¡ na maioria é questão de centavos), caso a API não coloque um rate limit ▒▒█
█ ▒,█▄ plausível, é possível que usuários maliciosos consigam efetuar várias transações ▒ █
█ █▀▀° gastando os tokens da sponsor account para pagar o gas fee, o que pode levar à █ █
█▒▒ falência da sponsor account e, causar um DoS na aplicação já que ela depende dos ▒▒█
█ ▒ tokens da sponsor account para operações básicas. ▒ █
█ █ █ █
█ ▒ Normalmente, esse ataque não é considerado tão crítico porque o atacante não ▒ █
█▒▒ consegue roubar o dinheiro para ele, portanto não existe tanta motivação para ▒▒█
█ ▒ efetuar esse ataque. ▒ █
█ █ █ █
█ ▒ Ataque B - Bypassando a validação da transação ▒ █
█▒▒ ▒▒█
█ ▒ A vulnerabilidade mais crítica que pode ocorrer nessa feature é quando a ▒ █
█ █ transação não é validada corretamente. Vou explicar 2 casos que já encontrei em █ █
█ ▒ real life, uma em ambiente Solana e outro no Stellar. ▒ █
█▒▒ ¡ ▒▒█
█ ▒ Caso 1 - Solana sponsorship hack ▄█,▒ █
█ █ °▀▀█ █
█▒▒ O caso do ambiente solana é um pouco mais complicado de explicar sem dar muito ▒▒█
█ ▒ detalhes sobre a aplicação. Basicamente, ela utilizava de smart contracts para ▒ █
█ █ criar sessões (logins) de usuários dentro da blockchain, onde se você fizesse █ █
█ ▒ login com sua conta principal em um domínio, esse domínio só teria acesso à sua ▒ █
█▒▒ sessão, que era mais limitado do que a conta principal (restrição de token ▒▒█
█ ▒ allowance). ▒ █
█ █ █ █
█ ▒ 2. Inicia sessão para dApp1 +------------------------+ ▒ █
█▒▒ +------------------------------+ | ▒▒█
█ ▒ | | Smart Contract da | ▒ █
█ █ | +---------------------------> Sessão | █ █
█ ▒ | | +------------------------+ ▒ █
█▒▒ ¡ | | ▒▒█
█ ▒,█▄ | | ▒ █
█ █▀▀° | | +------------------------+ █ █
█▒▒ +-----v--+----+ 1.Obter sponsorship | | ▒▒█
█ ▒ | <----------------------+ | ▒ █
█ █ | Usuário +----------------------> dApp 1 sponsorship API | █ █
█ ▒ | | | | ▒ █
█▒▒ +--------+----+ | | ▒▒█
█ ▒ | +------------------------+ ▒ █
█ █ | █ █
█ ▒ | ▒ █
█▒▒ | ▒▒█
█ ▒ | ▒ █
█ █ | +-------------------+ █ █
█ ▒ | 3. Manda a sessão para o app| | ▒ █
█▒▒ +-----------------------------> dApp 1 (dapp1.com)| ¡ ▒▒█
█ ▒ | | ▄█,▒ █
█ █ +-------------------+ °▀▀█ █
█▒▒ ▒▒█
█ ▒ ▒ █
█ █ A vuln encontrada aqui foi na validação da instrução, que permitia que a sponsor █ █
█ ▒ account do dApp1 pagasse pelos gas fees de uma início de sessão para o dApp2. ▒ █
█▒▒ Apesar de não ser uma vulnerabilidade tão crítica, ainda sim é possível utilizar ▒▒█
█ ▒ os fundos de outro projeto para financiar o seu malware ou site de phishing. ▒ █
█ █ █ █
█ ▒ 2. Inicia sessão para dApp2 +------------------------+ ▒ █
█▒▒ +------------------------------+ | ▒▒█
█ ▒ | | Smart Contract da | ▒ █
█ █ | +---------------------------> Sessão | █ █
█ ▒ | | +------------------------+ ▒ █
█▒▒ ¡ | | ▒▒█
█ ▒,█▄ | | ▒ █
█ █▀▀° | | +------------------------+ █ █
█▒▒ +-----v--+----+ 1.Obter sponsorship | | ▒▒█
█ ▒ | <----------------------+ | ▒ █
█ █ | Usuário +----------------------> dApp 1 sponsorship API | █ █
█ ▒ | | | | ▒ █
█▒▒ +--------+----+ | | ▒▒█
█ ▒ | +------------------------+ ▒ █
█ █ | █ █
█ ▒ | ▒ █
█▒▒ | ▒▒█
█ ▒ | ▒ █
█ █ | +-------------------+ █ █
█ ▒ | 3. Manda a sessão para o app| | ▒ █
█▒▒ +-----------------------------> dApp 2 (dapp2.com)| ¡ ▒▒█
█ ▒ | | ▄█,▒ █
█ █ +-------------------+ °▀▀█ █
█▒▒ ▒▒█
█ ▒ ▒ █
█ █ Isso era possível porque a validação da transação não validava se a sessão sendo █ █
█ ▒ iniciada era para o mesmo dApp da sponsor account. ▒ █
█▒▒ ▒▒█
█ ▒ Caso 2 - Stellar sponsorship hack ▒ █
█ █ █ █
█ ▒ O caso que eu encontrei em uma wallet da Stellar foi mais preocupante. A vuln ▒ █
█▒▒ permitia drenar todos os fundos presentes na sponsor account da wallet, que era ▒▒█
█ ▒ em torno de $150,000 na época do beta test. No caso da chain Stellar, as ▒ █
█ █ transações e instruções funcionam de forma um pouco diferente. Cada transação █ █
█ ▒ tem um array de “operations”, que funcionam como instruções, por exemplo: ▒ █
█▒▒ ¡ ▒▒█
█ ▒,█▄ Transaction ▒ █
█ █▀▀° █ █
█▒▒ Operation 1 - CreateAccount ▒▒█
█ ▒ ▒ █
█ █ +---------------+-----------+--------------------------------------------------------------+ █ █
█ ▒ | Campo | Tipo | Descrição | ▒ █
█▒▒ +---------------+-----------+--------------------------------------------------------------+ ▒▒█
█ ▒ | Destination | AccountID | O ID da nova conta a ser criada. | ▒ █
█ █ | Starting | Int64 | A quantidade inicial de XLM (Lumens) para financiar a nova | █ █
█ ▒ | Balance | | conta. Deve ser suficiente para atender ao requisito de | ▒ █
█▒▒ | | | reserva mínima. | ▒▒█
█ ▒ +---------------+-----------+--------------------------------------------------------------+ ▒ █
█ █ █ █
█ ▒ ▒ █
█▒▒ Operation 2 - Payment ¡ ▒▒█
█ ▒ ▄█,▒ █
█ █ +-------------+-----------+----------------------------------------------------------------+ °▀▀█ █
█▒▒ | Campo | Tipo | Descrição | ▒▒█
█ ▒ +-------------+-----------+----------------------------------------------------------------+ ▒ █
█ █ | Destination | AccountID | Conta que receberá o pagamento. | █ █
█ ▒ | Asset | Asset | O ativo a ser enviado. Pode ser XLM (nativo) ou um token | ▒ █
█▒▒ | | | (emitido). | ▒▒█
█ ▒ | Amount | Int64 | A quantidade do ativo a ser enviada. | ▒ █
█ █ +-------------+-----------+----------------------------------------------------------------+ █ █
█ ▒ ▒ █
█▒▒ ▒▒█
█ ▒ Essa transação poderia ser usada para enviar criar uma nova conta primeiro e ▒ █
█ █ depois enviar token para ela (ou outra conta, dependendo do Destination). █ █
█ ▒ ▒ █
█▒▒ ¡ Para transações com sponsorship na blockchain Stellar, existem 2 operações que ▒▒█
█ ▒,█▄ devem ser usadas [2]: ▒ █
█ █▀▀° █ █
█▒▒ - BeginSponsoringFutureReserves ▒▒█
█ ▒ - Parâmetro SourceAccount —> define o sponsor account, que deve ser um ▒ █
█ █ signer da transação █ █
█ ▒ - Parâmetro SponsoredID —> define a conta que será patrocinada pelo ▒ █
█▒▒ sponsor (a conta que não vai precisar pagar os gas fees ou outros custos) ▒▒█
█ ▒ - EndSponsoringFutureReserves ▒ █
█ █ - Parâmetro SourceAccount █ █
█ ▒ ▒ █
█▒▒ Elas definem quais operações devem ser sponsored e quais não a partir do que ▒▒█
█ ▒ está no meio das duas operações. Por exemplo, se quisermos fazer o sponsor ▒ █
█ █ apenas da criação da conta, mas não do pagamento (levando como base o exemplo █ █
█ ▒ passado), devemos ter uma transação com as seguintes operações (com os devidos ▒ █
█▒▒ parâmetros setados corretamente): ¡ ▒▒█
█ ▒ ▄█,▒ █
█ █ 1. BeginSponsoringFutureReserves °▀▀█ █
█▒▒ 2. CreateAccount ▒▒█
█ ▒ 3. EndSponsoringFutureReserves ▒ █
█ █ 4. Payment █ █
█ ▒ ▒ █
█▒▒ Levando em conta que a sponsor account também deve assinar a transação para que ▒▒█
█ ▒ ela seja validada e incluída na blockchain, uma API HTTP também é necessária ▒ █
█ █ para a validação e assinatura da transação. Agora que você já sabe como funciona █ █
█ ▒ o básico das transações e operações da Stellar, vamos ver como foi possível ▒ █
█▒▒ drenar a sponsor account da wallet. ▒▒█
█ ▒ ▒ █
█ █ O principal erro cometido foi a falta de validação dos parâmetros globais █ █
█ ▒ opcionais. Se você olhar na documentação da Stellar para uma operação, você vai ▒ █
█▒▒ ¡ ver só os parâmetros específicos daquela operação (ex: Payment): ▒▒█
█ ▒,█▄ ▒ █
█ █▀▀° Payment █ █
█▒▒ ▒▒█
█ ▒ Sends an amount in a specific asset to a destination account. ▒ █
█ █ █ █
█ ▒ SDKs: JavaScript | Java | Go ▒ █
█▒▒ Threshold: Medium ▒▒█
█ ▒ Result: PaymentResult ▒ █
█ █ █ █
█ ▒ Parameters ▒ █
█▒▒ ▒▒█
█ ▒ +-------------+------------+---------------------------------------------+ ▒ █
█ █ | Parameter | Type | Description | █ █
█ ▒ +-------------+------------+---------------------------------------------+ ▒ █
█▒▒ | Destination | account ID | Account address that receives the payment. | ¡ ▒▒█
█ ▒ | Asset | asset | Asset to send to the destination account. | ▄█,▒ █
█ █ | Amount | integer | Amount of the aforementioned asset to send. | °▀▀█ █
█▒▒ +-------------+------------+---------------------------------------------+ ▒▒█
█ ▒ ▒ █
█ █ █ █
█ ▒ Porém, toda operação também possui o parâmetro opcional SourceAccount , que ▒ █
█▒▒ dita qual conta que é responsável por realizar aquela operação (no caso do ▒▒█
█ ▒ Payment, qual conta que vai fazer o pagamento). Caso o parâmetro não seja ▒ █
█ █ setado, o usuário que iniciou a transação que irá ser o SourceAccount padrão [6]. █ █
█ ▒ ▒ █
█▒▒ No caso da wallet, eles não validavam isso em uma operação Payment e permitiam ▒▒█
█ ▒ eu enviar uma transação com o SourceAccount = sponsorAccount. Isso passava ▒ █
█ █ pela validação da transação e a assinatura da sponsor account era incluida. Uma █ █
█ ▒ vez que o sponsor account “aceitou a transação” (por conta da assinatura estar ▒ █
█▒▒ ¡ incluída), o pagamento era feito a partir da conta do sponsor account para o ▒▒█
█ ▒,█▄ destino escolhido. Dessa forma, era possível drenar todos os fundos que a ▒ █
█ █▀▀° sponsor account tinha ($150,000 no momento). █ █
█▒▒ ▒▒█
█ ▒ Situação 2 - Depósitos em Exchanges ▒ █
█ █ █ █
█ ▒ Outra situação que envolve blockchain que é possível de ser exploitada é quando ▒ █
█▒▒ os depósitos em exchanges são validados de forma incorreta. Isso geralmente ▒▒█
█ ▒ ocorre pela complexidade de lidar com várias blockchains diferentes, que possuem ▒ █
█ █ detalhes técnicos diferentes. Vou explicar alguns casos que já encontrei em uma █ █
█ ▒ das minhas auditorias. ▒ █
█▒▒ ▒▒█
█ ▒ Caso 1: XRP deposit (deprecated) ▒ █
█ █ █ █
█ ▒ O cliente havia implementado o depósito de crypto em XRP, que possui uma ▒ █
█▒▒ blockchain própria (XRPL). O problema nesse caso foi que a forma que a transação ¡ ▒▒█
█ ▒ de depósito era validada não estava correta pela existência de uma feature da ▄█,▒ █
█ █ chain XRP. Apesar de ser uma vulnerabilidade conhecida e bem documentada [3], quem °▀▀█ █
█▒▒ está implementando o depósito não espera que exista essa peculiaridade e acaba ▒▒█
█ ▒ achando que é só mais uma blockchain inserida na lista de criptomoedas aceitas ▒ █
█ █ como depósito. █ █
█ ▒ ▒ █
█▒▒ Primeiro, para entender o exploit, precisamos ver como funciona uma transação de ▒▒█
█ ▒ pagamento no XRPL: ▒ █
█ █ █ █
█ ▒ { ▒ █
█▒▒ "TransactionType": "Payment", ▒▒█
█ ▒ "Account": "rSenderAccountID....................", ▒ █
█ █ "Destination": "rRecipientAccountID.................", █ █
█ ▒ "Amount": "100000000", ▒ █
█▒▒ ¡ "Fee": "12", ▒▒█
█ ▒,█▄ "Sequence": 123456, ▒ █
█ █▀▀° "Flags": 0, █ █
█▒▒ "SigningPubKey": ▒▒█
█ ▒ "ED................................................................", ▒ █
█ █ "TxnSignature": █ █
█ ▒ "......................................................" ▒ █
█▒▒ } ▒▒█
█ ▒ ▒ █
█ █ █ █
█ ▒ Vemos que o o pagamento será feito em XRP (já que nenhum outro token foi ▒ █
█▒▒ especificado) com o Amount sendo 100000000 drops (10^-6 XRP) ou 1 XRP. ▒▒█
█ ▒ Entretanto, existe uma feature no XRP que são os “Partial Payments”, que permite ▒ █
█ █ que uma transação seja bem sucedida mesmo pagando um valor menor que o █ █
█ ▒ especificado no campo Amount , que tem alguns use cases quando você não quer ▒ █
█▒▒ que a transação reverta totalmente quando não há liquidez de um token. ¡ ▒▒█
█ ▒ ▄█,▒ █
█ █ Portanto, imagine que uma exchange verifique o valor do depósito a partir do °▀▀█ █
█▒▒ campo Amount : ▒▒█
█ ▒ ▒ █
█ █ // pseudocodigo █ █
█ ▒ function validateDeposit(tx){ ▒ █
█▒▒ deposited_value = tx.Amount ▒▒█
█ ▒ ▒ █
█ █ if (tx.status == "tesSUCCESS"){ █ █
█ ▒ return deposited_value ▒ █
█▒▒ } ▒▒█
█ ▒ } ▒ █
█ █ █ █
█ ▒ ▒ █
█▒▒ ¡ Em uma situação como essa, é possível exploitar a exchange: ▒▒█
█ ▒,█▄ ▒ █
█ █▀▀° 1. Um atacante envia uma transação com o flag tfPartialPayment ativado e █ █
█▒▒ com o campo Amount definido para um valor muito alto (ex: 1.000.000 USD). ▒▒█
█ ▒ 2. A transação é executada e, devido a limitações (falta de liquidez), entrega ▒ █
█ █ apenas um valor muito pequeno (ex: 1 USD). O resultado da transação é █ █
█ ▒ tesSUCCESS. ▒ █
█▒▒ 3. Se a exchange ou o gateway vulnerável verificar apenas o campo Amount ▒▒█
█ ▒ (o valor máximo que o atacante disse que queria enviar) e não o valor real ▒ █
█ █ entregue (que é armazenado no campo DeliveredAmount da transação), a █ █
█ ▒ exchange credita os 1.000.000 USD ao atacante, em vez do 1 USD real. ▒ █
█▒▒ ▒▒█
█ ▒ É importante ressaltar que o campo Amount foi renomeado para DeliverMax ▒ █
█ █ alguns meses atrás, portanto acredito estar mais difícil dos desenvolvedores se █ █
█ ▒ confundirem e deixarem as exchanges vulneráveis. ▒ █
█▒▒ ¡ ▒▒█
█ ▒ Caso 2: Invalid success check ▄█,▒ █
█ █ °▀▀█ █
█▒▒ Outro caso de exchanges sendo vulneráveis na feature de depósito aconteceu com a ▒▒█
█ ▒ Kraken e foi reportado em seu bug bounty (de forma meio controversa). O hacker ▒ █
█ █ descobriu que a Kraken não validava corretamente se o depósito foi bem sucedido █ █
█ ▒ em blockchains que usam EVM (Ethereum Virtual Machine) [4]. ▒ █
█▒▒ ▒▒█
█ ▒ Para entender os detalhes, é preciso saber que redes Ethereum-like possuem ▒ █
█ █ internal transactions para uma transação. Quando uma função de um smart contract █ █
█ ▒ chama outra função, isso gera uma internalTx, porém, é importante ressaltar que ▒ █
█▒▒ elas podem ser chamadas de 2 formas: ▒▒█
█ ▒ ▒ █
█ █ 1. Forma convencional █ █
█ ▒ ▒ █
█▒▒ ¡ contract ContratoA { ▒▒█
█ ▒,█▄ // Endereço do ContratoB para a chamada ▒ █
█ █▀▀° ContratoB public B; █ █
█▒▒ ▒▒█
█ ▒ // Construtor: Vincula ContratoA ao endereço do ContratoB ▒ █
█ █ teste(address _enderecoContratoB) public { █ █
█ ▒ // Cria uma instância do ContratoB na memória, apontando para o endereço ▒ █
█▒▒ real ▒▒█
█ ▒ B = ContratoB(_enderecoContratoB); ▒ █
█ █ B.teste(); // chama contratoB.teste() ---> Internal Tx █ █
█ ▒ } ▒ █
█▒▒ ▒▒█
█ ▒ ▒ █
█ █ Nesse caso, se a chamada para B.teste() falhar/reverter, toda a transação é █ █
█ ▒ revertida, lembrando que a transação principal é a chamada para ▒ █
█▒▒ ContratoA.teste(). ¡ ▒▒█
█ ▒ ▄█,▒ █
█ █ 1. Forma Crua (Raw) [5] °▀▀█ █
█▒▒ ▒▒█
█ ▒ contract ContratoA { ▒ █
█ █ // Endereço do ContratoB para a chamada █ █
█ ▒ ContratoB public B; ▒ █
█▒▒ ▒▒█
█ ▒ // Construtor: Vincula ContratoA ao endereço do ContratoB ▒ █
█ █ teste(address _enderecoContratoB) public { █ █
█ ▒ // Cria uma instância do ContratoB na memória, apontando para o endereço ▒ █
█▒▒ real ▒▒█
█ ▒ B = ContratoB(_enderecoContratoB); ▒ █
█ █ █ █
█ ▒ bytes memory data = abi.encodeWithSelector( ▒ █
█▒▒ ¡ bytes4(keccak256("teste()")), ▒▒█
█ ▒,█▄ ); // discriminator ▒ █
█ █▀▀° █ █
█▒▒ (bool sucesso, bytes memory returndata) = B.call(data); // chama ▒▒█
█ ▒ contratoB.teste() ---> Internal Tx ▒ █
█ █ } █ █
█ ▒ ▒ █
█▒▒ ▒▒█
█ ▒ Nesse caso, se a internal transaction reverter, a transação principal não ▒ █
█ █ reverte automaticamente. Ao invés disso, o sucesso ou não da internal █ █
█ ▒ transaction é retornado e deve ser checado de forma manual. ▒ █
█▒▒ ▒▒█
█ ▒ Agora, que você sabe como as internal transactions funciona, vamos ver como ▒ █
█ █ funcionava o exploit: █ █
█ ▒ ▒ █
█▒▒ Fluxo do Ataque (Fluxograma) ¡ ▒▒█
█ ▒ ▄█,▒ █
█ █ ┌───────────────────────────┐ °▀▀█ █
█▒▒ │ Attack.controlAttack() │ ▒▒█
█ ▒ └───────────────┬───────────┘ ▒ █
█ █ │ █ █
█ ▒ │ Chamada externa para si mesmo ▒ █
█▒▒ │ Permite que o nível superior ▒▒█
█ ▒ │ ignore o revert que ocorre abaixo ▒ █
█ █ │ █ █
█ ▒ (! NÃO reverte neste nível!) ▒ █
█▒▒ │ ▒▒█
█ ▒ ▼ ▒ █
█ █ ┌───────────────────────────┐ █ █
█ ▒ │ Attack.doDeposit() │ ▒ █
█▒▒ ¡ └───────────────┬───────────┘ ▒▒█
█ ▒,█▄ │ ▒ █
█ █▀▀° │ doDeposit() é basicamente: █ █
█▒▒ │ transfer() ▒▒█
█ ▒ │ revert() ▒ █
█ █ │ █ █
█ ▒ (! ISTO REVERTE !) ▒ █
█▒▒ │ ▒▒█
█ ▒ ▼ ▒ █
█ █ ┌──────────────────────────────┐ █ █
█ ▒ │ Depósito Matic para Kraken │ ▒ █
█▒▒ └──────────────────────────────┘ ▒▒█
█ ▒ ▒ █
█ █ █ █
█ ▒ Trecho de Código Relevante (O que acontece internamente) ▒ █
█▒▒ ¡ ▒▒█
█ ▒ function 0xb6852ff9(address varg0, uint256 varg1) public nonPayable { ▄█,▒ █
█ █ require(4 + (msg.data.length - 4) - 4 >= 64); °▀▀█ █
█▒▒ ▒▒█
█ ▒ v0 = v1 = msg.sender == _sender; ▒ █
█ █ if (msg.sender != _sender) { █ █
█ ▒ v0 = msg.sender == address(this); ▒ █
█▒▒ } ▒▒█
█ ▒ ▒ █
█ █ require(v0, Error("Only owner.")); █ █
█ ▒ v2 = varg0.call().value(varg1).gas(!varg1 * 2300); ▒ █
█▒▒ ▒▒█
█ ▒ require(bool(v2), 0, RETURNDATASIZE()); ▒ █
█ █ require(varg1 < 1, Error("revert")); █ █
█ ▒ } ▒ █
█▒▒ ¡ ▒▒█
█ ▒,█▄ ▒ █
█ █▀▀° Como é possível ver, o código do exploit era mais ou menos assim: █ █
█▒▒ ▒▒█
█ ▒ contract Attack { ▒ █
█ █ controlAttack() public { █ █
█ ▒ bytes memory data = abi.encodeWithSelector( ▒ █
█▒▒ bytes4(keccak256("doDeposit()")), ▒▒█
█ ▒ ); // discriminator ▒ █
█ █ █ █
█ ▒ address(this).call(data); ▒ █
█▒▒ } ▒▒█
█ ▒ ▒ █
█ █ doDeposit() public { █ █
█ ▒ address("KrakenDepositAddress").transfer(100000000000000); // ▒ █
█▒▒ envia 100000 wei (0.0001 ETH) para o endereço de deposito da kraken ¡ ▒▒█
█ ▒ revert(); ▄█,▒ █
█ █ } °▀▀█ █
█▒▒ ▒▒█
█ ▒ ▒ █
█ █ Aqui, o atacante consegue enviar 0.00001 ETH para o endereço e depois reverter a █ █
█ ▒ transação interna (a que chama doDeposit()), mas não reverter a transação ▒ █
█▒▒ inteira. Provavelmente, o validador de depósito da kraken só validava se a ▒▒█
█ ▒ transação “pai” foi bem sucedida e não validava a internalTx que fez o depósito, ▒ █
█ █ fazendo com que os 0.00001 ETH fossem depositados na exchange, mas não fossem █ █
█ ▒ enviados para o endereço da blockchain. ▒ █
█▒▒ ▒▒█
█ ▒ No caso desses 2 ataques contra exchanges, se o exploit for bem sucedido você ▒ █
█ █ teria depositado tokens na exchange e, para drenar as reservas da plataforma só █ █
█ ▒ seria necessário realizar o saque (withdraw) dos tokens que foram depositados de ▒ █
█▒▒ ¡ forma maliciosa. ▒▒█
█ ▒,█▄ ▒ █
█ █▀▀° Conclusão █ █
█▒▒ ▒▒█
█ ▒ Com a natureza irreversível da blockchain e que não oferece a segurança ▒ █
█ █ operacional dos sistemas financeiros tradicionais, a segurança se resume à █ █
█ ▒ perfeição do código. Os exemplos analisados — desde a exploração de ▒ █
█▒▒ patrocínio de transações (sponsorship) no Solana e Stellar até as falhas de ▒▒█
█ ▒ validação de depósitos em exchanges como a Kraken e o caso do XRP — demonstram ▒ █
█ █ que a principal vulnerabilidade reside em erros de lógica na interface entre █ █
█ ▒ as regras de negócio e os detalhes técnicos de cada chain. Espero que, caso o ▒ █
█▒▒ seu interesse seja procurar falhas em sistemas Web3, esse artigo tenha deixado ▒▒█
█ ▒ mais claro o tipo e classe de vulnerabilidades que são encontradas e que são ▒ █
█ █ consideradas críticas nesse ecossistema. █ █
█ ▒ ▒ █
█▒▒ Referências ¡ ▒▒█
█ ▒ ▄█,▒ █
█ █ [1] https://solana.com/docs/core/transactions#signatures °▀▀█ █
█▒▒ [2] https://developers.stellar.org/docs/build/guides/transactions/sponsored-reserves ▒▒█
█ ▒ [3] https://xrpl.org/docs/concepts/payment-types/partial-payments ▒ █
█ █ [4] https://x.com/danielvf/status/1803780167027871878 █ █
█ ▒ [5] https://docs.soliditylang.org/en/latest/types.html#members-of-addresses ▒ █
█▒▒ [6] https://developers.stellar.org/docs/learn/fundamentals/transactions/list-of-operations ▒▒█
█ ▒ ▒ █
█ █ █ █
█▒▒▒▒▒ ░░ ▒▒▒▒▒▒▒▒█
█ ▒ ▓▄█
█ █ T R A M O I A · Z I N E · 2 0 2 6 gld ██
▀▄▄▄▄ ▒▒▄▄▀