BLOCKCHAIN-SECURITY
Authorized use only. Offensive reference for systems you own or are explicitly permitted to test. You are responsible for staying within the law.
Security vulnerabilities, audit tools, and attack vectors for blockchain applications and smart contracts.
COMMON SOLIDITY VULNERABILITIES#
1. REENTRANCY
Description: External call allows callee to re-enter the calling
function before state is updated.
Vulnerable code:
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] -= amount; // state updated AFTER call
}
Attack:
// Attacker contract
fallback() external payable {
if (address(target).balance >= amount) {
target.withdraw(amount); // re-enter before balance updated
}
}
Fix (Checks-Effects-Interactions pattern):
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // update state FIRST
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
Additional mitigations:
- Use ReentrancyGuard (OpenZeppelin nonReentrant modifier)
- Use pull payment pattern instead of push
- Limit gas forwarded in external calls
2. INTEGER OVERFLOW / UNDERFLOW
Description: Arithmetic operations exceed type bounds.
Note: Solidity 0.8.0+ has built-in overflow checks.
Vulnerable (Solidity < 0.8.0):
uint8 balance = 255;
balance += 1; // overflows to 0
uint8 balance = 0;
balance -= 1; // underflows to 255
Fix:
- Use Solidity >= 0.8.0 (automatic checks)
- For < 0.8.0: use SafeMath library
- Use unchecked{} blocks only when overflow is intentional
3. ACCESS CONTROL
Description: Missing or improper authorization checks.
Vulnerable:
function setOwner(address newOwner) public {
owner = newOwner; // anyone can call!
}
Fix:
function setOwner(address newOwner) public onlyOwner {
owner = newOwner;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
Best practices:
- Use OpenZeppelin Ownable or AccessControl
- Role-based access with AccessControl
- Multi-sig for critical operations
- TimeLock for governance changes
4. UNCHECKED EXTERNAL CALLS
Description: Not checking return value of external calls.
Vulnerable:
payable(addr).send(amount); // returns bool, not checked
payable(addr).transfer(amount); // reverts but limited to 2300 gas
Fix:
(bool success, ) = payable(addr).call{value: amount}("");
require(success, "Transfer failed");
5. FRONT-RUNNING (MEV)
Description: Attacker sees pending transaction and submits
their own with higher gas price to execute first.
Common targets:
- DEX trades (sandwich attacks)
- Token approvals
- Auction bids
- Oracle updates
Mitigations:
- Commit-reveal schemes
- Use private mempools (Flashbots Protect)
- Slippage protection on DEX trades
- Batch auctions
- Submarine sends
6. DENIAL OF SERVICE
Description: Making contract functions unusable.
Patterns:
- Unexpected revert in loop (one failure blocks all)
- Block gas limit reached (unbounded loops)
- External call failure blocking critical functions
- Self-destruct sending ETH to contract without receive()
Fix:
- Use pull over push patterns
- Limit loop iterations
- Handle external call failures gracefully
- Set gas limits on external calls
7. ORACLE MANIPULATION
Description: Manipulating price feeds or external data sources.
Attack:
1. Flash loan large amount of token A
2. Swap to manipulate price on DEX (AMM)
3. Interact with victim contract using manipulated price
4. Profit from price difference
5. Repay flash loan
Fix:
- Use time-weighted average prices (TWAP)
- Use decentralized oracle networks (Chainlink)
- Use multiple oracle sources
- Implement circuit breakers for extreme price movements
- Add price deviation checks
8. DELEGATECALL INJECTION
Description: delegatecall executes code in the context of the
calling contract, allowing storage manipulation.
Vulnerable:
function execute(address target, bytes calldata data) public {
target.delegatecall(data); // target can modify our storage
}
Fix:
- Restrict delegatecall targets to trusted contracts
- Use proxy patterns carefully (UUPS, Transparent)
- Audit storage layout compatibility
9. SIGNATURE REPLAY
Description: Valid signature reused on different chain or context.
Fix:
- Include chain ID in signed data (EIP-155)
- Include nonce to prevent replay
- Include contract address in signed message
- Use EIP-712 for typed structured data signing
- Implement deadline/expiration
10. FLASH LOAN ATTACKS
Description: Borrow large amounts without collateral in single
transaction to exploit price-dependent logic.
Common pattern:
1. Flash loan millions in tokens
2. Manipulate AMM price
3. Exploit lending protocol using wrong price
4. Repay flash loan with profit
Mitigations:
- Use Chainlink oracles (not AMM spot price)
- Implement TWAP oracles
- Add transaction-level access controls
- Rate limiting / cooldown periods
SECURITY ANALYSIS TOOLS#
Slither (Trail of Bits):
pip install slither-analyzer
# Basic analysis
slither contract.sol
slither . --solc-remaps "@openzeppelin=node_modules/@openzeppelin"
# Specific detectors
slither contract.sol --detect reentrancy-eth
slither contract.sol --detect arbitrary-send-eth
slither contract.sol --detect unchecked-transfer
# Print useful information
slither contract.sol --print contract-summary
slither contract.sol --print function-summary
slither contract.sol --print inheritance-graph
Key detectors:
reentrancy-eth High: reentrancy with ETH transfer
reentrancy-no-eth Medium: reentrancy without ETH
arbitrary-send-eth High: unprotected ETH send
suicidal High: unprotected selfdestruct
unprotected-upgrade High: unprotected proxy upgrade
unchecked-transfer Medium: unchecked ERC20 transfer
tx-origin Medium: use of tx.origin for auth
locked-ether Medium: contract can receive but not send ETH
Mythril (ConsenSys):
pip install mythril
# Analyze contract
myth analyze contract.sol
myth analyze contract.sol --solc-json mythril.config.json
# Analyze on-chain contract
myth analyze -a <contract_address> --rpc <rpc_url>
# Specific checks
myth analyze contract.sol --execution-timeout 300
Detection capabilities:
- Integer overflow/underflow
- Reentrancy
- Unprotected selfdestruct
- Delegatecall to untrusted contract
- Ether transfer to arbitrary address
- Unchecked return values
Echidna (Trail of Bits):
# Property-based fuzzer for Solidity
pip install echidna
# Write test properties
contract TestContract is TargetContract {
function echidna_balance_positive() public view returns (bool) {
return address(this).balance >= 0;
}
function echidna_no_overflow() public view returns (bool) {
return totalSupply <= MAX_SUPPLY;
}
}
# Run fuzzer
echidna contract.sol --contract TestContract --config echidna.yaml
# Configuration (echidna.yaml)
testMode: assertion # or property
testLimit: 50000
shrinkLimit: 5000
seqLen: 100
Foundry (for security testing):
# Fuzz testing
function testFuzz_Withdraw(uint256 amount) public {
vm.assume(amount > 0 && amount <= address(this).balance);
// ... test withdraw logic
}
# Invariant testing
function invariant_totalSupply() public {
assertEq(token.totalSupply(), expectedSupply);
}
# Fork testing (test against mainnet state)
forge test --fork-url https://eth-mainnet.alchemyapi.io/v2/<KEY>
Additional tools:
Aderyn: Rust-based static analyzer (fast)
Certora Prover: Formal verification (commercial)
Manticore: Symbolic execution (Trail of Bits)
Securify2: Automated security scanner (ETH Zurich)
4naly3er: Static analysis report generator
DEFI ATTACK VECTORS#
Flash Loan Attacks:
Platforms: Aave, dYdX, Uniswap V3 (flash swaps)
Pattern: borrow -> manipulate -> exploit -> profit -> repay
Notable: bZx ($8M), Harvest Finance ($34M), Cream Finance ($130M)
Oracle Manipulation:
Attack: Manipulate spot price on AMM, exploit price-dependent protocol
Targets: Lending protocols using AMM as oracle
Defense: Chainlink, TWAP, multiple oracles
Sandwich Attacks (MEV):
Attack: Front-run + back-run victim's swap
1. See victim's pending swap (buy token X)
2. Buy token X first (front-run, price goes up)
3. Victim's swap executes at worse price
4. Sell token X (back-run, profit from price difference)
Defense: MEV protection (Flashbots), slippage limits
Governance Attacks:
Attack: Acquire enough tokens to pass malicious proposal
Flash loan governance tokens, vote, execute in same block
Defense: Time-locked proposals, vote escrow, snapshot voting
Rug Pulls:
Types:
- Liquidity removal (pull LP tokens)
- Minting unlimited tokens
- Hidden backdoor functions
- Proxy upgrade to malicious implementation
Red flags:
- Unverified contract source
- Owner can mint unlimited tokens
- No timelock on admin functions
- Low liquidity locked amount
- Anonymous team
Price Manipulation via AMMs:
Attack: Large swap to skew price, exploit dependent protocol
Vulnerable: Any protocol reading AMM spot price as oracle
Defense: TWAP, Chainlink, EMA oracles
WALLET SECURITY#
Hot wallet security: - Use hardware wallet for large amounts - Enable all available 2FA - Use dedicated device for crypto operations - Verify transaction details on hardware wallet screen - Be cautious of approval transactions (check spender and amount) Private key management: - Never store private keys in plaintext - Use hardware wallets (Ledger, Trezor) - Multi-signature wallets for team/treasury (Safe/Gnosis) - Seed phrase: offline, metal backup, split storage - Never share seed phrase or private key Common wallet attacks: - Phishing (fake dApp sites, wallet connect scams) - Approval exploits (infinite approve to malicious contract) - Address poisoning (dust transactions with similar addresses) - Clipboard malware (replaces copied addresses) - Fake airdrops (interaction triggers token approval) - Social engineering (fake support, fake giveaways) Token approval safety: # Check approvals # Use: revoke.cash, etherscan token approval checker # Revoke approvals # Set approval to 0 for unused contracts token.approve(spenderAddress, 0); # Best practice: approve exact amount needed token.approve(spenderAddress, exactAmount);
AUDIT METHODOLOGY#
Pre-audit:
1. Gather documentation (specs, architecture, threat model)
2. Understand the protocol's intended behavior
3. Review similar protocols and past audits
4. Set up development environment
5. Compile and run existing tests
Automated analysis:
1. Run Slither (static analysis)
2. Run Mythril (symbolic execution)
3. Run Echidna (fuzzing) with custom properties
4. Run Aderyn (fast static checks)
5. Review automated findings, filter false positives
Manual review checklist:
[ ] Access control: who can call each function?
[ ] Reentrancy: external calls before state changes?
[ ] Integer arithmetic: overflow/underflow possible?
[ ] Oracle usage: is price data manipulation-resistant?
[ ] Flash loan resistance: can logic be exploited in single tx?
[ ] Front-running: are transactions MEV-susceptible?
[ ] Upgrade safety: proxy storage layout compatible?
[ ] Token handling: ERC20 return values checked? Fee-on-transfer?
[ ] Gas optimization: unbounded loops? DoS potential?
[ ] Input validation: all parameters validated?
[ ] Event emissions: state changes properly logged?
[ ] Centralization risks: admin key single point of failure?
[ ] Economic model: game theory sound? Incentive alignment?
[ ] Edge cases: zero values, max values, empty arrays?
Reporting:
Severity levels:
Critical: Direct loss of funds, protocol takeover
High: Indirect loss of funds, significant impact
Medium: Limited impact, conditional exploit
Low: Best practice violations, minor issues
Informational: Gas optimization, code quality
Finding format:
- Title
- Severity
- Description
- Impact
- Proof of Concept (test code)
- Recommendation
COMMON SECURITY PATTERNS#
Checks-Effects-Interactions:
function withdraw(uint amount) external {
require(balances[msg.sender] >= amount); // CHECK
balances[msg.sender] -= amount; // EFFECT
(bool ok,) = msg.sender.call{value: amount}(""); // INTERACTION
require(ok);
}
Pull over Push:
// Instead of sending to many, let each claim
mapping(address => uint) public pendingWithdrawals;
function withdraw() external {
uint amount = pendingWithdrawals[msg.sender];
pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
Emergency stop:
bool public paused;
modifier whenNotPaused() { require(!paused); _; }
function pause() external onlyOwner { paused = true; }
Timelock:
// Delay between proposal and execution
// Gives users time to exit if they disagree
Rate limiting:
mapping(address => uint) public lastAction;
modifier rateLimit(uint delay) {
require(block.timestamp >= lastAction[msg.sender] + delay);
lastAction[msg.sender] = block.timestamp;
_;
}
REFERENCES#
- SWC Registry: https://swcregistry.io/ (Smart Contract Weakness Classification) - Slither: https://github.com/crytic/slither - Mythril: https://github.com/Consensys/mythril - Echidna: https://github.com/crytic/echidna - OpenZeppelin Contracts: https://github.com/OpenZeppelin/openzeppelin-contracts - Rekt News: https://rekt.news/ (DeFi exploit database) - DeFi Llama Hacks: https://defillama.com/hacks - Solidity by Example: https://solidity-by-example.org/