Los ataques por validación de entrada ausente ocurren cuando un contrato Solidity acepta direcciones controladas por el usuario, montos, identificadores, precios o valores de configuración sin verificar que estos satisfacen las suposiciones económicas y de seguridad del contrato. Para prevenirlo, valide cada entrada externa en el límite de confianza mediante listas blancas, comprobaciones de límites, verificaciones de direcciones cero, autorizaciones e invariantes de estado antes de que los activos se muevan o haya cambios en la contabilidad.
El impacto es tangible: Qubit perdió $80.0M en BSC el 28-01-2022 en un incidente clasificado como Validación de Entrada Ausente. Estas fallas no son simplemente problemas de transacciones mal formadas. Un valor no revisado puede hacer que código válido de Solidity ejecute una acción comercial inválida, como acreditar colateral inexistente, transferir activos a un destino no confiable o crear deudas que exceden el límite real de un prestatario.
¿Qué es una vulnerabilidad de validación de entrada en Solidity y cómo funciona un ataque?
Una vulnerabilidad de validación de entrada en Solidity existe cuando un contrato trata un valor proporcionado por el llamante como confiable, aunque dicho valor controle permisos, la contabilidad de activos, precios o transiciones de estado. Un atacante proporciona un valor que es sintácticamente válido para el ABI pero económicamente inválido para el protocolo, y luego usa el estado resultante para retirar activos, acuñar reclamaciones, o evadir restricciones.
Solidity aplica tipos, no la intención del protocolo. Un uint256 puede ser cualquier número que no cause revert, y una address puede identificar desde una EOA, un contrato malicioso, la dirección cero o un token no soportado. El contrato debe hacer cumplir las reglas semánticas faltantes.
Una secuencia típica de ataque es:
- El atacante identifica una función pública que acepta una cantidad, token, destinatario, precio o identificador sin verificar.
- El atacante elige un valor que viola la suposición intencionada del protocolo.
- El contrato registra un saldo, derecho, límite de deuda o estado de autorización no seguro.
- El atacante llama a una función posterior que confía en ese estado contaminado.
- El protocolo transfiere activos o acuña una reclamación transferible basada en ese estado falso.
- Otros usuarios asumen la pérdida cuando la discrepancia en la contabilidad se hace visible.
La pregunta crítica de revisión es: ¿qué hecho pretende representar esta entrada, y dónde verifica independientemente el contrato ese hecho? Si la respuesta es “el llamante lo proporcionó”, el contrato tiene una vulnerabilidad de validación de entrada inapropiada.
¿Qué patrones en Solidity causan ataques por validación de entrada?
Los ataques por validación de entrada suelen surgir cuando un contrato acepta flexibilidad para integraciones o experiencia de usuario, pero no restringe esa flexibilidad al dominio soportado por el protocolo. El parámetro peligroso no siempre se llama claramente price o amount; puede ser una dirección de token, ruta, destinatario, ID de tranche, prueba, o dirección de contrato externo.
Patrones inseguros comunes incluyen:
- Valor de colateral declarado por el llamante: Un prestatario aporta el valor que utiliza para calcular su capacidad de préstamo.
- Direcciones arbitrarias de tokens: Una función de depósito acepta cualquier ERC-20, incluyendo tokens con comportamiento de transferencia no estándar.
- Direcciones de destinatarios sin verificar: Una función acepta
address(0), la misma dirección del contrato, o una dirección que viola reglas de custodia. - Porcentajes y ratios sin límites: Una comisión, LTV, descuento, slippage, o tasa de recompensa que supera su rango en puntos base previsto.
- Relaciones en arreglos sin validar: Arreglos paralelos con diferencias en longitud, entradas duplicadas aceptadas, o una ruta que repite un activo inesperadamente.
- Transiciones de estado sin precondiciones: Acción de reclamación, cancelación, redención, o migración que corre en un estado del ciclo de vida equivocado.
- Datos externos aceptados como hechos: Una función confía en precios, saldos, firmas o valores de retorno proporcionados por el llamante sin verificación previa.
La validación debe ser contextual. require(amount > 0) es útil, pero no prueba que amount sea asequible, colateralizado, dentro de un límite, o coherente con el estado actual. Asimismo, comprobar que una dirección de token no es cero no garantiza que el token esté aprobado, se comporte como se espera, o pertenezca al mercado correcto.
La metodología de revisión de Soken trata cada parámetro externo como una transición de estado intentada: identifica la invariancia que puede afectar, y luego verifica que la función imponga esa invariancia antes de escribir en almacenamiento.
¿Cómo se ve un ejemplo de ataque por validación de entrada en Solidity?
Este ejemplo de ataque por validación de entrada permite a un prestatario presentar su propio collateralValue. El contrato verifica que el préstamo solicitado no sea mayor que ese valor, pero nunca establece que el prestatario haya depositado un colateral equivalente. Un llamante puede, por tanto, configurar ambos valores en un nivel arbitrario y tomar los activos financiados por el prestamista.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
}
contract UnsafeLender {
IERC20 public immutable asset;
mapping(address => uint256) public debt;
constructor(IERC20 asset_) {
require(address(asset_) != address(0), "activo cero");
asset = asset_;
}
function borrow(
uint256 collateralValue,
uint256 amount
) external {
require(collateralValue > 0, "sin colateral");
require(amount <= collateralValue, "colateral insuficiente");
// VULNERABLE: el prestatario suministra el valor que respalda el préstamo.
debt[msg.sender] += amount;
require(asset.transfer(msg.sender, amount), "transferencia fallida");
}
}
El diseño corregido obtiene la capacidad de préstamo únicamente del colateral que realmente recibió el contrato. Las direcciones de activos inmutables funcionan como una lista blanca restringida, las verificaciones en el constructor rechazan direcciones cero, el LTV está acotado, y la verificación del delta de saldo valida la invariancia de transferencia en lugar de confiar en la cantidad de depósito solicitada.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
contract ValidatedLender {
IERC20 public immutable loanAsset;
IERC20 public immutable collateralAsset;
uint256 public immutable maxLtvBps;
mapping(address => uint256) public collateral;
mapping(address => uint256) public debt;
constructor(IERC20 loanAsset_, IERC20 collateralAsset_, uint256 maxLtvBps_) {
require(address(loanAsset_) != address(0), "activo de préstamo cero");
require(address(collateralAsset_) != address(0), "activo de colateral cero");
require(maxLtvBps_ > 0 && maxLtvBps_ <= 10_000, "LTV inválido");
loanAsset = loanAsset_;
collateralAsset = collateralAsset_;
maxLtvBps = maxLtvBps_;
}
function depositCollateral(uint256 amount) external {
require(amount > 0, "cantidad cero");
uint256 beforeBalance = collateralAsset.balanceOf(address(this));
require(collateralAsset.transferFrom(msg.sender, address(this), amount), "transferencia fallida");
uint256 received = collateralAsset.balanceOf(address(this)) - beforeBalance;
// CORREGIDO: sólo contar el colateral realmente recibido.
require(received == amount, "comportamiento no soportado del token");
collateral[msg.sender] += received;
}
function borrow(uint256 amount) external {
require(amount > 0, "cantidad cero");
uint256 limit = (collateral[msg.sender] * maxLtvBps) / 10_000;
require(debt[msg.sender] + amount <= limit, "límite de préstamo excedido");
debt[msg.sender] += amount;
require(loanAsset.transfer(msg.sender, amount), "transferencia fallida");
}
}
Si tu protocolo acepta activos, rutas o entradas de contabilidad flexibles, una auditoría de contratos inteligentes adversarios y pruebas de penetración puede rastrear si cada valor reclamado está establecido de manera independiente antes de afectar los fondos.
¿Cómo evitas ataques por validación de entrada en Solidity?
Prevenir estos ataques requiere validar las entradas en el momento de entrada y mantener las suposiciones resultantes con invariantes explícitos durante todo el ciclo de vida del contrato. La mejor defensa no es un solo modificador; es un diseño donde cada cambio de estado importante tenga un conjunto claramente definido de entradas permitidas y una condición verificable que permanezca verdadera después.
| Técnica | Lo que evita | Costo en gas o complejidad | Limitación |
|---|---|---|---|
| Listas blancas de tokens y de implementación | Tokens ERC-20 no soportados, adaptadores maliciosos, llamadas externas arbitrarias | Búsqueda en almacenamiento o comparación de direcciones inmutables | Los tokens aprobados aún pueden introducir riesgos económicos |
| Comprobaciones de límites | Montos cero, tasas excesivas, puntos base inválidos, plazos no seguros | Bajo, generalmente unas pocas comparaciones | Los límites por sí solos no garantizan corrección económica |
| Verificación de direcciones cero y destinatarios | Quemas de activos, propiedad inutilizable, destinos de custodia inválidos | Bajo, una comparación de dirección | Una dirección no cero aún puede ser no confiable |
| Autorización y verificaciones del ciclo de vida | llamadas en estado incorrecto o por actores no autorizados | Lecturas en almacenamiento y diseño de roles | Los roles requieren administración segura |
| Validación del delta de saldo | Créditos de depósito falsos y comportamiento no soportado en transferencias | Lecturas adicionales del saldo de tokens | Requiere manejo cuidadoso de activos con rebases |
| Validación de invariantes | Solvencia, relación entre suministro, deuda y reserva rota | Lecturas adicionales y aritmética | Los invariantes deben cubrir todo el modelo económico |
Para protocolos configurables, valide la configuración tanto en su creación como al ser consumida. Un parámetro definido por gobernanza puede ser seguro en la creación, pero inseguro tras un cambio en una dependencia. Para sistemas actualizables, valide los parámetros de inicialización con la misma rigurosidad que las entradas de funciones públicas.
Perspectiva de seguridad: La metodología de Soken comienza desde el invariant, no desde la sentencia
require. Si los revisores no pueden determinar qué debe permanecer verdadero tras procesar una entrada controlada por el usuario, no pueden establecer si la validación es suficiente.
¿Qué incidentes reales se clasificaron como Validación de Entrada Ausente?
La Validación de Entrada Ausente se ha asociado con pérdidas materiales en contratos inteligentes en contextos de préstamos, swaps y gestión de activos. La categoría de incidentes abarca distintas cadenas y diseños de protocolos, evidenciando que la validación inadecuada no es un error específico de Solidity, sino una falla en la lógica del protocolo.
| Protocolo | Fecha | Cantidad perdida | Cadena |
|---|---|---|---|
| Qubit | 2022-01-28 | $80.0M | BSC |
| Hedgey | 2024-04-19 | $44.7M | Arbitrum, Ethereum |
| Transit Swap | 2022-10-02 | $28.9M | Ethereum, BSC |
| Pickle | 2020-11-22 | $19.7M | Ethereum |
Estos incidentes comparten una lección central: los contratos no pueden inferir de forma segura la verdad del protocolo solo a partir de un valor porque una transacción hubiera codificado y pasado ese valor. Los sistemas de activos necesitan validaciones semánticas en torno a la propiedad específica que protege el valor, ya sea este colateral recibido, un activo soportado, una ruta autorizada o una transición válida en el ciclo de vida.
DefiLlama clasifica 52 incidentes en contratos inteligentes desde 2020 hasta 2026 bajo Validación de Entrada Ausente, con pérdidas combinadas de $330.5M. Esa amplitud explica por qué Soken trata la revisión de validación como un ejercicio de seguridad económica, no como una lista superficial de require.
¿Cómo detectar vulnerabilidades por validación de entrada incorrecta?
Puedes detectar vulnerabilidades por validación de entrada incorrecta siguiendo estas prácticas:
- Rastrear cada parámetro externo alcanzable hasta las escrituras en almacenamiento, llamadas externas y transferencias de activos que influye.
- Verificar si valores inválidos pero válidos para el ABI puedan violar un invariant.
- La análisis estático ayuda a encontrar algunas verificaciones faltantes, pero las pruebas basadas en escenarios son esenciales para las asunciones específicas del protocolo.
Utiliza esta lista de revisión:
- Enumera cada parámetro
externalypublicque influye en valores, permisos o rutas. - Verifica si cada dirección es no cero, aprobada y apropiada para su función.
- Comprueba cada monto contra límites del protocolo, saldos, límites de deuda, límites de suministro y supuestos sobre decimales.
- Confirma que precios, valores de colateral y saldos provienen de fuentes confiables o verificadas independientemente.
- Valida longitudes de arreglos, unicidad, orden y relaciones cruzadas de índices entre arreglos.
- Asegúrate de que cada función solo permita estados del ciclo de vida válidos y llamadas autorizadas.
- Prueba la contabilidad del delta de saldo ante comisiones, rebases y comportamientos inusuales en ERC-20.
- Declara y prueba la condición posterior para cada operación que mueva activos.
Slither puede identificar patrones como llamadas externas peligrosas, valores de retorno no revisados y controles de acceso débiles que a menudo rodean fallos de validación. No puede inferir todos los invariantes del protocolo, por lo que se recomienda usar fuzzing en Foundry para generar valores cero, máximos, arreglos duplicados, direcciones inesperadas y tokens no soportados.
Las pruebas de invariantes son especialmente efectivas. Por ejemplo, verificar que la deuda total pendiente nunca exceda la capacidad de préstamo configurada, que el colateral acreditado nunca supere los activos recibidos, y que solo los activos aprobados puedan modificar la contabilidad de un mercado. Explora el Soken Hub al construir un proceso más amplio de revisión de seguridad basado en esas propiedades.
La validación de entrada es la frontera entre la intención del usuario y la verdad del protocolo. Un contrato nunca debe acreditar, autorizar, valorar o transferir únicamente con base en lo que el llamante afirma. Deriva valores sensibles de un estado verificado, restringe dominios aceptados y aplica el invariant que hace que el estado resultante sea seguro desde el punto de vista económico.
El siguiente paso práctico es crear un mapa parámetro-invariante para cada función que mueva activos en tu código. Cualquier parámetro sin una fuente de confianza documentada, rango permitido o post-condición se merece una revisión inmediata.