Access Control Bypass Exploit Generator
Sigvex exploit generator that validates missing access control by calling privileged functions from an unauthorized address and checking for storage mutations.
Access Control Bypass Exploit Generator
Overview
The access control bypass exploit generator validates findings from the missing-access-control and unprotected_function detectors by invoking a privileged function from a non-owner address and checking whether the transaction reverts. If the call succeeds and produces storage mutations (such as overwriting the owner slot), the finding is confirmed as exploitable.
Missing access control is among the most common critical vulnerabilities in deployed smart contracts. When administrative functions (setOwner, mint, pause, drain) lack a caller check, any address can execute them.
Note: Exploit generation in Sigvex is for vulnerability validation purposes only.
Attack Scenario
- Setup: The victim contract stores the owner address in storage slot 0. A legitimate owner address is set at deployment.
- Trigger: An attacker calls
transferOwnership(address)(selector0xf2fde38b) directly from their address without any prior authorization. The generator also extracts the specific function selector from the finding location when available. - Exploitation: The function lacks an
onlyOwnermodifier or equivalent check. It executes the state change, overwriting the owner slot with the attacker’s address. - Impact: The attacker is now the owner. They can call any owner-gated function: drain funds, mint arbitrary tokens, pause the protocol, upgrade the proxy implementation, or call
selfdestruct.
The attacker contract in the generated PoC is intentionally minimal — no special mechanism is required since the vulnerability is that no mechanism is checked:
interface IVictim {
function transferOwnership(address newOwner) external;
function withdraw() external;
}
contract AccessControlAttacker {
function exploit(address target) external {
IVictim victim = IVictim(target);
// Step 1: Take ownership — no modifier blocks this call
victim.transferOwnership(address(this));
// Step 2: Now that we are owner, drain all funds
victim.withdraw();
}
// Receive stolen ETH
receive() external payable {}
}
Exploit Mechanics
The generator configures a simulated world state with the owner address stored in slot 0, then executes the target function from an unauthorized caller address. The transferOwnership(address) selector is used unless a more specific selector is available from the finding location. Calldata is constructed with the attacker’s address as the argument.
Verdict logic:
- Execution reverts: access control is in place — the finding is a false positive at the exploit stage.
- Execution succeeds and slot 0 is unchanged: potential issue but no confirmed state corruption (confidence 0.60).
- Execution succeeds and slot 0 is mutated to the attacker’s address: confirmed bypass with confidence 0.85.
The generator also checks whether an Ownable contract pattern was detected by static analysis. Contracts with a confirmed ownership pattern produce higher-confidence findings when the access control check is bypassed.
Remediation
- Detector: Access Control Detector
- Remediation Guide: Access Control Remediation
Use OpenZeppelin’s Ownable or AccessControl:
import "@openzeppelin/contracts/access/Ownable.sol";
contract SecureContract is Ownable {
// SECURE: onlyOwner modifier prevents unauthorized calls
function setNewOwner(address newOwner) external onlyOwner {
transferOwnership(newOwner);
}
// SECURE: role-based access for granular permissions
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
}
For proxy contracts, ensure the initialize() function can only be called once and only by the deployer:
function initialize(address admin) external initializer {
__Ownable_init();
transferOwnership(admin);
}