Beosin revela una vulnerabilidad crítica en la instrucción IDL en la versión antigua del marco Solana Anchor

iconMetaEra
Compartir
AI summary iconResumen
El equipo de seguridad de Beosin ha identificado una vulnerabilidad crítica en la instrucción IDL en marcos Solana Anchor de versiones antiguas. La falla permite a los atacantes secuestrar cuentas PDA propiedad de programas mediante la explotación de declaraciones AccountInfo, facilitando el robo de fondos en dos pasos sin necesidad de privilegios de usuario. El incidente subraya la necesidad de un marco de cumplimiento más sólido en el desarrollo de contratos inteligentes. Con la aproximación de MiCA (Regulación de Mercados de Activos Criptográficos de la UE), tales vulnerabilidades enfatizan la importancia de auditorías de seguridad en tiempo real y el cumplimiento de normas regulatorias.

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):

https://wdcdn.qpic.cn/MTMxMDI3MDE1MTgxMDU0NzA_812435_a37-yrU_dY02i1_I_1785900257?w=1080&h=1287

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!

Descargo de responsabilidad: La información contenida en esta página puede proceder de terceros y no refleja necesariamente los puntos de vista u opiniones de KuCoin. Este contenido se proporciona solo con fines informativos generales, sin ninguna representación o garantía de ningún tipo, y tampoco debe interpretarse como asesoramiento financiero o de inversión. KuCoin no es responsable de ningún error u omisión, ni de ningún resultado derivado del uso de esta información. Las inversiones en activos digitales pueden ser arriesgadas. Evalúa con cuidado los riesgos de un producto y tu tolerancia al riesgo en función de tus propias circunstancias financieras. Para más información, consulta nuestras Condiciones de uso y la Declaración de riesgos.