09 · Seguridad y auditoría
Nivel: Avanzado · ⏱️ Duración estimada: 180 min · Fuente: Trail of Bits Building Secure Contracts y ConsenSys Smart Contract Best Practices ⬅️ Currículo · 📚 Bibliografía 🧭 ⬅️ Anterior: 08 · Tokens y estándares · 📚 Índice · ➡️ Siguiente: 10 · Oráculos, almacenamiento e indexación 📖 Glosario de términos · 🌱 ¿Nuevo en esto? Empieza aquí
🎯 Objetivos
- Reconocer las clases de vulnerabilidad más frecuentes en contratos y sus señales típicas en el código.
- Aplicar un proceso de auditoría reproducible de siete pasos, desde el alcance hasta la verificación de la corrección.
- Combinar revisión manual con pruebas unitarias, fuzzing, pruebas de invariantes y análisis estático.
- Reproducir un hallazgo con una prueba mínima y clasificarlo por impacto y probabilidad.
- Practicar la seguridad respetando límites éticos y legales, sin atacar sistemas ajenos.
📚 Resultados de aprendizaje
Al finalizar, el estudiante podrá:
- Identificar reentrancia, fallos de control de acceso, lógica económica y manipulación de oráculo en código real.
- Ejecutar un ciclo de auditoría con alcance, actores, activos e invariantes documentados.
- Construir pruebas de fuzzing e invariantes que expongan una vulnerabilidad concreta.
- Reproducir un exploit con una prueba mínima y explicar la corrección que lo neutraliza.
- Clasificar hallazgos por impacto y probabilidad para priorizar su remediación.
- Aplicar herramientas de análisis estático como Slither dentro del flujo de revisión.
🗺️ Temas
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Reentrancia | Permite reentrar antes de actualizar estado y drenar fondos; patrón clásico y aún vigente. |
| 2 | Control de acceso | Un onlyOwner faltante o mal aplicado entrega el contrato a cualquiera. |
| 3 | Lógica económica | Errores de precisión, redondeo y flujos de valor generan pérdidas silenciosas. |
| 4 | Front-running y MEV | El orden de las transacciones es manipulable y afecta a subastas y liquidaciones. |
| 5 | Manipulación de oráculo | Un precio spot barato de mover habilita ataques, a veces vía flash loan. |
| 6 | delegatecall y upgrades |
Ejecutar código externo sobre el propio almacenamiento puede corromper el estado. |
| 7 | DoS y agotamiento de gas | Bucles no acotados o dependencias externas pueden bloquear funciones críticas. |
| 8 | Firmas y replay | Firmas sin nonce o sin chainId se reutilizan en otra cadena o transacción. |
🧠 Modelo mental
Auditar un contrato se parece a inspeccionar un edificio antes de abrirlo al público: no basta con que las luces enciendan. Recorres cada puerta preguntando quién puede abrirla, qué pasa si alguien fuerza el orden de entrada y salida, y qué ocurre si un proveedor externo, como el ascensor, deja de responder. El código "funciona" en la ruta feliz; el auditor busca las rutas que nadie quiso recorrer.
El límite de la analogía: un edificio es estático y su inspector confía en normas maduras, mientras que un contrato opera en un entorno adversarial y componible donde otros contratos pueden llamarlo en cualquier orden y un atacante puede pedir prestado capital enorme por unos segundos. Por eso la seguridad no termina en una lista de comprobación: exige razonar sobre invariantes que deben cumplirse pase lo que pase.
🧩 Esquema visual
La reentrancia clásica explota que el contrato envía valor antes de actualizar su estado: el fallback del atacante reingresa mientras el saldo sigue intacto.
sequenceDiagram
participant A as "Atacante"
participant V as "Vault"
A->>V: withdraw
V->>A: call externo que envia ETH
Note over A: se ejecuta el fallback del atacante
A->>V: withdraw de nuevo
V->>A: call externo que envia ETH otra vez
Note over V: el saldo aun no se habia actualizado
V->>V: actualiza el saldo demasiado tarde
Una auditoría profesional es un ciclo con verificación final, no una lectura única del código.
flowchart LR
A["Definir alcance e invariantes"] --> B["Revisión manual del código"]
B --> C["Pruebas, fuzzing e invariantes"]
C --> D["Hallazgos clasificados por severidad"]
D --> E["Corrección por el equipo"]
E --> F["Verificación del fix"]
F --> B
📖 Conceptos y definiciones
- Invariante: propiedad que siempre debe ser cierta (por ejemplo, la suma de saldos igual al suministro); es la base de las pruebas de invariantes.
- Reentrancia: reingreso a una función antes de que actualice su estado; se mitiga con el patrón checks-effects-interactions y guardas.
- Front-running / MEV: extracción de valor reordenando, insertando o censurando transacciones en el mempool.
- Manipulación de oráculo: distorsión temporal de un precio de referencia para engañar la lógica del contrato.
delegatecall: llamada que ejecuta código ajeno usando el almacenamiento del llamante; base de los proxies actualizables y de riesgos graves.- Fuzzing: generación de entradas aleatorias para violar una propiedad; Echidna y el fuzzer de Foundry son herramientas habituales.
- Análisis estático: inspección del código sin ejecutarlo para detectar patrones peligrosos; Slither y Mythril son ejemplos.
- Impacto y probabilidad: dimensiones para clasificar un hallazgo y priorizar su corrección.
🔬 Profundización
Severidad: la matriz impacto × probabilidad
Las firmas de auditoría no reportan "bugs" sueltos: clasifican cada hallazgo cruzando cuánto daño causaría (fondos perdidos, protocolo congelado, dato incorrecto) con qué tan plausible es que ocurra (¿lo dispara cualquiera, o exige condiciones improbables y capital enorme?). Una matriz típica:
| Impacto \ Probabilidad | Alta | Media | Baja |
|---|---|---|---|
| Alto (pérdida de fondos) | Crítica | Alta | Media |
| Medio (funcionalidad degradada) | Alta | Media | Baja |
| Bajo (molestia o gas extra) | Media | Baja | Informativa |
Trail of Bits, OpenZeppelin y las plataformas de concursos como Code4rena o Sherlock usan variantes de este esquema; lo importante no es la etiqueta exacta sino que la clasificación sea argumentada y reproducible. Un hallazgo "crítico pero teórico" mal justificado destruye la credibilidad de un informe tanto como uno real que se pasó por alto.
Mini-casos históricos: la misma lección, distinta década
| Caso | Año | Pérdida histórica | Causa raíz | Lección |
|---|---|---|---|---|
| The DAO | 2016 | ~3.6 M ETH | Reentrancia: enviaba ETH antes de actualizar el saldo | Checks-effects-interactions no es opcional; el incidente motivó el fork que separó Ethereum de Ethereum Classic |
| Ronin Bridge | 2022 | ~624 M USD | Claves de 5 de 9 validadores comprometidas vía ingeniería social | La criptografía perfecta no protege un quorum de confianza demasiado pequeño y mal custodiado |
| Wormhole | 2022 | ~326 M USD | Verificación de firma defectuosa: aceptaba una función de validación obsoleta | Todo lo que "verifica" debe probarse con entradas hostiles, no solo con las válidas |
| Euler Finance | 2023 | ~197 M USD | Una función de donación rompía el invariante de solvencia usado por la liquidación | Cada función nueva debe evaluarse contra los invariantes de todo el sistema; los fondos fueron devueltos tras negociación |
Nótese el patrón: solo uno de los cuatro es "un bug de Solidity" clásico. Los otros tres son fallos de diseño, de custodia de claves o de interacción entre módulos correctos por separado.
Qué detecta cada técnica (y qué no)
Ninguna herramienta cubre todo el espectro; una auditoría seria las apila porque sus puntos ciegos son complementarios:
| Clase de bug | Análisis estático (Slither) | Fuzzing (Foundry/Echidna) | Invariantes | Revisión manual |
|---|---|---|---|---|
| Reentrancia por patrón de código | ✅ detecta el patrón | ⚠️ solo si la prueba lo ejercita | ✅ si el invariante de saldos está definido | ✅ |
| Control de acceso faltante | ✅ parcial | ✅ con llamadas desde cuentas aleatorias | ✅ | ✅ |
| Redondeo y precisión | ❌ | ✅ excelente con entradas extremas | ✅ | ⚠️ fácil de pasar por alto |
| Lógica económica multi-contrato | ❌ | ⚠️ requiere entorno completo | ✅ la técnica más fuerte | ✅ |
| Error de diseño del protocolo | ❌ | ❌ | ❌ | ✅ única técnica que lo ve |
La consecuencia práctica: un slither . limpio y un fuzzing en verde acotan clases enteras de errores, pero el caso Euler demuestra que el fallo puede vivir en la interacción entre funciones individualmente correctas, territorio exclusivo de los invariantes bien elegidos y de la revisión humana.
Anatomía de un ataque de préstamo relámpago
Los retos de este módulo se entienden mejor viendo cómo se combinan. Un flash loan no es una vulnerabilidad: es una herramienta que elimina el capital como barrera de entrada. Quien no tiene un millón puede operar como si lo tuviera, siempre que lo devuelva en la misma transacción.
Eso convierte ataques que eran teóricos en ataques que cualquiera puede ejecutar.
El ataque completo, en una sola transacción atómica:
1. Pedir prestados 10 000 000 USDC ← sin colateral: se devuelve al final o revierte todo
2. Comprar TOKEN en un pool pequeño ← el precio spot de ese pool se dispara
3. Depositar TOKEN como garantía en el ← el protocolo lee el precio del pool manipulado
protocolo víctima, que lo valora caro y concede un préstamo desproporcionado
4. Retirar el préstamo en USDC
5. Deshacer la compra: el precio vuelve
6. Devolver los 10 000 000 + comisión
7. Quedarse la diferencia
Lo que hay que ver: el atacante no rompió ninguna criptografía ni encontró un desbordamiento. Usó cada contrato exactamente como estaba escrito. El fallo está en un supuesto: que el precio de un pool refleja el valor de mercado. Es cierto en condiciones normales y falso durante un bloque con liquidez prestada.
Por qué la atomicidad lo hace posible: si algo sale mal en cualquier paso, toda la transacción revierte y el préstamo nunca existió. El atacante no arriesga capital, solo gas. Un intento fallido cuesta unos céntimos.
Las tres defensas, por orden de eficacia:
| Defensa | Qué logra | Límite |
|---|---|---|
| TWAP en lugar de spot | Manipular la media exige sostener el precio muchos bloques, lo que multiplica el coste | Reacciona con retraso: en un movimiento real de mercado, va por detrás |
| Agregar varias fuentes | Un solo pool manipulado no mueve la mediana | Añade dependencia de más proveedores, cada uno con su riesgo |
| Circuit breaker | Detiene la operación si el precio se sale de rango | Alguien tiene que definir "rango razonable", y ese alguien es un punto de confianza |
💡 En una frase: los ataques caros de verdad no rompen el código, rompen un supuesto. Escribe los supuestos de tu contrato antes de escribir el contrato.
🎓 Si ya dominas esto — método de auditoría, más allá del catálogo de bugs
- Empieza por las invariantes, no por el código. "La suma de saldos iguala el suministro", "nadie retira más de lo que depositó". Un catálogo de vulnerabilidades encuentra lo conocido; una invariante bien escrita encuentra lo que nadie ha catalogado todavía.
- La reentrancia entre funciones distintas evade el guard ingenuo.
nonReentrantenretirar()no protege si el reingreso ocurre portransferir(). Hay que razonar sobre el estado compartido, no sobre la función. - La reentrancia de solo lectura fue la sorpresa de 2022. Una función
viewconsultada a mitad de una transferencia devuelve un estado inconsistente; un tercero que confía en eseviewtoma decisiones sobre datos que no representan ningún estado real. No hay escritura, y aun así hay agujero. tx.originno es control de acceso. Distingue quién inició la cadena de llamadas de quién llama directamente; usarlo para autorizar permite que un contrato intermedio actúe en nombre de la víctima. Se usamsg.sender, siempre.- La severidad no es una etiqueta, es impacto × probabilidad. Un fallo que drena todo con una condición irrepetible puede ser menos urgente que uno que filtra poco de forma continua. Un informe que no argumenta ambos ejes no ayuda a priorizar.
- Ninguna herramienta sustituye la revisión manual. Slither y Echidna encuentran patrones y violaciones de propiedades que tú definiste. Que la lógica de negocio permita retirar el doble no es un patrón: es una propiedad que solo detecta quien entendió el negocio.
🧪 Laboratorio guiado
- Abre los retos del repositorio en
security-challengesy compila los contratos para partir de una base limpia.
forge build
- Ejecuta las pruebas con trazas para observar el flujo de un exploit reproducido paso a paso.
forge test -vv
Pasa el análisis estático sobre los contratos y contrasta cada alerta de
slithercon una revisión manual del flujo señalado.Consulta el catálogo de laboratorios para elegir el reto adecuado a la vulnerabilidad que estudias.
📝 Reto verificable
Toma un contrato vulnerable de security-challenges, escribe una prueba mínima que reproduzca el exploit, corrige la causa raíz y documenta el hallazgo con su clasificación de impacto y probabilidad.
Criterio de aceptación: existe una prueba que falla contra el contrato vulnerable y pasa contra el corregido; el informe describe alcance, activos, invariante violado y la corrección; y forge test -vv queda en verde tras el arreglo sin desactivar la prueba del exploit.
⚠️ Errores frecuentes
| Síntoma | Causa y cómo comprobarlo |
|---|---|
| Fondos drenados en una sola transacción | Reentrancia; busca llamadas externas antes de actualizar estado y añade una prueba de reingreso. |
| Cualquiera ejecuta una función privilegiada | Falta o mal uso de modificadores de acceso; prueba llamando desde una cuenta sin rol. |
| El precio usado es absurdo por un instante | Oráculo spot manipulable; valida antigüedad, rango y considera TWAP. |
| Un proxy queda inutilizable tras actualizar | Colisión de almacenamiento en delegatecall; compara los layouts antes y después. |
| Una firma vale en otra cadena | Falta de chainId/nonce; revisa el dominio EIP-712 y añade protección de replay. |
| El análisis estático "no encuentra nada" | Falsa sensación de seguridad; ninguna herramienta sustituye la revisión manual de invariantes. |
🛡️ Seguridad y ética
- Nunca pruebes exploits contra sistemas en producción ni contra contratos de terceros sin autorización explícita.
- Trabaja solo en local o testnet, con contratos propios o de laboratorio, y sin fondos ni claves reales.
- Practica la divulgación responsable: si descubres una vulnerabilidad real, repórtala de forma privada al equipo afectado.
- Documenta cada hallazgo de forma reproducible para que la corrección pueda verificarse de manera independiente.
- Recuerda que las herramientas automáticas complementan, pero no reemplazan, el razonamiento sobre invariantes y privilegios.
🔗 Referencias
- Trail of Bits, Building Secure Contracts — https://secure-contracts.com/
- ConsenSys, Smart Contract Best Practices — https://consensysdiligence.github.io/smart-contract-best-practices/
- SWC Registry, Smart Contract Weakness Classification — https://swcregistry.io/
- Damn Vulnerable DeFi — https://www.damnvulnerabledefi.xyz/
- Fuente primaria: EIP-155, Simple replay attack protection — https://eips.ethereum.org/EIPS/eip-155
✅ Criterio de dominio
- Reproduces y corriges al menos una vulnerabilidad con una prueba que documenta el antes y el después.
- Redactas un informe con alcance, invariantes y clasificación de impacto y probabilidad.
- Justificas por qué la revisión manual sigue siendo imprescindible pese al análisis estático y el fuzzing.
🧭 Navegación
⬅️ Módulo 08 · Tokens y estándares · 📚 Índice del currículo · ➡️ Módulo 10 · Oráculos, almacenamiento e indexación
🧠 Autoevaluación del módulo
Responde sin volver atrás. Cada opción incorrecta corresponde a un error frecuente documentado en este mismo módulo: si fallas, la explicación te dice qué releer.