This agent detects when someone with the executor role could escalate privileges and become an admin of the TimelockController. The agent is separated into four threads:
- First thread detects the
RoleGranted(bytes32,address,address)
event for the executor and creates an alert when the executor gets the proposer or admin roles. - Second thread detects when the TimelockController vulnerability is exploited as it is described in the post-mortem.
- Third thread detects untrusted executors as it is described in the post-mortem.
- Fourth thread detects the
RoleRevoked(bytes32,address,address)
event and provides alerts in 3 different situations:- The contract lost its
Admin
role. - The function
renounceRole()
was used to revoke its own role. - There is common
RoleRevoked(bytes32,address,address)
event.
- The contract lost its
- Ethereum
You can specify roles signatures in the src/utils.py
:
class Roles(Enum):
admin = "0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5"
executor = "0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63"
proposer = "0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1"
TIMELOCK-ZERO-DELAY
- Fired when
MinDelayChange(uint256,uint256)
event hasnewDuration
argument equal to zero - this means that someone set TimelockController Minimum Delay to zero - Severity is always set to
Info
- FindingType is always set to
Suspicious
- Metadata:
contract
- TimelockController addresstx_hash
- hash of the transactionold_delay
- previous delay
- Fired when
TIMELOCK-EXPLOIT
- Fired when malicious executor set Minimum Delay to zero, executes the proposal and after that schedules the proposal with the same id
- Severity is always set to
Critical
- FindingType is always set to
Exploit
- Metadata:
contract
- TimelockController addresstx_hash
- hash of the transactionexecutor
- executor addressproposal_id
- proposal id
TIMELOCK-PROPOSAL-LIFECYCLE-VIOLATION
- Fired when proposal was scheduled after the execution, but Minimum Delay was not changed
- Severity is always set to
Critical
- FindingType is always set to
Suspicious
- Metadata:
contract
- TimelockController addresstx_hash
- hash of the transactionexecutor
- executor addressproposal_id
- proposal id
TIMELOCK-EXECUTOR-PROPOSER
- Fired when executor gets the proposal role
- Severity is always set to
Medium
- FindingType is always set to
Suspicious
- Metadata:
contract
- TimelockController addresstx_hash
- hash of the transactionexecutor
- executor addressinitiator
- initiator address
TIMELOCK-EXECUTOR-ADMIN
- Fired when executor gets the admin role
- Severity is always set to
Critical
- FindingType is always set to
Suspicious
- Metadata:
contract
- TimelockController addresstx_hash
- hash of the transactionexecutor
- executor addressinitiator
- initiator address
TIMELOCK-UNTRUSTED-EXECUTOR
- Fired when executor has not Proposer role
- Severity is always set to
High
- FindingType is always set to
Suspicious
- Metadata:
contract
- TimelockController addressexecutor
- executor address
TIMELOCK-ADMIN-REVOKED
- Fired when contract loses its
Admin
role - Severity is always set to
High
- FindingType is always set to
Suspicious
- Metadata:
contract
- TimelockController addresstx_hash
- hash of the transactionfrom
- transaction sender address
- Fired when contract loses its
TIMELOCK-ROLE-RENOUNCED
- Fired when address renounced its own role
- Severity is always set to
Medium
- FindingType is always set to
Info
- Metadata:
contract
- TimelockController addresstx_hash
- hash of the transactionfrom
- transaction sender addressrole
- hex-role
TIMELOCK-ROLE-REVOKED
- Fired when there is normal
RoleRevoke
event in the log - Severity is always set to
Medium
- FindingType is always set to
Info
- Metadata:
contract
- TimelockController addresstx_hash
- hash of the transactionfrom
- transaction sender addressrole
- hex-roleaccount
- target account
- Fired when there is normal
- Python:
3.10
npm start
This attack has not, as far as we know, been executed on chain.
You can test the agent using
npm test
There are 11 test that should pass:
test_returns_main_exploit_finding()
test_dont_return_main_finding_if_executed_after_schedule()
test_return_only_proposal_lifecycle_violation_finding_if_min_delay_was_not_changed()
test_return_zero_delay_finding()
test_return_executor_got_proposer_role_finding()
test_return_executor_got_admin_role_finding()
test_returns_untrusted_executor_finding()
test_returns_zero_findings_if_executor_has_proposer_role_and_everything_is_ok()
test_return_contact_lost_selfadministration_finding()
test_return_renounced_finding()
test_return_role_revoked_finding()
For these purposes, web3 and event mocks are used. You can check web3 mock in src/test/web3_mock.py
and events mocks in src/test/agent_test.py