Autor: Xiao Bai
Editar: Liz
Resumen del contexto
En el ecosistema de Ethereum, el mecanismo de generación determinista de direcciones de contrato proporciona conveniencia a los desarrolladores, pero también introduce nuevas superficies de ataque. En esta edición analizaremos las tácticas de ataque y estrategias de defensa a través del uso de los códigos de operación CREATE y CREATE2 para desplegar diferentes contratos en la misma dirección en diferentes momentos. Artículos anteriores sobre auditoría de seguridad de contratos inteligentes pueden encontrarse en la colección.
Conocimientos previos
Primero entendamos las dos reglas para la generación de direcciones en Ethereum:
1. CREATE
CREATE es el código de operación nativo utilizado para desplegar dinámicamente contratos inteligentes en la Máquina Virtual de Ethereum (EVM). Desde el bloque génesis de Ethereum, todos los despliegues de contratos dependen de este mecanismo. Su característica central es que la generación de direcciones depende del nonce de la cuenta del desplegador, por lo tanto, la dirección es no determinista (no se puede predecir con precisión antes del despliegue).
La dirección del contrato generada por CREATE depende de la dirección del desplegador y del nonce de la dirección:

2. CREATE2
CREATE2 es el nuevo código de operación de creación de contratos introducido en el hard fork de Constantinopla de Ethereum (febrero de 2019). A diferencia de CREATE tradicional, antes del despliegue del contrato, los participantes fuera de la cadena pueden calcular de antemano la dirección del contrato, lo que hace posible la interacción fuera de la cadena (como los canales de estado) y arquitecturas de contratos complejas.
La dirección del contrato generada por CREATE2 se determina por los siguientes cuatro parámetros:

Hasta aquí, creo que todos ya entienden las dos maneras de generar direcciones de contrato en Ethereum. Los lectores más atentos pueden preguntarse, ¿qué pasa si la dirección de contrato calculada ya existe? No hay necesidad de preocuparse, en Ethereum, ya sea a través de CREATE o CREATE2, si la dirección ya existe en la cadena (ya sea una cuenta externa EOA o una cuenta de contrato), EVM rechazará la solicitud de creación del contrato. A continuación se presentan dos escenarios de conflicto de dirección:
1. La dirección objetivo es una cuenta externa (EOA)
Regla: Si la dirección objetivo es una EOA existente (por ejemplo, la dirección de la billetera del usuario), EVM rechazará la solicitud de despliegue del contrato.
Resultado: Transacción fallida, el Gas se consume, el contrato no se crea y no se sobrescriben los datos de esa dirección.
2. La dirección objetivo es una cuenta de contrato
Regla: Si la dirección objetivo es una cuenta de contrato ya desplegada, EVM también rechazará la solicitud de despliegue.
Resultado: Transacción fallida, el Gas se consume, el código del contrato original y los datos de almacenamiento permanecen sin cambios.
Por supuesto, hay excepciones; si la dirección objetivo es un contrato y ha sido destruido por selfdestruct, en este momento se puede redeplegar un nuevo contrato en esa dirección.
Hasta aquí, se han presentado varias características de los códigos de operación CREATE y CREATE2, ahora veamos cómo utilizar las características de estos dos códigos para lanzar una combinación de ataques y completar un ataque de contrato.
Ejemplo de vulnerabilidad

Análisis de vulnerabilidades
Este contrato DAO implementa un mecanismo básico de gobernanza: el Owner revisa y registra la dirección del contrato de propuesta en el array Proposals a través de la función approve, cualquier usuario puede luego ejecutar la propuesta revisada a través de la función execute. Aunque parece haber un control de permisos sólido (solo el Owner puede revisar propuestas) combinado con una verificación de ejecución (la propuesta debe ser revisada y no ejecutada), en realidad existe un vacío lógico oculto: la dirección de la propuesta revisada puede apuntar a un código de contrato completamente diferente al ejecutarse. El atacante puede llevar a cabo el ataque en tres pasos:
1. Despliega un contrato normal, obtén autorización
El atacante primero despliega un contrato A que contiene una función executeProposal() inofensiva, y a través de la revisión del Owner, agrega la dirección a la lista de propuestas.
2. Autodestruir el contrato original, ocupar la dirección
El contrato A ejecuta la operación de autodestrucción (selfdestruct) para vaciar el código, y luego el atacante utiliza el código de operación CREATE2 para desplegar el contrato malicioso B en la misma dirección (que contiene la lógica peligrosa de executeProposal()).
3. Disparar la ejecución, tomar el control
Cuando el usuario llama a execute, el contrato ejecutará el código del contrato malicioso B recién desplegado a través de delegatecall. Dado que delegatecall preserva el contexto actual del contrato, el atacante puede modificar el estado del contrato DAO (como cambiar el Owner) o transferir activos a través de esta operación.
A continuación, combinaremos el contrato de ataque para ver el flujo de ataque específico.
Contrato de ataque

El flujo de ataque es el siguiente:
1. Alice desplegó el contrato DAO.
2. Evil despliega el contrato DeployerDeployer, con la dirección DD.
3. Evil llama a DD.deploy(), utilizando CREATE2 para desplegar Deployer en la dirección D (salt fijo).
4. Llama a D.deployProposal(), crea la dirección del contrato Proposal P, en este momento el nonce de D es 0.
5. Alice aprueba la dirección P.
6. El atacante llama a D.kill(), destruye D, en este momento la cuenta de la dirección D es eliminada, incluido el nonce que también se restablece a 0.
7. El atacante llama nuevamente a DD.deploy(), redeplegando Deployer en la dirección D (los mismos parámetros de CREATE2).
8. Llama a D.deployAttack(), porque en este momento el nonce de D se ha restablecido a 0 y la dirección de D no ha cambiado, por lo que la dirección del contrato Attack creado es la misma que la dirección del contrato Proposal anterior P.
9. En este punto, hay una propuesta en el array proposals del DAO que apunta a la dirección P, pero ahora la dirección P ha sido redeplegada como el contrato Attack. Por lo tanto, cuando se llama a execute(), se ejecutará executeProposal() de Attack, modificando finalmente el Owner del DAO a msg.sender, que es el contrato Attack.
El principio del ataque se puede resumir en que el atacante utiliza la característica de CREATE2 para redeplegar un contrato en la misma dirección, primero despliega un contrato Proposal legítimo y lo hace aprobar por el DAO. Luego destruye el contrato Deployer, redepliega el Deployer en la misma dirección, restablece el nonce de esa dirección y finalmente despliega con éxito un contrato malicioso en la misma dirección que la propuesta anterior. Dado que la dirección de propuesta almacenada en el DAO ahora apunta al contrato malicioso, al ejecutar la propuesta, el código malicioso se ejecuta en el contexto del DAO, modificando con éxito el Owner al atacante.
Sugerencia de reparación
Como desarrollador:
Al aprobar la propuesta, no solo se debe registrar la dirección, sino también el hash del código de esa dirección, y verificar la consistencia del hash del código al ejecutarla.
Evita usar delegatecall para llamar contratos externos, a menos que haya medidas de seguridad adecuadas.
Considera la posibilidad de redeplegar el contrato en la misma dirección después de la autodestrucción, al realizar llamadas externas, verifica si el contrato externo es confiable, si necesitas llamar a un contrato externo desconocido, se recomienda verificar si hay mecanismos de prevención de autodestrucción en el contrato.
Como auditor:
Identifica si hay una función de autodestrucción selfdestruct en el contrato de llamada externa, si existe, se debe tener cuidado con el riesgo de ataque de que un contrato externo se autodestruya y despliegue código malicioso en la misma dirección.
Verifica si el método de despliegue del contrato utilizó CREATE2, asegurando que el valor de salt utilizado para generar la dirección del contrato sea lo suficientemente aleatorio, para prevenir que la dirección del contrato sea ocupada por un atacante que la despliegue primero.
Verifica si el DAO valida la consistencia del código de la dirección objetivo al ejecutar propuestas, como comparar el hash del código actual con el hash aprobado.
Analiza cuidadosamente los escenarios de uso de delegatecall, confirmando si la dirección objetivo es confiable y si existe el riesgo de que se llame a cualquier dirección.
Revisa la gestión del ciclo de vida de las propuestas, como si hay mecanismos para prevenir que las propuestas sean modificadas o reemplazadas, por ejemplo, bloqueando el estado de la dirección objetivo una vez aprobada.
