Desde el hack de The DAO en 2016, las vulnerabilidades de smart contracts le han costado a la industria blockchain más de USD 8 mil millones en fondos robados o congelados. La naturaleza inmutable de blockchain significa que una vez que un contrato defectuoso se despliega, no puede ser parcheado: el código es ley, bugs y todo. Cada vulnerabilidad que llega a mainnet es un exploit potencial esperando a suceder, y los atacantes son sofisticados, bien financiados e implacables.
Entender los patrones de vulnerabilidad más comunes es la primera línea de defensa. Esta guía cubre diez vulnerabilidades críticas de smart contracts, explica como funciona cada una y provee estrategias concretas de prevención que los equipos de desarrollo deberían implementar antes de que cualquier código llegue a producción.
1. Ataques de reentrancia
La reentrancia es la vulnerabilidad de smart contracts más infame, responsable del hack original de The DAO que llevo al hard fork de Ethereum. Ocurre cuando un contrato hace una llamada externa a otro contrato antes de actualizar su propio estado. El contrato llamado puede entonces volver a llamar a la función original antes de que la primera ejecución se complete, creando un loop recursivo que drena fondos más allá de lo que debería permitirse.
El ejemplo clásico es una función de retiro que envía ETH a un usuario antes de establecer su saldo en cero. Un atacante despliega un contrato con una función fallback que llama a withdraw nuevamente cuando recibe ETH. El resultado es que el atacante puede retirar su saldo múltiples veces antes de que el contrato registre la deduccion.
Los ataques de reentrancia modernos han evoluciónado más allá del patrón de función única. La reentrancia cross-function explota estado compartido entre diferentes funciones en el mismo contrato. La reentrancia cross-contract apunta a estado compartido entre múltiples contratos en un protocolo. La reentrancia de solo lectura manipula funciones view de las que contratos externos dependen para calculos de precios o colateral.
- Segui el patrón checks-effects-interactions: válida condiciones, actualiza estado, después hace llamadas externas, nunca al reves
- Usa guardas de reentrancia (mutex locks) en todas las funciones que cambian estado y hacen llamadas externas
- Audita reentrancia cross-function y cross-contract, no solo patrones de función única
- Considera vectores de reentrancia de solo lectura si tu contrato expone funciones view usadas por otros protocolos
- Usa patrones pull-over-push de pago donde los usuarios retiran fondos en lugar de que el contrato los envie
2. Overflow y underflow de enteros
El overflow de enteros ocurre cuando una operación aritmetica produce un valor mayor que el máximo que un tipo de variable puede contener, causando que se reinicie a cero o un número pequeno. El underflow de enteros es lo inverso: restar de cero se reinicia al valor máximo. En contratos financieros, esto puede significar que un usuario con cero tokens repentinamente tenga miles de millones, o un saldo enorme se vuelva insignificante.
Antes de Solidity 0.8.0, las operaciones aritmeticas no verificaban overflow o underflow por defecto, haciendo de está una de las clases de vulnerabilidad más comunes. El exploit del token BEC en 2018 uso un overflow de enteros para generar miles de millones de tokens de la nada, colapsando el valor del token a cero.
Aunque Solidity 0.8+ incluye verificaciones de overflow integradas, el peligro no ha desaparecido. El bloque unchecked deshabilita explícitamente estas protecciones para optimización de gas. Además, el casting de tipos entre diferentes tamanos de enteros (uint256 a uint128, por ejemplo) puede truncar valores silenciosamente. Y los protocolos escritos en otros lenguajes como Vyper o Rust tienen sus propios comportamientos de overflow.
- Usa Solidity 0.8.0 o posterior para beneficiarte de las verificaciones de overflow y underflow integradas
- Audita cada uso de la keyword unchecked para confirmar que el overflow es verdaderamente imposible en ese contexto
- Se cuidadoso con el casting de tipos: válida explícitamente que los valores caben en el tipo destino antes de castear
- Usa SafeMath o librerias equivalentes para contratos que deben soportar versiones anteriores de Solidity
- Testea condiciones de borde: que pasa en cero, en valores máximos y en límites de tipo
3. Front-Running y ataques MEV
El front-running ocurre cuando un atacante ve una transacción pendiente en el mempool y envía su propia transacción con un precio de gas más alto para ejecutar primero. En blockchains públicas, todas las transacciones pendientes son visibles antes de incluirse en un bloque, creando una asimetria de información que actores sofisticados explotan para obtener ganancias. Esto es parte del fenomeno más amplio de Maximal Extractable Value (MEV).
El patrón de front-running más común en DeFi es el ataque sandwich. Un atacante ve un swap grande pendiente en un exchange descentralizado, compra el token objetivo primero (empujando el precio hacia arriba), deja que la transacción de la victima se ejecute al precio inflado, y luego vende inmediatamente después para obtener ganancia. La victima obtiene menos tokens de lo esperado, y el atacante se queda con la diferencia.
El front-running también afecta lanzamientos de tokens, mints de NFT, liquidaciones, actualizaciones de oráculos y votos de gobernanza. Cualquier transacción donde el resultado depende del orden de ejecución es potencialmente vulnerable.
- Implementa protección de slippage con impacto de precio máximo aceptable definido por el usuario en todas las funciones de swap
- Usa esquemas commit-reveal para operaciones donde el contenido de la transacción debe estar oculto hasta la ejecución
- Considera el envío de transacciones privadas vía Flashbots Protect o servicios similares de protección MEV
- Disena mecanismos que sean independientes del orden donde sea posible: subastas por lotes en lugar de primero en llegar, primero en servir
- Establece plazos razonables en las transacciones para evitar que sean retenidas y ejecutadas en momentos desventajosos
- Para gobernanza, usa propuestas con time-lock con votacion por snapshot para prevenir manipulación de votos con flash loans
4. Manipulación de oráculos
Los oráculos proporcionan datos externos a smart contracts, más comúnmente feeds de precios para protocolos DeFi. Si un atacante puede manipular los datos de precio de los que depende un contrato, puede enganar al protocolo para que tome decisiones basadas en información falsa. Este es uno de los vectores de ataque más devastadores financieramente, responsable de cientos de millones en pérdidas.
La técnica más común de manipulación de oráculos usa flash loans para distorsionar temporalmente precios on-chain. Un atacante pide prestado una cantidad masiva de tokens, los usa para mover el precio en un DEX, activa un protocolo vulnerable que lee ese precio del DEX, y luego se beneficia de la operación con precio incorrecto, todo en una sola transacción. Porque los flash loans no requieren colateral, el atacante no arriesga nada.
Incluso los proveedores de oráculos bien conocidos no son inmunes. Los feeds de Chainlink pueden tener datos obsoletos si las condiciones de actualización no se cumplen. Los oráculos TWAP de Uniswap pueden ser manipulados con capital sostenido durante múltiples bloques. Las implementaciones personalizadas de oráculos frecuentemente tienen riesgos de centralizacion o retrasos en las actualizaciones que crean ventanas de explotación.
- Usa precios promediados ponderados por tiempo (TWAP) en lugar de precios spot para resistir manipulación de un solo bloque
- Implementa verificaciones de desviacion de precio que rechacen actualizaciones más allá de un umbral razonable
- Usa múltiples fuentes de oráculos independientes con un mecanismo de mediana o consenso
- Agrega verificaciones de frescura: rechaza datos de precio más antiguos que un umbral definido
- Implementa circuit breakers que pasen operaciones durante volatilidad extrema del mercado
- Nunca uses un único pool de DEX como tu fuente de precio exclusiva para operaciones financieras críticas
5. Fallas de control de acceso
Las vulnerabilidades de control de acceso ocurren cuando funciones críticas carecen de verificaciones de permisos adecuadas, permitiendo a usuarios no autorizados ejecutar operaciones privilegiadas. Esto incluye funciones de inicialización desprotegidas, verificaciones de roles faltantes en operaciones de administración, y transferencias de ownership mal implementadas. El hack de la wallet multi-sig de Parity, que congelo más de USD 150 millones en ETH, fue causado por una función de inicialización desprotegida que cualquiera podía llamar.
El peligro se amplifica por la naturaleza pública de blockchain. Cada función en un contrato es visible y llamable por cualquiera a menos que se restrinja explícitamente. A diferencia del software tradicional donde el servidor controla el acceso, los smart contracts deben aplicar sus propios permisos enteramente a través de código. No hay firewall, no hay segmentación de red y no hay validación del lado del servidor, solo lo que el contrato mismo verifica.
Los patrones de proxy introducen complejidad adicional de control de acceso. El admin del proxy, el owner de la implementación y la autoridad de upgrade pueden ser roles diferentes, y la confusión entre ellos crea vulnerabilidades. Los patrones de proxy transparente ayudan separando llamadas de admin de llamadas de usuario, pero la mala configuración sigue siendo una fuente común de bugs.
- Usa librerias establecidas como AccessControl u Ownable de OpenZeppelin para gestión de permisos
- Implementa el principio de mínimo privilegio: cada rol debería tener solo los permisos que necesita
- Usa transferencias de ownership de dos pasos (proponer y aceptar) para prevenir transferencias accidentales a direcciónes incorrectas
- Agrega time-locks a cambios de parámetros críticos para que la comunidad pueda revisar y reaccionar antes de que los cambios surtan efecto
- Requeri aprobación multi-firma para operaciones de administración: nunca uses un único EOA para gobernanza del protocolo
- Audita patrones de proxy cuidadosamente, especialmente la relación entre el admin del proxy y el owner de la implementación
6. Ataques de flash loans
Los flash loans permiten a cualquiera pedir prestado una cantidad ilimitada de tokens con cero colateral, siempre que el préstamo sea devuelto dentro de la misma transacción. Aunque son una innovación legotima de DeFi para arbitraje y refinanciamiento, se han convertido en el mecanismo de financiamiento principal para exploits sofisticados. Los flash loans efectivamente le dan a cada atacante el capital de una ballena, eliminando la barrera financiera para ejecutar ataques de manipulación de mercado.
Un ataque típico de flash loan combina múltiples vulnerabilidades en una sola transacción atómica. El atacante pide prestados millones de dólares en tokens, los usa para manipular un oráculo de precios, explota un protocolo que depende de ese oráculo, extrae ganancias, devuelve el préstamo con interés, y se queda con la diferencia. Si cualquier paso falla, toda la transacción se revierte y el atacante pierde solo la tarifa de gas.
Los ataques de flash loan son particularmente peligrosos porque son libres de riesgo para el atacante y pueden ser ejecutados por cualquiera con el conocimiento técnico para construir la transacción. El exploit de Euler Finance en 2023 uso un flash loan para manipular precios de colateral, resultando en una perdida de USD 197 millones, el mayor ataque de flash loan hasta la fecha.
- Disena protocolos para ser resistentes a flash loans asumiendo que cualquier usuario podría tener capital ilimitado en una sola transacción
- Usa oráculos TWAP y promedios de precio multi-bloque que no puedan ser manipulados dentro de una sola transacción
- Implementa períodos mínimos de bloqueo para depósitos antes de que puedan usarse como colateral o poder de voto
- Agrega restricciones de operación en el mismo bloque: preveni depósito y préstamo en la misma transacción donde sea apropiado
- Testea tu protocolo con herramientas de simulación de flash loan para identificar vectores de ataque potenciales
- Considera si los invariantes de tu protocolo se mantienen cuando un usuario tiene capital temporal ilimitado
7. Denegacion de servicio (DoS)
La denegación de servicio en smart contracts ocurre cuando un atacante puede impedir que usuarios legitimos interactuen con un contrato. A diferencia de los ataques DoS web tradicionales que abruman servidores con tráfico, los DoS de smart contracts explotan fallas logicas que bloquean funciones críticas permanente o temporalmente.
El patrón más común es el DoS por loop ilimitado. Si un contrato itera sobre un array que crece sin límite, como una lista de holders de tokens para distribución de dividendos, un atacante puede agregar suficientes entradas para que el costo de gas de la iteración exceda el límite de gas del bloque. La función se vuelve permanentemente inllamable, y cualquier fondo bloqueado detrás de ella se vuelve inaccesible.
Otro vector común es el DoS por revert inesperado. Si un contrato envía ETH a una lista de direcciónes y una de esas direcciónes es un contrato que revierte al recibir, toda la operación por lotes falla. Un atacante puede explotar esto para bloquear liquidaciones de subastas, conteo de votos de gobernanza, o cualquier operación que deba procesar una lista de direcciónes.
- Evita loops ilimitados: usa patrones de paginacion o establece límites duros en los tamanos de arrays
- Preferi patrones pull-over-push de pago: deja que los usuarios retiren en lugar de enviarles pagos
- No hagas que operaciones críticas dependan de que llamadas externas sean exitosas: maneja las fallas con gracia
- Usa llamadas externas con gas limitado (call con stipend) para prevenir que contratos llamados consuman todo el gas
- Implementa mecanismos de recuperación de emergencia que puedan sortear funciones bloqueadas cuando sea necesario
- ¿Testea con escenarios adversarios: que pasa si un participante actúa maliciosamente?
8. Bugs de lógica en la lógica de negocio
Los bugs de lógica son vulnerabilidades donde el código hace exactamente lo que fue escrito para hacer, pero lo que fue escrito no es lo que los desarrolladores tenían la intencion. Estos son los bugs más difíciles de detectar porque las herramientas automatizadas buscan patrones de vulnerabilidad conocidos, mientras que los bugs de lógica son únicos para las reglas de negocio específicas de cada protocolo.
Ejemplos comunes incluyen calculos de tarifas incorrectos que permiten a los usuarios evitar pagar tarifas bajo condiciones específicas, fórmulas de distribución de recompensas que pueden ser manipuladas depositando y retirando en momentos estratégicos, y mecanismos de liquidación que no contemplan casos límite en ratios de colateral. El incidente de gobernanza de Compound Finance, donde un bug en la lógica de distribución de recompensas causo que USD 90 millones en tokens se distribuyeran incorrectamente, fue un puro bug de lógica: el código se ejecuto impecablemente, pero la fórmula estaba mal.
Las vulnerabilidades de lógica de negocio se amplifican por la composabilidad. Cuando tu protocolo interactua con otros protocolos, el comportamiento combinado puede producir resultados que ninguno de los protocolos anticipo. Un protocolo de préstamos y un agregador de rendimiento podrian ser seguros individualmente, pero su interacción crea un loop de retroalimentación explotable.
- Escrihi especificaciones completas antes de codificar: documenta cada fórmula, cada caso límite y cada suposición
- Implementa testing basado en propiedades que verifica que los invariantes se mantienen en miles de escenarios aleatorios
- Usa verificación formal para propiedades matemáticas críticas como conservación de tokens y solvencia
- Testea con escenarios económicos realistas, no solo tests unitarios: simula condiciones de estrés del mercado
- Que expertos en el dominio (no solo investigadores de seguridad) revisen la lógica de negocio contra la especificación
- Documenta todas las suposiciones del protocolo explícitamente y testea que pasa cuando esas suposiciones se violan
9. Ataques de replay de firmas
El replay de firmas ocurre cuando un mensaje firmado válido puede ser reutilizado en un contexto donde no debería ser aceptado. Si un contrato acepta una autorización firmada para transferir tokens pero no rastrea que firmas han sido usadas, la misma firma puede ser envíada múltiples veces para drenar el saldo completo del usuario. Replicar firmas entre cadenas es otra variante: una firma válida en Ethereum puede ser replicada en Polygon o BSC si el contrato no incluye datos específicos de la cadena en el mensaje firmado.
Esta vulnerabilidad aparece frecuentemente en sistemas de meta-transacciones, transacciones sin gas, order books off-chain, y cualquier sistema que use firma de datos tipados EIP-712. La función permit (EIP-2612) es una implementación común que permite aprobaciónes de tokens vía firmas en lugar de transacciones on-chain, y es un objetivo común para ataques de replay cuando se implementa incorrectamente.
El riesgo se eleva después de forks de cadena. Cuando Ethereum transiciono a proof-of-stake y la cadena fork de proof-of-work continuo como ETHW, las firmas creadas en una cadena eran validas en ambas. Los protocolos que no incluian el chain ID en sus datos firmados eran vulnerables a replay cross-chain.
- Incluye un nonce en cada mensaje firmado y rastrea los nonces usados on-chain para prevenir reutilizacion
- Incluye el chain ID (EIP-155) en todos los datos firmados para prevenir replay cross-chain
- Incluye la dirección del contrato en los datos firmados para prevenir replay entre diferentes instancias del contrato
- Segui EIP-712 para firma de datos estructurados tipados: incluye separadores de dominio que previenen la mayoría de los vectores de replay
- Implementá expiración de firmas con deadlines para limitar la ventana de replay potencial
- Después de forks de cadena, audita todas las funciones basadas en firmas para vulnerabilidad de replay cross-chain
10. Storage no inicializado y problemas de proxy
Las vulnerabilidades de storage no inicializado ocurren cuando las variables de contrato no se configuran apropiadamente durante el despliegue o la inicialización, dejandolas con valores por defecto (cero para enteros, vacio para direcciónes) que crean condiciones explotables. Esto es especialmente peligroso con patrones de proxy, donde el constructor del contrato de implementación nunca se llama: el proxy llama a una función de inicialización en su lugar, y si esa función puede ser llamada por cualquiera, un atacante puede tomar ownership del contrato.
El ataque de proxy no inicializado de mayor perfil ocurrió contra el contrato de implementación de Wormhole en 2022. El equipo había dejado el contrato de implementación sin inicializar, permitiendo a un atacante llamar la función de inicialización, tomar ownership y actualizar el contrato a una versión maliciosa, resultando en una perdida de USD 320 millones.
La colisión de storage es un riesgo relacionado en patrones de proxy. Si el proxy y los contratos de implementación usan los mismos slots de storage para diferentes variables, escribir en uno corrompe al otro. El estándar EIP-1967 define slots de storage específicos para las direcciónes del admin del proxy y la implementación para evitar esto, pero las implementaciones de proxy personalizadas frecuentemente lo hacen mal.
- Siempre llama al inicializador en la misma transacción que el despliegue del proxy para prevenir front-running
- Usa el modifier initializer de OpenZeppelin para asegurar que las funciones de inicialización solo puedan llamarse una vez
- Deshabilita inicializadores en el constructor del contrato de implementación para prevenir ataques de inicialización directa
- Segui las convenciones de slots de storage de EIP-1967 para patrones de proxy para evitar colisiones de storage
- Verifica que todas las variables de estado tengan valores iniciales sensatos: nunca asumas que el valor por defecto cero es seguro
- Audita los caminos de upgrade cuidadosamente: asegurate de que las nuevas versiones de implementación no introduzcan conflictos de layout de storage
Construyendo un proceso de desarrollo con seguridad primero
Prevenir vulnerabilidades no se trata solo de conocer los patrones: requiere una cultura de desarrollo y un proceso que haga de la seguridad una preocupacion de primera clase en cada etapa.
- Escrihi una especificación detallada antes de escribir código, incluyendo todos los casos límite, suposiciones económicas y modos de falla
- Usa librerias establecidas y auditadas como OpenZeppelin en lugar de implementar patrones estándar desde cero
- Implementa suites de testing completas: tests unitarios para funciones individuales, tests de integración para interacciones cross-contract, y fuzz tests para condiciones de borde
- Ejecuta herramientas de análisis de seguridad automatizadas (Slither, Mythril, Echidna) como parte de tu pipeline CI/CD, no solo antes de auditorías
- Conduce revisiones de seguridad internas con un checklist cubriendo las diez categorías de vulnerabilidad de esta guía
- Usa verificación formal para invariantes matematicos críticos como conservación de tokens y ratios de colateral
- Despliega en testnets y ejecuta períodos de testing extendidos con monitoreo antes del despliegue en mainnet
- Implementa upgradeability o circuit breakers que te permitan responder a vulnerabilidades descubiertas
- Establece un programa de bug bounty con recompensas significativas proporcionales al valor que tus contratos protegen
- Monitorea tus contratos post-despliegue con alertas automatizadas para patrones de transacción inusuales
Cuando obtener una auditoría profesional
Una auditoría de seguridad profesional debería considerarse obligatoria para cualquier contrato que va a manejar valor significativo. Pero no todas las auditorías son iguales, y el timing importa. Auditar demasiado temprano, antes de que el código este estable, desperdicia dinero ya que los hallazgos se vuelven obsoletos con cada cambio. Auditar demasiado tarde, justo antes del lanzamiento, no deja tiempo para abordar apropiadamente los hallazgos.
El timing ideal es después de que el codebase está completo en funcionalidades, después de que se han conducido revisiones internas y análisis automatizados, y con suficiente margen antes del lanzamiento para abordar hallazgos y obtener una re-revisión. Planifica al menos 4-6 semanas para la auditoría inicial y 2-3 semanas para la re-revisión de correcciones.
Al evaluar firmas de auditoría, busca equipos con experiencia específica en el dominio de tu protocolo: DeFi lending, AMMs, bridges, marketplaces de NFT y sistemas de gobernanza cada uno tiene patrones de vulnerabilidad únicos. Las mejores firmas de auditoría combinan herramientas automatizadas, revisión manual de código por múltiples auditores independientes y modelado de ataques económicos.
Considera contratar múltiples firmas de auditoría para protocolos de alto valor. Diferentes auditores tienen diferentes fortalezas y puntos ciegos, y una vulnerabilidad crítica encontrada por el segundo auditor que el primero no detecto puede ahorrar millones.
La seguridad de smart contracts no es una actividad de una sola vez: es una disciplina continua que abarca todo el ciclo de vida del desarrollo. Las diez vulnerabilidades descritas en esta guía representan la mayoría de los fondos perdidos en exploits de blockchain, y cada una es prevenible con diseño, testing y revisión adecuados.
En Xcapit, nuestro equipo de ciberseguridad combina expertise profundo en smart contracts con certificación ISO 27001 y años de experiencia en blockchain en producción. Desde auditorías de seguridad y penetration testing hasta la construcción de procesos de desarrollo con seguridad primero, ayudamos a proyectos blockchain a proteger a sus usuarios y su reputación. Conocé más sobre nuestros servicios de ciberseguridad.
Fernando Boiero
CTO & Co-Fundador
Más de 20 años en la industria tecnológica. Fundador y director de Blockchain Lab, profesor universitario y PMP certificado. Experto y líder de pensamiento en ciberseguridad, blockchain e inteligencia artificial.
Mantente al día
Recibí novedades sobre IA, blockchain y ciberseguridad en tu bandeja de entrada.
Respetamos tu privacidad. Podés desuscribirte en cualquier momento.
¿Construyendo sobre blockchain?
Tokenización, smart contracts, DeFi — lo hemos implementado todo.
También te puede interesar
Transformación digital para utilities: cómo modernizar energía sin reemplazar el core
Una guía práctica para utilities y empresas de energía: cómo integrar SCADA, IoT, IA, tokenización y ciberseguridad para modernizar operaciones sin reemplazar sistemas críticos.
Account abstraction: ERC-4337 y el futuro de la UX Crypto
Aprendé cómo ERC-4337 account abstraction elimina las seed phrases y los fees de gas. Explorá la arquitectura, capacidades, implementaciones y cómo construir con ella.
Cómo construir pipelines DevSecOps para proyectos Blockchain
Cómo diseñar e implementar un pipeline DevSecOps específico para desarrollo blockchain — análisis estático de smart contracts, pipelines de auditoría automatizadas, gestión de secretos, automatización de deployments y monitoreo post-deployment.