Bem-vindo de volta ao Spotlight de Vulnerabilidades do Sherlock, onde destacamos uma vulnerabilidade impactante descoberta durante uma auditoria do Sherlock. Esta semana, examinamos um cálculo incorreto do nível de colateral encontrado no concurso da @plaza_finance por @0xadrii, @KupiaSecurity, @f, @farman1094_, e @0xnovaman33.
Resumo da Vulnerabilidade: Na função getRedeemAmount(...), o contrato calcula o nível de colateral para resgates de BOND usando o estado pós-negociação: // código atual (vulnerável) collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECISION) / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); Como a precificação do resgate usa então este collateralLevel estimado para decidir se paga o preço máximo ($100), um atacante pode comprar BOND a taxas de criação mistas (às vezes com desconto) até que o nível de colateral (atual) caia ≤ 1.2, e então resgatar todo o BOND no limite de $100—calculando com um depositAmount elaborado que empurra o nível de colateral estimado de volta acima de 1.2. Isso permite que o atacante extraia ETH (spread "sem risco") até que o nível de colateral atual do pool atinja o limite.
Passos do Ataque: 1) Fase de Configuração Parâmetros de exemplo do pool: - poolReserve = 120 ETH, bondSupply = 3000, levSupply = 200, preço do ETH = $3075 Regras de precificação: Criar (comprar) BOND: - Se collateralLevel ≤ 1.2: creationRate = tvl * 0.8 / bondSupply - Caso contrário: creationRate = $100 Resgatar (vender) BOND: - Se estimated collateralLevel ≤ 1.2: redeemRate = tvl * 0.8 / bondSupply - Caso contrário: redeemRate = $100
2) Fase A – Comprar BOND a taxas mistas/descontadas - Compre BOND enquanto monitora (atualmente) o nível de colateral = tvl / (bondSupply * 100). - A primeira compra ocorre enquanto o nível de colateral > 1.2 → mint perto de $100 por BOND. - Compras subsequentes empurram o nível de colateral atual abaixo de 1.2 → mint a uma taxa de criação descontada (por exemplo, ~$94.07), acumulando um grande saldo de BOND de forma barata.
3) Fase B – Resgatar todos os BOND ao preço máximo - Chamar redeem(BOND, depositAmount = attackerBondBalance, ...) - O contrato calcula o nível de colateral estimado usando o estado pós-resgate (bondSupply - depositAmount) e (tvl - depositAmount * 100) e (no exemplo) obtém um valor > 1.2 - Como esse valor estimado excede o limite, a redeemRate é definida para o máximo de $100, permitindo que o atacante retire todos os BOND acumulados a $100
4) Realização de Lucros - A diferença entre compras com desconto e vendas de $100 resulta em lucro líquido de ETH. - Nos números do PoC: gastar 60 ETH em duas compras retorna ~61.89 ETH na redenção → ~1.89 ETH de lucro. - O atacante pode iterar até que o nível de colateral do pool atual caia para ~1.2, extraindo aproximadamente: USD extraível ≈ ethPrice × poolReserve − 120 × bondSupply
Qual é o impacto? Extração direta de fundos / vazamento de valor: O atacante cria tokens a taxas com desconto e resgata a $100, drenando ETH do pool. Sem limites até o limite: Pode continuar até que o nível atual de colateral do pool diminua para ≈ 1.2. Inconsistência de preços sistêmica: Cria arbitragem que os usuários honestos pagam através de reservas/resultados piores do pool.
A Causa Raiz: Uso do estado pós-negociação para precificação de resgates O código calcula o collateralLevel como se o resgate já tivesse ocorrido: // usa (tvl - depositAmount*100) e (bondSupply - depositAmount) collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECISION) / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); 1. Isso permite que um atacante escolha depositAmount de forma que o nível estimado ultrapasse o limite (> 1.2), desbloqueando a taxa de resgate de $100, mesmo quando o estado atual do pool não justificaria isso. 2. Limitação de limite em uma métrica manipulada A decisão do teto de $100 depende deste nível colateral estimado manipulável, em vez do nível atual (pré-negociação), permitindo manipulação de preços.
A Mitigação: Calcule o preço de resgate a partir do estado atual do pool, não dos saldos estimados pós-resgate. A correção sugerida pelo projeto está correta: - collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECISION) - / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); + collateralLevel = (tvl * PRECISION) / (bondSupply * BOND_TARGET_PRICE);
Reforço Adicional (recomendado): 1. Monotonicidade de preço: Garantir que o preço de resgate não aumente à medida que o depositAmount aumenta (sem "vender mais, obter um melhor preço por unidade"). 2. Preços baseados em invariantes: Derivar a criação/resgate de um único invariante para que a simetria de compra/venda previna arbitragem unilateral. 3. Verificações de slippage: Exigir taxas mínimas/máximas fornecidas pelo usuário para criação e resgate. 4. Limite e controle: Limites de resgate por transação e por bloco para limitar danos se os limites forem alcançados. 5. Consistência entre caminhos: Alinhar os caminhos de criação e resgate para usar a mesma definição de nível de colateral (apenas estado atual).
Estamos orgulhosos de ter ajudado a garantir a @plaza_finance através desta descoberta. Quando precisa absolutamente de ser seguro, Sherlock é a escolha certa.
3,27K