Guía para la auditoría automática de contratos inteligentes. Parte 2: Slither

Analizador: Slither
Descripción: Marco de análisis estático de código abierto para Solidity
githib: https://github.com/trailofbits/slither


Este es un analizador de código estático escrito en python. Él sabe cómo monitorear variables, llamadas y detecta una lista de vulnerabilidades . Cada vulnerabilidad tiene un enlace con una descripción, y si eres nuevo en Solidity, tiene sentido que conozcas a todos.


Slither puede funcionar como un módulo de Python y proporcionar al programador una interfaz para auditar de acuerdo con su propio plan. Aquí se puede ver un ejemplo simple e ilustrativo de lo que puede hacer slither .


Volveremos a los escenarios de análisis al final del artículo, pero por ahora ejecutamos Slither:


git clone https://github.com/trailofbits/slither.git cd slither docker build -t slither . 

e intenta analizar nuestro contrato.


Entramos en el directorio con constructor-eth-booking e intentamos ejecutar slither desde la ventana acoplable:


 $ docker run -v $(pwd)/contracts:/slither/contracts slither contracts/flattened.sol 

obtenemos el error "El archivo de origen requiere una versión diferente del compilador", y ahora tenemos que poner la versión de solc=0.4.20 en el solc=0.4.20 . Para hacer esto, edite el Dockerfile de Slither en sí, como se indicó en la Sección 2 de la introducción , es decir. en algún lugar al final del Dockerfile agregamos la línea:


 COPY --from=ethereum/solc:0.4.20 /usr/bin/solc /usr/bin 

, reconstruir la imagen, correr, aplausos, todo se compila.


Vemos el resultado, advirtiendo sobre diferentes "pragmas" y sobre nombres de variables incorrectos como el Parameter '_price' of Booking.Booking (flattened.sol#73) is not in mixedCase . Los analizadores dan muchas advertencias, pero estamos buscando errores reales y no prestaremos atención a la bagatela. Filtramos todos los mensajes sobre MixedCase, ahora no depende del estilo:


 $ docker run -v $(pwd)/contracts:/slither/contracts slither contracts/flattened.sol 2>&1 | fgrep -v 'mixedCase' 

Los verdaderos programadores extrañan todo lo verde, lo ven todo rojo, y esto es lo que Slither encontró en el contrato además de los falsos positivos:


 Booking.refundWithoutCancellationFee (flattened.sol#243-250) sends eth to arbirary user Dangerous calls: - client.transfer(address(this).balance) (flattened.sol#249) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations INFO:Detectors: Booking.refundWithCancellationFee (flattened.sol#252-259) sends eth to arbirary user Dangerous calls: - owner.transfer(m_cancellationFee) (flattened.sol#257) - client.transfer(address(this).balance) (flattened.sol#258) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations 

Ahora mire lo que está mal con esta función en el contrato:


  /************************** PRIVATE **********************/ function refundWithoutCancellationFee() private { address client = m_client; m_client = address(0); changeState(State.OFFER); client.transfer(address(this).balance); } function refundWithCancellationFee() private { address client = m_client; m_client = address(0); changeState(State.CANCELED); owner.transfer(m_cancellationFee); client.transfer(address(this).balance); } 

en este caso, por ejemplo, la función refundWithoutCancellationFee() se llama así:


  function rejectPayment() external onlyOwner onlyState(State.PAID) { refundWithoutCancellationFee(); } function refund() external onlyClient onlyState(State.PAID) { refundWithoutCancellationFee(); } 

Hmm, formalmente no hay errores: las llamadas están protegidas por todo tipo de onlyOwner , pero Slither jura que, dicen, la transmisión se envía sin ningún tipo de cheque dentro del reembolsoWithoutCancellationFee (). Y tiene razón, la función en sí misma casi no tiene restricciones. Deje que sea privado, y se llama desde los envoltorios "rechazarPago ()" "reembolso ()" con las restricciones necesarias, pero de esta forma, si modifica el contrato, existe un gran riesgo de olvidar las restricciones y pegar el refundWithoutCancellationFee() llamar a la llamada de refundWithoutCancellationFee() a otro lugar, disponible para el atacante. Por lo tanto, incluso si formalmente no hay vulnerabilidad, la información resultó ser útil: este es al menos un nivel de "advertencia", si la asignación planea desarrollar más el código del contrato. En este caso, dos funciones de diferentes participantes usan el mismo código, y esta decisión se tomó para ahorrar gas: el contrato es único y el costo de su cálculo es un factor importante.


Volví a comprobar que Slither está maldiciendo por cualquier transmisión, y transfirí el cuerpo de la función directamente al "rechazoPago") y "reembolso ()", la advertencia desapareció, es decir, Slither se dio cuenta de que ahora la transmisión no se envía sin verificación de dirección. Gran comienzo!


Ahora veamos cómo Slither monitorea la inicialización de variables, para esto comentamos dos inicializaciones:


 - m_fileHash = _fileHash; + // m_fileHash = _fileHash; - m_price = _price; + // m_price = _price; 

el primero no es muy importante, en términos de agujeros, excepto por el desperdicio de recursos, porque m_fileHash no se usa en ningún lado, simplemente se almacena en la cadena de bloques al crear un contrato. Pero se usa m_price, y Slither jura correctamente que m_price no se inicializa en ninguna parte, aunque se usa:


 Booking.m_price (flattened.sol#128) is never initialized. It is used in: - fallback (flattened.sol#144-156) 

Bueno, este es un truco simple, como se esperaba, todo funcionó bien.


Ahora agregaremos al contrato reentranc tan querido por todos: cambiaremos el estado del contrato después de una llamada externa. Hacemos los siguientes cambios:


  function refundWithoutCancellationFee() private { address client = m_client; - m_client = address(0); - changeState(State.OFFER); - client.transfer(address(this).balance); + client.call.value(address(this).balance)(); + m_client = address(0); + changeState(State.OFFER); } 

Tuve que reemplazar la transferencia con llamada, porque la variante de transferencia no jura porque la transferencia envía una llamada con un mínimo de gas, y una devolución de llamada es imposible (aunque al cambiar a la bifurcación de Constantinopla en Ethereum, el precio del gas cambió y esto volvió a habilitar el ataque de reentrada mediante transferencia .


Resultado de búsqueda de reentrada:


 Reentrancy in Booking.refundWithoutCancellationFee (flattened.sol#243-253): External calls: - client.call.value(address(this).balance)() (flattened.sol#245) State variables written after the call(s): - m_client (flattened.sol#246) 

Está bien, al menos no dará variables de estado después de llamadas externas, y esto es muy bueno.


Si se mueve a lo largo de la lista, entonces las vulnerabilidades restantes en la lista simplemente buscan métodos específicos en el código o patrones conocidos que, por supuesto, si el acceso al marcado por python está disponible, funciona de manera bastante confiable. Es decir patrones bien conocidos Slither no se perderá.


Ahora, haré cambios que muestren perfectamente los detalles del trabajo de los analizadores estáticos:


 - client.transfer(address(this).balance + for (uint i=0; i < 1; i++) { + client.transfer(address(this).balance - 999999999999999999); + } 

y el resultado:


 Booking.refundWithoutCancellationFee has external calls inside a loop: - client.transfer(address(this).balance - 999999999999999999) (flattened.sol#252) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description/_edit#calls-inside-a-loop 

El ciclo se ejecuta una vez y es degenerado; por lo tanto, la advertencia emitida es falsamente positiva y la ausencia de una advertencia sobre aritmética peligrosa es falsamente negativa. Análisis de tipos, resultados de operaciones, conteo de llamadas: las tareas no son para analizadores estáticos. Por lo tanto, comprenda claramente qué errores encontrará Slither y cuáles deben buscarse utilizando otras herramientas.


Prometimos mencionar la posibilidad de escribir nuestros propios scripts para probar, y la salida de cualquier información interesante sobre el contrato usando la tecla --print . Desde este punto de vista, Slither es una gran herramienta para CI. Los desarrolladores de un gran sistema de contratos conocen los nombres de las variables críticas para la seguridad: saldos, tamaños de comisión, banderas y pueden escribir un script de prueba que bloqueará cualquier cambio en el código que, por ejemplo, sobrescriba una variable importante o cambie las variables de estado después de una llamada externa, y Su análisis altamente predecible es una gran herramienta para su uso en ganchos.
La tarea de Slither es salvarte de errores estúpidos, encontrar patrones peligrosos conocidos y advertir al desarrollador. En esta versión, es bueno como una herramienta para un desarrollador novato en Solidity, que le indica inmediatamente cómo escribir el código en Solidity correctamente.


Resumen


En mis pruebas personales, pondría el Slither four por versatilidad, simplicidad y facilidad de uso, así como por guiones de prueba simples y comprensibles y adaptabilidad a CI.


Slither encontró con confianza una ADVERTENCIA real asociada con el uso de la función que envía la transmisión, encontró todos los errores introducidos. No podía hacer frente solo al análisis dinámico, que formalmente no debería hacerse, de lo contrario tendría que sacrificar la universalidad, la previsibilidad y la facilidad de uso.


En el próximo artículo, trataremos con el analizador Mythril, pero aquí está la tabla de contenido para los artículos que están listos o planeados para escribir:


Parte 1. Introducción. Compilación, aplanamiento, versiones de Solidity
Parte 2. Slither (este artículo)
Parte 3. Mythril
Parte 4. Manticore (en proceso de escritura)
Parte 5. Equidna (en proceso de escritura)

Source: https://habr.com/ru/post/438338/


All Articles