- Golang
- Foundry and its tools
- Make CLI tool
Reentrancy Attack Detection and Front-Running Execution for exploit prevention Demonstration Video
- ETHERSCAN_API_KEY: API key for Etherscan.
- ETHEREUM_CLIENT_URL: URL of the Ethereum client (Holesky testnet).
- DEPLOYER_WALLET: Foundry Cast wallet name of the deployer wallet.
- DEPLOYER_WALLET_ADDRESS: Address of the deployer wallet.
- ACCOUNT_WALLET: Foundry Cast wallet name of the account wallet for depositing funds and attacking.
- PROXY_ADMIN_CONTRACT_ADDRESS: Address of the proxy admin contract.
- VULNERABLE_CONTRACT_ADDRESS_PROXY: Address of the proxy contract for the vulnerable contract.
- ATTACK_CONTRACT_ADDRESS: Address of the attack contract.
- ETHEREUM_CLIENT_URL: URL of the Ethereum client (Holesky testnet).
- CONTRACT_ADDRESS_ATTACK: Address of the attack contract.
- CONTRACT_ADDRESS_VULNERABLE_PROXY: Address of the proxy contract for the vulnerable contract.
- OWNER_PRIVATE_KEY: Private key of the contract owner (deployer wallet).
- DB_CONN_STRING: Database connection string.
- TENDERLY_RPC_URL: RPC URL for Tenderly (should point to same network as ETHEREUM_CLIENT_URL).
- ENABLE_EMAIL_NOTIFICATION: Enable email notifications (true/false).
- EMAIL_JS_SERVICE_ID: EmailJS service ID.
- EMAIL_JS_TEMPLATE_ID: EmailJS template ID.
- EMAIL_JS_USER_ID: EmailJS user ID/ public key
- EMAIL_RECEIVER: Email address to receive notifications.
- EMAIL_JS_API_KEY: EmailJS API key.
- Download the git repository.
- Navigate to the
VulnerableContractProject
directory:cd VulnerableContractProject
- Copy
.env.example
to.env
:cp .env.example .env
- Set up the
.env
file yourself or use the shared.env
values. - Ensure you have two wallets:
- One for deploying and upgrading contracts. (DEPLOYER_WALLET)
- Another for depositing and attacking the vulnerable contract. (ACCOUNT_WALLET)
- Ensure both wallets have at least 0.05 ETH for executing transactions. If not, get funds from here
- Ensure that
ETHEREUM_CLIENT_URL
in is set to the Holesky testnet URL. - Deploy the contracts to the testnet:
make deploy-testnet
- Copy the attack, proxy, and proxy admin contract addresses from the logs and set them in the respective
.env
files in theVulnerableContractProject
andgo-microservice
folders (refer to the video for detailed instructions).
- Navigate to the
go-microservice
directory:cd ../go-microservice
- Copy
.env.example
to.env
:cp .env.example .env
- Set up the
.env
file yourself or use the shared.env
values. - Use the deployed contract addresses and the contract deployer wallet private key for pause transactions. Ensure
OWNER_PRIVATE_KEY
ingo-microservice/.env
corresponds toDEPLOYER_WALLET_ADDRESS
address inVulnerableContractProject/.env
. - Tidy up the Go modules:
go mod tidy
- Export all the
.env
values:export $(xargs < .env)
- Run the Go application:
go run main.go
- Your server should indicate that it is monitoring transactions for the attack contract address.
-
Deposit Funds:
- Run the deposit command. Ensure to use a different cast wallet than the one used for deploying (or use
--private-key
if you don’t have a cast wallet):make deposit
- The server should detect the transaction but not recognize it as a reentrancy transaction.
- Run the deposit command. Ensure to use a different cast wallet than the one used for deploying (or use
-
Check Attack Contract Balance:
- Check the attack contract balance:
make get-attack-deposit-value
- This should show that the attack contract has deposited 0.0002 ETH into the vulnerable smart contract.
- Check the attack contract balance:
-
Initiate Reentrancy Attack:
- Initiate a reentrancy attack:
make attack
- This will trigger a reentrancy attack transaction via the attack contract. The Go microservice will intercept this, send a dynamic gas-priced pause transaction to the proxy contract to front-run the attack transaction, and send an email notification with the attack transaction hash and pause transaction hash.
- You should receive an email about the attack transaction, and the attack transaction should fail with an error.
- Initiate a reentrancy attack:
-
Upgrade the Implementation Contract:
- Upgrade the proxy contract:
make deploy-upgrade-implementation
- This will deploy the fixed contract implementation and upgrade the proxy contract to the new implementation contract. The Go microservice will detect this and send an email notification with the upgrade transaction hash. It will also unpause the contract.
- Upgrade the proxy contract:
-
Shutdown the Go Microservice:
- Shutdown the Go microservice:
- Press
Ctrl+C
to stop the Go microservice.
- Press
- Shutdown the Go microservice:
-
Attempt Reentrancy Attack Again:
- Initiate a reentrancy attack again:
make attack
- This time, the attack transaction should fail as the upgraded implementation contract has no reentrancy vulnerability.
- Initiate a reentrancy attack again:
-
Check Attack Contract Balance:
- Check the attack contract balance:
make get-attack-deposit-value
- This should show that the attack contract has deposited 0.0002 ETH into the vulnerable smart contract. Since all attack transactions failed, the balance should remain the same.
- Check the attack contract balance:
-
Clean Up:
- Withdraw the funds from the attack contract:
make withdraw-all
- Withdraw the funds from the attack contract:
This project consists of two main components:
- Smart Contracts: Deployed on the Ethereum blockchain, including both vulnerable and fixed contracts.
- Go Microservice: Monitors transactions and intercepts reentrancy attacks on the vulnerable contract.
.gitmodules
.vscode/
launch.json
settings.json
go-microservice/
.env
.env.example
go.mod
go.sum
main.go
pkg/
constants/
dto/
model/
service/
utils/
LICENSE
README.md
VulnerableContractProject/
.env
.env.example
.github/
workflows/
.gitignore
broadcast/
DeployFixedContract.s.sol/
DeployVulnerableContract.s.sol/
cache/
DeployFixedContract.s.sol/
DeployVulnerableContract.s.sol/
...
foundry.toml
lib/
forge-std/
...
Makefile
script/
src/
test/
-
Contracts:
VulnerableContract
: Contains a vulnerability that can be exploited via a reentrancy attack.FixedContract
: A fixed version of the vulnerable contract.AttackContract
: Used to perform the reentrancy attack.
-
Deployment Scripts:
DeployVulnerableContract.s.sol
: Deploys the vulnerable contract, proxy admin, and proxy contract.DeployFixedContract.s.sol
: Deploys the fixed contract and upgrades the proxy to point to the fixed implementation.
-
Makefile:
deploy-testnet
: Deploys the vulnerable contract to the testnet.deploy-upgrade-implementation
: Deploys the fixed contract and upgrades the proxy.deposit
: Sends a deposit transaction to the attack contract.attack
: Initiates a reentrancy attack via the attack contract.
- DeployVulnerableContract.s.sol:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "forge-std/Script.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "../src/VulnerableContract.sol"; import "../src/AttackContract.sol"; contract DeployVulnerableContract is Script { function run() external { vm.startBroadcast(); VulnerableContract implementation = new VulnerableContract(); ProxyAdmin proxyAdmin = new ProxyAdmin(msg.sender); TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( address(implementation), address(proxyAdmin), abi.encodeWithSelector(VulnerableContract.initialize.selector) ); AttackContract attackContract = new AttackContract(proxy); vm.stopBroadcast(); console.log("Implementation contract deployed at:", address(implementation)); } }
- DeployFixedContract.s.sol:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "forge-std/Script.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "../src/FixedContract.sol"; import "../src/VulnerableContract.sol"; contract DeployFixedContract is Script { function run() external { vm.startBroadcast(); FixedContract fixedImplementation = new FixedContract(); address proxyAdminAddress = vm.envAddress("PROXY_ADMIN_CONTRACT_ADDRESS"); address proxyAddress = vm.envAddress("VULNERABLE_CONTRACT_ADDRESS_PROXY"); ProxyAdmin proxyAdmin = ProxyAdmin(proxyAdminAddress); bytes memory data = abi.encodeWithSignature("initialize()"); proxyAdmin.upgradeAndCall(ITransparentUpgradeableProxy(proxyAddress), address(fixedImplementation), data); } }
Main Components:
- main.go: Entry point of the Go application.
- pkg/service: Contains the service logic for monitoring transactions and pausing the contract.
- pkg/utils/email: Utility for sending email notifications.
package main
import (
"log"
"os"
"github.com/yourusername/yourrepo/pkg/service"
"github.com/yourusername/yourrepo/pkg/utils/email"
)
func main() {
clientURL := os.Getenv("ETHEREUM_CLIENT_URL")
contractAddrAttack := os.Getenv("CONTRACT_ADDRESS_ATTACK")
contractAddrVulnerableProxy := os.Getenv("CONTRACT_ADDRESS_VULNERABLE_PROXY")
privateKey := os.Getenv("OWNER_PRIVATE_KEY")
dbConnString := os.Getenv("DB_CONN_STRING")
if clientURL == "" || contractAddrAttack == "" || contractAddrVulnerableProxy == "" || privateKey == "" || dbConnString == "" {
log.Fatalf("Please provide values for ETHEREUM_CLIENT_URL, CONTRACT_ADDRESS, OWNER_PRIVATE_KEY, and DB_CONN_STRING")
}
emailService, err := email.NewEmailService()
if err != nil {
log.Fatalf("Failed to create EmailService: %v", err)
}
pauser, err := service.NewContractPauser(clientURL, contractAddrAttack, contractAddrVulnerableProxy, privateKey, dbConnString, emailService)
if err != nil {
log.Fatalf("Failed to create ContractPauser: %v", err)
}
pauser.MonitorPendingTransactions()
}
-
ContractPauser:
- Description: Monitors transactions and pauses the contract in case of a reentrancy attack.
- Methods:
MonitorPendingTransactions()
: Monitors pending transactions and pauses the contract if a reentrancy attack is detected.PauseContract()
: Pauses the contract by sending a dynamic gas-priced transaction to the proxy contract.
-
Email Utility Service:
- Description: Sends email notifications.
- Methods:
SendEmail()
: Sends an email notification with the transaction details.
Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
Foundry consists of:
- Forge: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- Cast: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- Anvil: Local Ethereum node, akin to Ganache, Hardhat Network.
- Chisel: Fast, utilitarian, and verbose solidity REPL.
$ forge build --evm-version cancun
$ forge test --evm-version cancun
$ forge fmt
$ forge snapshot
$ anvil
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
$ cast <subcommand>
$ forge --help
$ anvil --help
$ cast --help