# TheRedVillage: ungated router relay seizes UUPS upgrade, drains vault

Ungated PolygonTRVRouter.forwardRequest relayed upgradeTo to SeasonService UUPS proxy, letting attacker install malicious impl and sweep 31.43 WETH from TRVZooKeeper.

- Payout: Real-world hack
- Severity: critical
- Program: TheRedVillage
- Disclosed: 2026-09-21
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-09/TheRedVillage_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20260921-theredvillage
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:chained vuln:access-control vuln:auth-bypass vuln:business-logic

**Target:** PolygonTRVRouter / SeasonService UUPS proxy / TRVZooKeeper vault
**Root cause:** PolygonTRVRouter.forwardRequest has no caller gate and forwards arbitrary calldata to registered services; the router itself holds _authorizeUpgrade on SeasonService, so any caller can relay upgradeTo and pass the proxy's role check.
**Steps:**
1. Fork Polygon at block 94200340 (one block before setup tx).
2. Deploy MaliciousSeasonImpl with proxiableUUID() returning ERC1967 impl slot and a drain() entrypoint.
3. Call `ROUTER.forwardRequest("Season","SeasonService", abi.encodeWithSignature("upgradeTo(address)", impl))` from attacker EOA.
4. Proxy authorizes because msg.sender is the router which holds _authorizeUpgrade; implementation replaced.
5. Call `SeasonService.drain(WETH, ZOOKEEPER, attacker, players[299], amounts[299])`; impl loops transferERC20In per player then transferERC20Out of full vault balance.
6. Attacker receives 31.426018474517367982 WETH.
**How found:** Traced the setup tx and observed an unprivileged contract calling forwardRequest and the upgrade succeeding; confirmed on-fork via hasRole queries that the router holds _authorizeUpgrade on SeasonService and SeasonService holds transferERC20In/Out on TRVZooKeeper.
**Exploitation:** Attacker EOA (no privileges) relays upgradeTo through the ungated router to seize the SeasonService UUPS proxy, then the malicious implementation runs in proxy context and uses the proxy's transferERC20In/Out roles on TRVZooKeeper to pull 299 players' standing WETH approvals (31.350379502001367982 WETH) plus the vault's 0.075638972516 WETH and sweep all to the attacker. Two txs ~13 min apart.
**Escalation:** Chained router relay -> UUPS upgrade -> role inheritance on vault to convert a single ungated forwarder into full vault drain across 299 approvals.
**Insight:** When a router/registry forwards arbitrary calldata to proxies, check whether the router itself holds privileged roles (upgrade, token-mover) on those targets — the role check then validates the router, not the caller, turning any ungated forwarder into an authority-seizure primitive.
