Anchor es el marco de desarrollo más popular en el ecosistema de Solana. A través de características como la validación declarativa de cuentas, la serialización automática y controles de seguridad integrados, reduce significativamente la barrera de entrada para el desarrollo. Sin embargo, mientras el marco ofrece conveniencia, también inyecta en segundo plano instrucciones internas que los desarrolladores pueden no conocer. Estas instrucciones "ocultas" pueden ser explotadas por atacantes bajo ciertas condiciones, causando pérdidas significativas de fondos.
El equipo de seguridad de Beosin revelará un patrón de vulnerabilidad clave: en versiones antiguas de Anchor, cuando los desarrolladores utilizan AccountInfo para declarar cuentas PDA propiedad del programa, un atacante puede, mediante la instrucción IDL inyectada automáticamente por Anchor, tomar el control de la cuenta y vaciar todo el SOL en solo dos pasos, sin necesidad de obtener ningún privilegio.
I. Análisis de las instrucciones IDL y los mecanismos relacionados
1.1 Instrucción IDL
Anchor inyecta automáticamente un conjunto de instrucciones de IDL (Interface Definition Language, lenguaje de definición de interfaz) en cada programa, a menos que la característica no-idl se habilite explícitamente durante la compilación. Estas instrucciones incluyen:
IdlCreateAccount: crear una cuenta IDL en la cadena
IdlWrite: Escribir datos en la cuenta IDL / cuenta de búfer
IdlSetAuthority: Cambiar la autoridad (controlador) de la cuenta IDL
IdlCloseAccount: Cierra la cuenta IDL y transfiere todos los lamports al receptor designado
IdlResizeAccount: Ajustar el tamaño de la cuenta IDL
IdlCreateBuffer: crear una cuenta de buffer IDL (IDL Buffer)
IdlSetBuffer: Sobrescribir la cuenta IDL oficial con los datos de la cuenta de búfer
Estas instrucciones fueron diseñadas originalmente para la gestión de IDL en la cadena, pero tienen capacidades especiales sobre las cuentas poseídas por el programa (leer y escribir datos, cambiar el controlador, cerrar y transferir lamports): este es precisamente el núcleo de la vulnerabilidad. El atacante no necesita invocar ninguna instrucción de negocio escrita por el desarrollador; puede llamar directamente a estas instrucciones integradas.
1.2 Cuenta de búfer IDL
La cuenta de búfer IDL (IDL Buffer) es una cuenta temporal introducida por Anchor para cargar datos IDL grandes en segmentos. Dado que el IDL completo (comprimido en JSON) puede exceder el límite de tamaño de una sola transacción, Anchor permite crear primero un búfer mediante IdlCreateBuffer, escribirlo por partes con múltiples operaciones IdlWrite y finalmente enviarlo de forma integral mediante IdlSetBuffer.
El punto clave es la estructura de datos de la cuenta IDL / cuenta de búfer: comienza con una cabecera de diseño fijo que contiene un campo authority: Pubkey (denominado controller en la salida de prueba de este artículo). Las instrucciones IDL utilizan este campo para determinar "quién tiene permiso para operar en esta cuenta".
Pero el problema es que la lógica de procesamiento de IdlCreateBuffer en versiones antiguas de Anchor trata cualquier cuenta pasada, propiedad del programa actual, como una cuenta de búfer y establece directamente la autoridad como el firmante de la transacción. Es decir, siempre que el propietario de una cuenta sea el programa actual (por ejemplo, una PDA de tesorería del programa), un atacante puede asignarse a sí mismo como controlador y, a continuación, transferir legítimamente todos los SOL de la cuenta utilizando IdlCloseAccount.
1.3 Condiciones para activar la vulnerabilidad
Para activar esta vulnerabilidad, deben cumplirse simultáneamente las siguientes condiciones:
- Usar una versión antigua de Anchor: la instrucción IDL no realiza una verificación adecuada de las cuentas, lo que permite tratar incorrectamente una cuenta de negocio como una cuenta IDL.
- no-idl no está habilitado: el programa conserva la entrada de instrucciones IDL inyectada por defecto durante la compilación, exponiendo la superficie de ataque.
- Declarar el PDA poseído por el programa con AccountInfo: los desarrolladores usan AccountInfo sin formato para cargar cuentas de fondos (como el PDA de la caja fuerte), careciendo del discriminador / propietario incorporado de las cuentas tipificadas de Anchor (como Account), lo que hace que esta cuenta sea indistinguible de las cuentas del IDL según la instrucción del IDL.
- La cuenta es propiedad del programa y posee lamports: owner == este programa es la condición previa para que las instrucciones del IDL puedan operarla; solo tiene valor para ser vaciada si posee SOL.
Después de cumplir con las condiciones anteriores, el atacante solo necesita dos transacciones comunes: primero IdlCreateBuffer para tomar el control, luego IdlCloseAccount para transferir todos los SOL, completando así el ataque sin necesidad de que el programa objetivo otorgue ningún permiso.
1.4 Cadena de ataque
A continuación, combinando la implementación interna de Anchor, se desglosa paso a paso cómo el atacante utilizó solo dos instrucciones integradas para “fingir” que un fondo de fondos común era una cuenta IDL y vaciarla.
Vista general del proceso de ataque
Paso 1: IdlCreateBuffer(treasury, signer = attacker)
└─ treasury.controller ==> attacker (se convierte en controlador)
Paso 2: IdlCloseAccount(treasury, authority = attacker, dest = attacker)
└─ treasury.lamports ==> attacker (agota la tesorería)
Paso 1: IdlCreateBuffer toma la autoridad
Anchor implementa internamente aproximadamente lo siguiente:
#[derive(Accounts)]pub struct IdlCreateBuffer {
#[account(zero)] // ← Key: una cuenta con su discriminador todos 0 pub buffer: Account,
pub authority: Signer,}
pub fn idl_create_buffer(ctx: Context) -> Result
{
let idl = &mut ctx.accounts.buffer; idl.authority = *ctx.accounts.authority.key; // establecer attack como autoridad Ok(())}
El significado de #[account(zero)] es: aceptar una cuenta cuyo discriminator esté compuesto completamente por ceros y que sea poseída por el programa, como una cuenta IDL no inicializada para inicializarla. Y vault cumple exactamente con ambas condiciones:
Estado del vault
Condiciones
propiedad del programa
Después de init_if_needed, el propietario se establece como este programa.
discriminator todos ceros
Al usar el tipo AccountInfo, Anchor no escribe el discriminator, todos los datos son ceros.
Entonces el atacante pasó el vault a IdlCreateBuffer después:
- Anchor escribe el discriminator de IdlAccount en los primeros 8 bytes del vault;
- Escribir la clave pública del atacante en el campo authority.
Hasta ahora, el vault se ha "fingido" como un IdlAccount con el atacante como autoridad, y sus fondos de SOL permanecen intactos.
Paso 2: IdlCloseAccount —— Limpiar fondos
#[derive(Accounts)]pub struct IdlCloseAccount { #[account(mut, has_one = authority)] // ← check authority == signer pub account: Account, pub authority: Signer, #[account(mut)] pub destination: AccountInfo, // ← attack wallet}
El vault ya ha sido completamente verificado:
- discriminator coincide con IdlAccount
- El campo authority = clave pública del atacante
- has_one = authority verificación aprobada
Por lo tanto, todos los lamports dentro del vault (todo el SOL depositado por el usuario) se transfirieron legalmente a la cuenta del atacante, dejando el vault en cero.
Causa raíz:
Usar AccountInfo en deposit.rs en lugar de una Cuenta Anchor tipada es la causa fatal de esta vulnerabilidad:
(1) Las cuentas con tipo (como Account) escriben al inicializarse un discriminator de 8 bytes exclusivo de esa estructura;
(2) Una vez que se escribe el propio discriminator, Anchor ya no puede tratarlo como IdlAccount (el discriminator no coincide), y el primer paso, IdlCreateBuffer, fallará, rompiendo la cadena de ataque.
Dos: Análisis de casos
Las pruebas se basan en el contrato PoC del puente construido con Anchor 0.31.0. La prueba simula un tesoro de puente real (Bridge Treasury), cuyo propietario es el propio programa de puente, que contiene 1.001281 SOL depositados por los usuarios. La billetera del atacante inicialmente posee 2 SOL y no tiene ningún privilegio.
Proyecto
Valor / Descripción
Programa de puente (Bridge Program)
CJutNlr8d3oxdb11ReOLaZd5jPsqUxgwt2mv5e7equtE
Cuenta del tesoro (Treasury)
DxGkzaMhP5Wy824GhoehErdD6MudfuEPGPwaV2s77FsM
Propietario de la Caja Fuerte
= Programa propiedad de PDA
Saldo inicial de la caja fuerte
1.001281 SOL (fondos depositados por el usuario)
Controlador inicial
0x0000...0000 (todos ceros, no configurado)
Saldo inicial de la billetera del atacante
2.000000 SOL
Privilegios requeridos
NINGUNO (sin autorización necesaria)
Número de transacciones
2 operaciones
Saldo final de la bóveda
0.000000 SOL (vaciado)
El atacante obtiene ganancias
+1.001281 SOL
Captura de pantalla de la ejecución de PoC (tests/poc-idl-hijack.ts):
Se puede ver que IdlCreateBuffer en el Paso 1 establece al atacante como controlador del depósito (el saldo no cambia); IdlCloseAccount en el Paso 2 transfiere los 1.001281 SOL del depósito completamente a la billetera del atacante, dejando el saldo del depósito en cero.
Recomendaciones de reparación/protección:
(1) Actualización de la versión de Anchor: la versión más reciente soluciona este problema (las instrucciones IDL distinguen estrictamente entre cuentas IDL y cuentas de negocio); evitar el uso de versiones antiguas de Anchor es el método más directo y fundamental de defensa.
(2) Habilitar no-idl durante la compilación: desactivar explícitamente la inyección de instrucciones IDL para programas de producción, eliminando así esta superficie de ataque desde su origen.
(3) Utilice cuentas tipificadas en lugar de AccountInfo sin procesar: utilice cuentas tipificadas como Account / SystemAccount con verificación de discriminator y owner para alojar cuentas de fondos, evitando que sean malinterpretadas por instrucciones IDL.
(4) Minimizar las cuentas que puede vaciar el programa: agregar restricciones explícitas de owner / seeds / discriminator para el PDA que almacena fondos, y verificar el discriminador de la cuenta en instrucciones clave.
Conclusión
La vulnerabilidad radica en la combinación de “instrucciones ocultas del marco + falta de verificación del tipo de cuenta”. Los desarrolladores deben reconocer que las instrucciones IDL inyectadas por defecto por Anchor constituyen una superficie de ataque real; actualizar la versión del marco y aplicar restricciones de tipo estricto a las cuentas de fondos puede eliminar eficazmente este riesgo de vaciado no autorizado de fondos.
Beosin es una empresa líder en tecnología de seguridad blockchain y cumplimiento normativo, enfocada en auditorías de seguridad de contratos inteligentes antes del lanzamiento de proyectos, monitoreo y bloqueo de riesgos de seguridad durante la operación del proyecto, recuperación de activos robados, prevención de lavado de dinero (AML) para activos virtuales, e investigación y rastreo. Beosin ha proporcionado productos y servicios de cumplimiento blockchain “todo en uno” a más de 200 proveedores de activos virtuales y 4.500 proyectos Web3 en más de 20 países y regiones del mundo. ¡Contáctenos dejando un mensaje en el cuadro de comentarios de nuestro canal oficial!

