Insecure executeTransaction
id: LS14C
title: Insecure executeTransaction
baseSeverity: C
category: access-control
language: solidity
blockchain: [ethereum, polygon, bsc, arbitrum, optimism]
impact: Arbitrary function execution and asset theft
status: draft
complexity: low
attack_vector: internal
mitigation_difficulty: easy
versions: [">=0.4.0", "<=0.8.25"]
cwe: CWE-306: Missing Authentication for Critical Function
swc: SWC-105: Unprotected Function
📝 Description
- The executeTransaction() function is typically used to perform arbitrary external calls—common in Gnosis-style multisigs, DAOs, or admin upgrade logic.
- If this function is not protected by strict access control (e.g., onlyOwner, onlyGovernance), any user can execute arbitrary transactions from the contract’s balance or privileges.
- This leads to:
- Complete takeover of contract logic
- Transfer of funds or NFTs to attacker
- Governance bypass or upgrade hijack
🚨 Vulnerable Code
function executeTransaction(address target, uint256 value, bytes calldata data) external {
(bool success, ) = target.call{value: value}(data); // ❌ no access control
require(success, "Tx failed");
}
🧪 Exploit Scenario
Step-by-step exploit process:
- An attacker finds a protocol with an unprotected executeTransaction() function.
- This causes the contract to transfer tokens it holds to the attacker's address.
- Alternatively, the attacker could upgrade proxy logic by calling upgradeTo() or take admin control of other modules.
Assumptions:
- The function is externally callable by any address (external or public).
- There is no access control modifier (e.g., onlyOwner, onlyGovernance).
- The contract holds assets, interacts with privileged logic, or manages critical infrastructure (e.g., proxy admin, governance router).
- No checks exist to restrict the target address or the calldata payload.
✅ Fixed Code
function executeTransaction(address target, uint256 value, bytes calldata data) external onlyOwner {
(bool success, ) = target.call{value: value}(data);
require(success, "Tx failed");
}
🧭 Contextual Severity
- context: "Public contract with unrestricted executeTransaction"
severity: C
reasoning: "Allows full takeover or fund theft with one transaction."
- context: "Governance-gated execution with hashing or delay"
severity: M
reasoning: "Mitigated by proper proposal queueing and voting flow."
- context: "Internal tool or dev-only testnet function"
severity: L
reasoning: "Low exposure if not publicly callable or deployed with funds."
🛡️ Prevention
Primary Defenses
- Apply onlyOwner, onlyGovernance, or onlyMultisig modifiers to all arbitrary call functions.
- Use OpenZeppelin's AccessControl or Ownable to manage privileged access.
- Consider adding proposal queuing via TimelockController.
Additional Safeguards
- Whitelist target contracts or method signatures.
- Log every execution with transparent ExecuteTransaction events.
- Require multi-sig or DAO approval before sensitive actions.
Detection Methods
- Slither: unprotected-function, dangerous-low-level-call
- Manual review of functions using call, delegatecall, or executeTransaction
- Foundry fuzzing and symbolic execution tools (MythX, Manticore)
🕰️ Historical Exploits
- Name: xToken Arbitrary Execution Bug
- Date: 2021-05
- Loss: ~$25,000,000
- Post-mortem: Link to post-mortem
📚 Further Reading
✅ Vulnerability Report
id: LS14C
title: Insecure executeTransaction Function
severity: C
score:
impact: 5
exploitability: 4
reachability: 4
complexity: 2
detectability: 4
finalScore: 4.25
📄 Justifications & Analysis
- Impact: Enables full fund drain, logic hijack, or proxy upgrades.
- Exploitability: Can be exploited via any unprivileged EOA or contract if unprotected.
- Reachability: Found in many governance or dev tooling contracts.
- Complexity: Low to moderate—any attacker can craft calldata.
- Detectability: Highly visible in code reviews and static analysis.