Resumen de gestión
El equipo de ZenPool se puso en contacto con Sayfer Security para realizar una auditoría de seguridad completa para todos sus contratos.
Antes de evaluar estos servicios, llevamos a cabo una reunión inicial con el equipo técnico de ZenPool y recibimos una descripción general del sistema y los objetivos de esta evaluación.
La siguiente auditoría tardó 20 días-hombre en completarse, todos los contratos en cadena se revisaron línea por línea con al menos 2 auditores por contrato. Debido a las limitaciones de tiempo del lado del cliente, revisamos los componentes fuera de la cadena y tomamos un enfoque de mejor esfuerzo para asegurarnos de que no contengan vulnerabilidades críticas.
Encontramos un total de 9 hallazgos, 3 de los cuales se clasificaron como vulnerabilidades de riesgo "alto" y podrían ser aprovechadas por atacantes maliciosos, que podrían vaciar completamente los fondos del grupo.
Documentamos nuestro proceso y nuestras sugerencias sobre cómo se debe solucionar cada vulnerabilidad. El equipo de ZenPool implementó las correcciones que se documentaron en cada sección de vulnerabilidad.
Descripción general del sistema
ZenPool es un protocolo de mercado de préstamo y token sin custodia de código abierto.
Los usuarios pueden depositar sus criptoactivos para ganar intereses o pedir prestados otros tokens para pagar intereses en el mercado de ZenPool. ZenPool tiene su propio token llamado ZEN.
ZEN es una moneda de flotación libre respaldada por el suministro de tesorería de la moneda estable BUSD. Los tokens ZEN solo pueden ser acuñados y quemados por el protocolo, solo en respuesta al precio lo hace el protocolo. Cada ZEN está respaldado por al menos un BUSD.
Si el precio de ZEN cae por debajo de 1 BUSD, el protocolo compra y quema ZEN, lo que hace que el precio vuelva a subir a 1 BUSD.
ZenPool también admite bonos, que son otra forma de aumentar su tesorería. El protocolo vende bonos a cambio de varios activos y, a cambio, el comprador recibe ZEN a un precio significativamente reducido. Esto aumenta la tesorería y permite que ZeenPool brinde rendimientos increíbles a sus clientes.
Por último, una parte de todas las tarifas de los productos de ZenPool se utilizará como respaldo adicional, lo que podría proporcionar al token de ZenPool una pista infinita para recompensas de participación.
Vulnerabilidades por Riesgo
Alta
- DoS de transacciones
- pérdida directa de fondos
- congelamiento permanente de fondos
Medio
- Ataques contra clientes ligeros
- Parcialmente DoS
- Ataques de gas
Baja
- Mejores prácticas
Informativo
- No daña el sistema, y no tenemos suficientes datos o conocimientos para demostrar que alguna vez hará daño, sin embargo, es importante compartir nuestra
Alcance y Contratos
Como parte del alcance del proyecto y para comprender el enfoque y las necesidades de nuestros clientes, definimos los contratos que debemos probar en esta auditoría. Juntos encontramos el mejor equilibrio que se adapta a este proyecto específico.
El alcance es un alcance suave por definición, lo que significa que podríamos probar en un entorno local otros contratos que podrían estar interactuando con el contrato que se define como el alcance de la auditoría. Esto nos brinda la flexibilidad para encontrar problemas de seguridad que de otro modo podrían pasarse por alto.
La lista de contratos y su compromiso de Github:
Confirmar hash
Confirmar hash | Contrato |
5d253cb3c544a17399e7d2f4c381a1065bcfa0a0 | https://github.com/zenpoolproject/zenpool/tree/master/contracts/createGovernor.sol |
3887cd307163d130477d4805e74ade11f84d7a4d | https://github.com/zenpoolproject/zenpool/tree/master/contracts/ZenPoolUserManager.sol |
a50062dfc63a0e82ec43de98865420193270236a | https://github.com/zenpoolproject/zenpool/tree/master/contracts/ZenPoolManager.sol |
c4d0db2f9fbfc3146a1a1a797e55c3f3d7a19899 | https://github.com/zenpoolproject/zenpool/tree/master/contracts/ZenTickets.sol |
844132ae5210cb27101bf4a415d9c16e201e1afc | https://github.com/zenpoolproject/zenpool/tree/master/contracts/ZenPoolFunds.sol |
a70146c5d276f933a79c567d2e96512302a6a940 | https://github.com/zenpoolproject/zenpool/tree/master/contracts/ZenGovernorAlpha.sol |
d476137af592fe71cae2578a62e2f8f92b335d8c | https://github.com/zenpoolproject/zenpool/tree/master/contracts/ZenLendingPool.sol |
8c5d858ad7fbfe856cd23160fd8810997f3fdf70 | https://github.com/zenpoolproject/zenpool/tree/master/contracts/ZenGovernorAlpha.sol |
Ordenar auditoría de Sayfer
Hallazgos de auditoría de contratos inteligentes
Los usuarios pueden retirar fondos de otros saldos hasta que el grupo esté vacío
Contrato | contratos/ZenPoolFunds.sol |
Riesgo | Alta |
fijo | Sí |
Encontrado por | Prueba manual |
Descripción
El withdrawFunds
La función valida que el usuario solo puede retirar la cantidad máxima en su saldo, verificado por su dirección y luego comparado dentro del conjunto de tokens.
Posteriormente, el monto retirado se deduce del préstamo máximo actual del usuario al precio actual. Si la cantidad total de fondos prestados por el usuario supera el nuevo préstamo máximo, el método falla porque el usuario ya no tiene suficiente garantía para respaldar su posición de préstamo. Sin embargo, este requisito solo se verifica si el usuario aún no está sobreapalancado:
Un atacante podría usar esta funcionalidad para explotar la withdrawFunds
función para retirar más que el monto máximo de préstamo.
Él podría hacer eso porque el getMaxBorrow
solo se verificará si el usuario pide prestado menos de la cantidad máxima que se le permite, en una variedad de escenarios, el atacante podría abusar del flujo para omitir este específico require
, permitiéndole llamar al withdrawFund
varias veces hasta que la piscina se vacíe.
Mitigación
Cambie el require
para ser llamado antes de la línea 145 por lo que no depende de la instrucción if. De esta manera el require
siempre se ejecutará.
Autodestrucción insegura en contrato de proxy
Contrato | contratos/ZenTickets.sol contratos/ZenPoolManager.sol |
Riesgo | Alta |
fijo | Sí |
Encontrado por | Prueba manual |
Descripción
Cuando el principal ZenPoolManager
se implementa, también implementa varios contratos.
Uno de ellos es el ZenTickets
contrato que está garantizado en su mayor parte, a excepción de la destoryContract
función que no tiene un mecanismo ACL como el resto de las funciones:
Al explotar esta funcionalidad, un atacante no autenticado podría llamar al destoryContract
función y elegir a dónde se transferirá el ETH del contrato. Esto es fácil de explotar desde la perspectiva del atacante.
Mitigación
Ingrese al onlyOwner
modificador como el que se usa en el reinicio de las funciones.
Otra solución más específica sería usar un mecanismo de ACL personalizado como el ACL de roles de openzeppelin que solo permitirá que roles específicos accedan al destoryContract
funciones
Los grupos de usuarios están sujetos a ataques MEV
Contrato | contratos/ZenPoolUserManager.sol |
Riesgo | Alta |
fijo | No – Riesgo Tomado |
Encontrado por | Prueba manual |
Descripción
El principal ZenPoolUserManager
está manejando grupos de usuarios personalizados que se han creado en el sistema. El usuario puede llamar a diferentes acciones en estos grupos si tiene los permisos correctos para hacerlo, un usuario también puede otorgar acceso a través del mecanismo de roles basado en los contratos de control de acceso de OpenZeppelin.
El ZenPoolUserManager
tiene un mecanismo ACL adecuado, pero al mismo tiempo, tiene 2 funciones que están sujetas a ejecución frontal/posterior o cualquier ataque MEV.
Si bien la lógica comercial en sí es correcta y parece que no puede hacer mucho daño ya que tiene una ACL adecuada, un usuario con el mismo rol podría explotarla a través de ataques MEV.
Mitigación
Ingrese al onlyOwner
modificador como el que se usa en el reinicio de las funciones.
Otra solución más específica sería usar un mecanismo de ACL personalizado como el ACL de roles de openzeppelin que solo permitirá que roles específicos accedan al destoryContract
funciones.
Falta de mitigación de guardianes
Contrato | contratos/ZenGovernorAlpha.sol |
Riesgo | Medio |
fijo | fijo |
Encontrado por | Análisis de código heredado y pruebas manuales |
Descripción
Durante nuestra auditoría, realizamos un análisis del código original y comparamos el código base actual del cliente con el
proyectos de código abierto que el cliente utilizó para desarrollar el código. Si descubrimos que se realizaron cambios en el código de fuente abierta, los analizamos más a fondo para asegurarnos de que se hicieran de manera inteligente.
La mayoría de los clientes consideran seguros los proyectos de código abierto de terceros porque provienen de los principales proveedores (grupo EG Uniswap). Esta suposición es mayormente cierta. Sin embargo, los errores de seguridad importantes ocurren cuando los clientes realizan cambios menores en el código y asumen que estos cambios no afectan la seguridad general del producto.
Durante nuestro análisis de código original, encontramos que ZenPool usa el código del protocolo STRIKE para crear un token de gobernanza, el código original del repositorio STRIKE es:
Mientras que el código en ZenGovernorAlpha es:
Se eliminó la siguiente declaración msg.sender == guardian
, esto significa que Guardian no puede cancelar pedidos si se alcanza un umbral. Eliminar el poder de los guardianes puede provocar situaciones muy peligrosas. Por ejemplo, si se realizó una propuesta comprometida que transfiere todo el dinero del contrato a un actor malicioso (mediante un hack de clave privada, por ejemplo), el mecanismo de protección de umbral es redundante y no puede ayudar, porque no hay un guardián que pueda detener la propuesta. de suceder.
Mitigación
Añada msg.sender == guardian
En el correo electrónico “Su Cuenta de Usuario en su Nuevo Sistema XNUMXCX”. require
.
Falta de guardia de reentrada
Contrato | contratos/ZenLendingPool.sol |
Riesgo | Medio |
fijo | fijo |
Encontrado por | Pruebas deslizantes y manuales |
Descripción
La siguiente función de préstamo no contiene una protección de reentrada ni el patrón de comprobaciones-efectos-interacciones, esto actualmente no es explotable ya que el grupo solo admite tokens ERC20, pero si en el futuro se agregará el nuevo ERC-777, puede ocurrir una vulnerabilidad de reentrada y causar el retiro total de los fondos del contrato.
Además, el código no sigue el patrón controles-efectos-interacciones. En este caso específico, el require
se verifica después de que ya se haya implementado alguna lógica de negocios, esto podría causar errores de reingreso si se agregarán efectos secundarios antes del require
.
Mitigación
Agregue una protección de reingreso para el reingreso de la misma función y también use el patrón de controles-efectos-interacciones para el reingreso complejo de funciones cruzadas.
No permitir transferencias de monto cero
Contrato | contratos/ZenPoolFunds.sol |
Riesgo | Baja |
fijo | Sí |
Encontrado por | Prueba manual |
Descripción
El ZenPoolFunds.sol
El contrato permite transferencias de importe cero entre usuarios. Si bien por sí mismo no es una vulnerabilidad de seguridad, esta es una muestra de código que puede expandir el vector de ataque de un atacante malicioso.
La vulnerabilidad existe porque transfer
en ZenPool emiten eventos, haciendo una cantidad cero transfer
un atacante podría emitir múltiples eventos que, en algunos escenarios, pueden desencadenar una lógica comercial fuera de la cadena, sin siquiera tener un token en el grupo.
Mitigación
Se necesita una comprensión más profunda del negocio. Si es posible, elimine esta funcionalidad.
Si hay casos de uso en los que tiene sentido usar transferencias de monto cero, implemente otra capa de controles para limitar el uso a los usuarios que forman parte de este grupo.
Registro insuficiente para funciones privilegiadas
Contrato | contratos/ZenGovernorAlpha.sol |
Riesgo | Info |
fijo | Sí |
Encontrado por | Prueba manual |
Descripción
Los siguientes privilegiados onlyOwner
La función no emite eventos.
Los eventos son una práctica común para funciones privilegiadas para anunciar al público que se ha realizado un cambio. Sin eventos, los cambios son más difíciles de detectar y los usuarios se sorprenden con el comportamiento del contrato.
El propietario puede llamar al chainId
ejecutando setChainId()
y modificando el chaindId
parámetro sin que se emita ningún evento en el proceso.
Mitigación
Agregue código que emita eventos.
Comprobación de condiciones redundantes
Contrato | contratos/createGobernador.sol |
Riesgo | Info |
fijo | Sí |
Encontrado por | Prueba manual |
Descripción
Al crear un nuevo token de gobernador, el _timelockDelay
el parámetro se está validando dos veces. una vez en todas las comprobaciones cuando se inicia la función y luego como parte de otra require
, esto es redundante y debe eliminarse.
El código duplicado requiere más gas para ejecutarse y puede generar errores futuros cuando cambia la lógica empresarial.
Mitigación
Eliminar el segundo redundante require
.
Inconsistencia de nombres
Contrato | – Múltiple – |
Riesgo | Info |
fijo | riesgo asumido |
Encontrado por | Pruebas deslizantes y manuales |
Descripción
Hay múltiples ocurrencias de diferentes estilos de mayúsculas (minúsculas camello, serpiente, etc.).
Mitigación
Revise el código para ver el estilo del código.
Agregue una guía de estilo de código y ejecútela mediante una tarea de CI.