Skip to content

add defi security section #399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions defi-security/crosschain_bridges/erc777/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# ERC777 Bridge Vulnerability: Reentrancy Attack in Token Accounting

Cross-chain bridges enable token transfers between different layers (e.g., L1 and L2). However, when these bridges support **ERC-777** tokens, their advanced callback hooks can introduce **reentrancy** vulnerabilities—allowing attackers to manipulate token accounting before the original transaction completes.

---

## Fee-on-Transfer Tokens

**Fee-on-transfer** tokens are tokens that automatically deduct a fee whenever they are transferred. As a result:

- The **sender** specifies an `amount` to send.
- The **receiver** ends up with a lesser amount (the difference is taken as a fee).
- The bridging contract cannot simply trust the `amount` passed to the `transfer` function.

Hence, bridges often rely on computing `balanceAfter - balanceBefore` to determine the **exact** amount that actually arrived in the contract. This practice accommodates fee-on-transfer tokens, but also opens up potential reentrancy risks when dealing with ERC-777 tokens if the contract is not safeguarded.

---

## How the Vulnerability Arises

1. **ERC-777 Callback Hooks**
- Unlike standard ERC-20 tokens, ERC-777 supports hooks via the ERC-1820 registry.
- Attackers can register a contract to be notified (`tokensToSend`) whenever a transfer occurs, creating an opening for reentrancy.

2. **Balance-Based Bridging Logic**
- Bridges measure `balanceBefore` and `balanceAfter` to handle fee-on-transfer tokens.
- If a malicious contract re-enters during the transfer, the contract may incorrectly calculate how many tokens were deposited.

3. **Lack of Reentrancy Protection**
- Without proper guards or a safe pattern, the bridge function can be invoked **twice** (or more) within a single flow, causing inaccurate state updates.

---
## Attack Scenario

1. **Attacker Registers a Malicious Sender Contract**
- The attacker sets their contract as an `ERC777TokensSender` in the ERC-1820 registry.

2. **Initial Bridge Call**
- The attacker calls `bridgeToken` with 500 tokens.
- The bridge records `balanceBefore = 0`, then initiates `safeTransferFrom`.

3. **Reentrancy During Callback**
- `tokensToSend` is triggered in the attacker’s contract, which **re-enters** `bridgeToken` to transfer another 500 tokens.
- Since the first call hasn’t updated state yet, the second call still sees `balanceBefore = 0`.

4. **Combined Transfers**
- By the time the original call finishes, `balanceAfter` is 1000 in the original call, but the logic credits 1500 tokens on L2 (500 from the reentrant call plus the erroneously calculated amount from the first call).

---

## Example Code (Vulnerable)

```solidity
function bridgeToken(address token, uint256 amount) external {
uint256 balanceBefore = IERC20(token).balanceOf(address(this));

// Transfer tokens from msg.sender to the bridge
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

uint256 balanceAfter = IERC20(token).balanceOf(address(this));
uint256 bridgedAmount = balanceAfter - balanceBefore;
}
```

## Mitigations



Leverage mechanisms like ReentrancyGuard or similar logic that disallows nested calls to vulnerable functions.
29 changes: 29 additions & 0 deletions defi-security/crosschain_bridges/self_calls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Avoiding Privilege Escalation via Self-Calls in Cross-Chain Bridges

In many cross-chain bridge designs, certain contracts (often called “managers” or “governors”) hold **privileged roles**. These roles allow them to manage core operations like minting/burning tokens, updating trusted validators, or forwarding user transactions across chains. If these privileged contracts can be **invoked by user-controlled data**—especially in a way that allows **self-calls**—an attacker can craft malicious parameters that make the bridge invoke its own privileged functions without proper authorization checks.

---

## The Core Issue

1. **Arbitrary Call Data**
- The bridge contract receives arbitrary parameters (e.g., `toContract`, `method`, `args`) from user transactions or relayers.
- If these parameters aren’t adequately validated, the contract might call **itself** (or another sensitive contract) with escalated privileges.

2. **Privileged Self-Calls**
- Some cross-chain manager contracts can act as an owner or admin for other contracts in the same system.
- If the manager can be tricked into calling itself with special permissions (e.g., `owner` functions), it effectively bypasses normal access controls.

3. **Insufficient Checks**
- If the bridge only checks signature validity or message authenticity but **not** the actual function or contract being invoked, an attacker can slip in a call to update privileged roles, change deposit addresses, or seize funds.

---

# Example: The Poly Network Hack

On August 10, 2021, the Poly Network—an interoperability protocol linking multiple blockchains—suffered a major breach. By crafting **malicious call data**, the attacker was able to convince the protocol’s **cross-chain manager** to call privileged functions **on itself**, granting the attacker elevated permissions. This resulted in the theft of hundreds of millions of dollars in various cryptocurrencies across Ethereum, BSC, and Polygon.

## Mitigation

Strict Validation of Target and Methods. Deny self-calls to privileged functions unless explicitly intended. Only allow a whitelisted set of external contracts and method signatures to be invoked.

33 changes: 33 additions & 0 deletions defi-security/crosschain_bridges/signature_replay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Preventing Signature Replay Attacks in Multi-Chain Ecosystems

When bridging assets between different blockchains, users typically sign messages or transactions that authorize their tokens to be transferred across networks. While this provides seamless interoperability, it also introduces a **signature replay** risk: a valid signature on one chain might be **reused** on another chain if no measures are taken to differentiate where the signature is supposed to be applied.

---

## What Is a Signature Replay Attack?

A signature replay attack occurs when:
1. **A user signs** a transaction or message on one blockchain (e.g., Ethereum Mainnet).
2. **The signature is still valid** on another chain (e.g., BNB Smart Chain or Polygon) because the system does not bind the signature to a specific network or context.
3. An attacker **reuses** this signature on the secondary chain, effectively duplicating the original transaction, which can lead to unauthorized asset movement or repeated token mints.

---

## Example: Vulnerable Cross-Chain Bridge

Let’s say Alice wants to move tokens from **Chain A** to **Chain B** . She creates a signature that authorizes the transfer of 100 tokens to a bridge contract. Once those tokens have been locked or burned on Chain A, an equivalent amount is minted for her on Chain B. However, if the bridge contract on **Chain C** (Polygon) also accepts the exact same signature without verifying that it is specific to Chain A, an attacker could take Alice’s signature and replay it on Polygon, minting another 100 tokens for free.

### Attack Steps
1. **Alice Authorizes Transfer**
- Alice signs data indicating she is moving 100 tokens from Chain A to Chain B.
2. **Chain B Bridge Validates**
- The bridge on Chain B sees Alice’s valid signature and releases 100 tokens to her address.
3. **Signature Replay on Chain C**
- A malicious actor sends the same signature to the bridge on Chain C, which incorrectly treats it as a new request.
- Since the signature is accepted again, another 100 tokens are minted on Chain C—effectively duplicating the transaction.

---

### Mitigation

Incorporate the chain ID or a domain separator (e.g., using EIP-712) into the signed data. This way, a signature for chain ID 1 (Ethereum Mainnet) cannot be accepted on chain ID 56 (BNB Smart Chain).
90 changes: 90 additions & 0 deletions defi-security/crosschain_bridges/transfer_method/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Cross-Chain Bridge Vulnerability: Permanent Locking of Funds Using `transfer()`

In cross-chain bridging, users often deposit tokens or ETH into a contract on one chain (e.g., Ethereum Layer 1) so that a corresponding representation can be minted on another chain (e.g., an L2 or sidechain). Eventually, users may wish to **redeem** or **withdraw** their tokens back to the original chain. However, some bridge implementations rely on the Solidity `transfer()` method when returning ETH to users. This can inadvertently lock funds **forever** if the recipient is a contract requiring more than 2300 gas in its `receive()` or `fallback()` function.

---

## How This Affects Cross-Chain Bridges

1. **Bridging and Redemption**
- In a typical cross-chain setup, a user sends ETH to a bridge contract on L1.
- After bridging, they might later request to withdraw or redeem that ETH back from the bridge.

2. **Using `transfer()` in the Bridge**
- The bridge tries to finalize the redemption by `transfer()`-ing the user’s ETH back.
- If that user’s address is actually a contract—like a multisig wallet or a DeFi protocol—that needs more than 2300 gas in its `fallback()`/`receive()`, the withdrawal fails.
- The bridging contract’s redemption process **reverts**, and the user cannot retrieve their ETH.

3. **Result: Funds Stuck in the Bridge**
- Because no other address is authorized to claim the same withdrawal, the ETH remains locked in the bridging contract.
- Future attempts to redeem the same balance also fail unless the code is modified or the bridging logic is upgraded (if at all possible).

---

## Example: Bridge Withdrawals That Fail for Contract Addresses

In this simplified `finalizeWithdrawal` function, the bridge tries to return ETH to the user (who might be a contract) using `transfer()`. The low gas stipend can cause a revert if the recipient’s fallback logic requires more gas.

```solidity
// Called after a user has claimed tokens on L2 and wants to unlock them on L1
function finalizeWithdrawal() external {
require(canWithdraw[msg.sender], "No withdrawal available");
uint256 amount = balances[msg.sender];
require(amount > 0, "Nothing to withdraw");

balances[msg.sender] = 0;

// Vulnerable step: Using transfer() to push ETH
// If msg.sender is a contract that needs >2300 gas, this will revert
payable(msg.sender).transfer(amount);


balances[msg.sender] = 0;
canWithdraw[msg.sender] = false;
}

```

Suppose a cross-chain bridge uses a queue-based redemption process:

1. **User Deposits ETH**
- Alice sends 10 ETH from L1 to the Bridge contract to mint a wrapped token on L2.

2. **Redemption Requested**
- Later, Alice wants to redeem her 10 ETH back to L1.
- She provides her **multisig** wallet address, which is stored in the bridge contract as the recipient.

3. **Bridge Attempts `transfer()`**
- After a cooldown or waiting period, the bridge finalizes the redemption by doing:
```solidity
payable(msg.sender).transfer(amount);
```
- However, the multisig’s `fallback()` requires more gas than `transfer()` provides (only 2300).

4. **Transaction Reverts**
- Since the multisig contract can’t execute its logic with 2300 gas, the transaction fails.
- The bridging contract reverts the withdrawal, leaving the 10 ETH stuck in the bridge’s custody.

5. **Assets Locked Forever**
- No alternative address can claim these funds, and Alice can’t modify her multisig to reduce gas usage.
- The 10 ETH remains trapped in the bridge contract indefinitely.

---


## Mitigations

1. **Use `call` Instead of `transfer()`**
- A safer approach is:
```solidity
(bool success, ) = payable(recipient).call{value: amount}("");
require(success, "ETH transfer failed");
```
- `call` can forward more gas, preventing the out-of-gas revert scenario. Make sure precautions are made against reentrancy.

2. **Adopt Pull-Based Withdrawals**
- Instead of pushing ETH automatically, let recipients **pull** their funds.
- If a contract needs more gas, it can handle the withdrawal logic internally on its own terms.


By using more flexible methods to send ETH and accommodating higher gas requirements, cross-chain bridges can avoid permanently freezing user funds—particularly for advanced contract wallets on L1 or L2.
28 changes: 28 additions & 0 deletions defi-security/crosschain_bridges/unsafe_transferFrom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Missing Revert on Failed Token Transfers in Bridges

When users deposit tokens into a cross-chain bridge, the typical process involves calling a token’s `transferFrom` function to move the user’s tokens into the bridge contract. The bridge then updates its internal records to reflect a successful deposit (e.g., incrementing a `balances[msg.sender]`). However, **not all tokens revert** upon transfer failure. Some tokens only return a boolean `false` without reverting the transaction. If a contract **ignores** this return value and assumes success, it can create a **false deposit** scenario where the user’s balance is updated even though **no tokens were actually transferred**.

## Why It Happens
- **Non-Reverting Tokens**
Several tokens (like **BAT**, **HT**, **cUSDC**, **ZRX**) do **not** revert if `transferFrom` fails (e.g., due to insufficient funds).
- **Bridge Assumes Success**
The bridging contract might assume that if the call didn’t revert, the tokens were deposited successfully—never checking the boolean return value.
- **Incorrect Balance Accounting**
As a result, the bridge increments the user’s deposit balance even though **no tokens** arrived in the bridge contract.

## Example: Vulnerable Bridge Deposit

```solidity
function deposit(address token, uint256 amount) external {
// Vulnerable approach: ignoring the transferFrom's return value
IERC20NonReverting(token).transferFrom(msg.sender, address(this), amount);

// Contract assumes deposit succeeded, updates user balance
deposits[token][msg.sender] += amount;
}

```

## Mitigation

Use safeTransferFrom. There are libraries that provide safe variants of transferFrom that revert on failure, eliminating silent failures.
79 changes: 79 additions & 0 deletions defi-security/erc4337/factory_create/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Vulnerability of Using `CREATE` Instead of `CREATE2` in ERC-4337 Factories

In the **ERC-4337** (Account Abstraction) model, new smart contract wallets are typically deployed via a *factory* contract. If that factory relies on `CREATE` (`0xF0`) instead of `CREATE2` (`0xF5`), it introduces non-deterministic wallet addresses and potential vulnerabilities. ERC-4337 explicitly recommends that factories use `CREATE2`, returning **the same wallet address** every time the same parameters are used—even if the wallet is already deployed.

---

## Why `CREATE` Is Problematic

1. **Non-Deterministic Address Generation**
- `CREATE` bases the newly created contract’s address on the **factory’s nonce**.
- Each transaction that increments the factory’s nonce can change the final address, causing unpredictability and potential confusion.

2. **Front-Running & Replay Attacks**
- Attackers can manipulate transaction ordering or intercept a user’s transaction to alter the factory’s nonce.
- This could lead to unexpected addresses, or let an attacker deploy a contract at a user’s intended address first.

3. **Funding a Wallet That Doesn’t Exist**
- Without a **deterministic** address, users risk sending funds to an address that might never be deployed at all, resulting in lost or irretrievable tokens.

4. **Lack of Reusable Addresses**
- If the wallet was already deployed at a given nonce, you can’t reliably re-derive that address to confirm if it’s the “same” wallet.
- Bundlers and other off-chain services can’t pre-compute addresses or check if an address is already deployed without complicated state checks on the factory’s nonce.

---

## The `CREATE2` Solution

1. **Deterministic Addressing**
- `CREATE2` calculates an address from:
- The **deployer** (factory) address
- A **salt** (user-specific data)
- The **creation code**
- The **bytecode** of the wallet
- Because of this, the wallet address is **independent** of the factory’s nonce or transaction ordering.

2. **Counterfactual Deployment**
- Users can generate and share their wallet address **before** it’s actually deployed.
- Funds can be sent there immediately, and the wallet contract is deployed only when the user needs to interact.

3. **Reproducible Address**
- If the same salt and code are provided again, `CREATE2` yields **the same** address.
- This means the factory can **return that address** even if it’s already been deployed, or if someone tries to deploy it again.
- It also allows bundlers to easily query the future address by simulating a call to `getSenderAddress()` without worrying if the contract is currently deployed.

4. **ERC-4337 Compatibility**
- The EntryPoint contract expects wallet creation to be repeatable.
- If the wallet address is **already deployed**, the factory method should still return **that same** address. This enables services like bundlers to handle user operations seamlessly.

---

## Returning the Same Address Even if Deployed

One core ERC-4337 requirement is that the factory must **return the wallet address** even if the wallet has already been created. This is essential because:

- **Bundlers** can safely simulate the wallet creation (via `initCode`) and get the address without knowing in advance whether the wallet is already deployed.
- **Idempotent Creation** ensures that repeated calls with the same parameters do not spawn multiple wallets but confirm or reveal the same address.

### Example Scenario

1. **User Operation**
- A user sends a `UserOperation` with `initCode` that points to the factory + constructor arguments.
- The EntryPoint calls the factory to deploy or confirm the wallet address.

2. **Factory Using `CREATE2`**
- If the wallet for those parameters is **not** deployed yet, the factory calls `CREATE2` to deploy it and returns the new address.
- If it **is** already deployed, `CREATE2` with the same salt + code results in the **same** address. The factory returns that address again.

3. **No Address Conflicts**
- The user or bundler can rely on the returned address being correct, regardless of nonce ordering or prior attempts.

---

## Takeaways

- **Using `CREATE`** can break the deterministic address property, which is integral to ERC-4337’s goal of “counterfactual” wallet creation.
- **`CREATE2`** offers **predictable**, **repeatable** deployments, letting the factory return the **same** address if it’s already been created with the same parameters.
- By adhering to these patterns, ERC-4337 wallets can seamlessly accept funds before actual deployment, minimize front-running risks, and simplify user onboarding.

Ultimately, **CREATE2** is **not optional**—it’s essential for safe, deterministic account abstraction in an ERC-4337 world, ensuring that the factory can confirm or create a wallet at a stable address each time.
Loading
Loading