⛓️ Blockchain Learning Path GitHub

Criterios de resolución

Navegación: Retos de seguridad · Módulo 09 · Seguridad · Inicio

Este documento explica cómo se razona la solución de cada reto: el patrón de vulnerabilidad, cómo se detecta y el arreglo correcto. No contiene exploits listos para copiar y atacar: los snippets muestran el patrón seguro, no un ataque ejecutable contra terceros.

Regla ética. Estas técnicas se practican solo en entornos propios o explícitamente autorizados (Anvil local o testnet). Usarlas contra sistemas de terceros es un delito. Intenta cada reto por tu cuenta antes de leer esta guía; copiar no enseña a auditar.

Los retos viven en contracts/ con un contrato vulnerable y su contraparte corregida. Una mitigación de código no corrige automáticamente una falla económica o de gobernanza.

Reto 01 · Reentrancia

function withdraw() external nonReentrant {
    uint256 amount = balance[msg.sender];
    balance[msg.sender] = 0;               // efecto ANTES de la interacción
    (bool ok,) = msg.sender.call{value: amount}("");
    require(ok, "transfer failed");
}

Reto 02 · Control de acceso

function proposeOwner(address next) external {
    require(msg.sender == owner && next != address(0), "unauthorized");
    pendingOwner = next;
}

function acceptOwner() external {
    require(msg.sender == pendingOwner, "not pending");
    owner = pendingOwner;
    pendingOwner = address(0);
}

Reto 03 · Manipulación de oráculo

function value(uint256 amount, uint256 price, uint256 updatedAt)
    external view returns (uint256)
{
    require(price > 0 && block.timestamp - updatedAt <= maxAge, "stale");
    return amount * price;
}

Reto 04 · Repetición de firma (replay)

bytes32 digest = keccak256(abi.encode(
    block.chainid, address(this), recipient, amount, nonce, deadline
));
require(!consumed[digest] && block.timestamp <= deadline, "invalid");
consumed[digest] = true;   // consumir ANTES de ejecutar el efecto

Reto 05 · Front-running

function commit(bytes32 commitment) external {
    commitments[msg.sender] = commitment;   // hash de (msg.sender, respuesta, salt)
}

function reveal(string calldata plain, bytes32 salt) external view returns (bool) {
    return commitments[msg.sender] == keccak256(abi.encode(msg.sender, plain, salt));
}

Complementa con slippage, deadline y privacidad del flujo cuando aplique.

Reto 06 · Storage collision (proxy)

contract AppendOnlyLayoutV2 {
    address public owner;   // slot 0 preservado
    uint256 public limit;   // slot 1 preservado
    uint256 public newValue; // slot 2 nuevo, sin desplazar los anteriores
}

Usa estándares probados (slots definidos, storage gaps) y una herramienta que compare layouts antes de actualizar.

Qué se evalúa en tu resolución

El fundamento conceptual está en el módulo 09 · Seguridad.