"""This module contains the detection code for unauthorized ether
withdrawal."""
import logging
from copy import copy
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.analysis.potential_issues import (
get_potential_issues_annotation,
PotentialIssue,
)
from mythril.laser.ethereum.transaction.symbolic import ACTORS
from mythril.analysis.swc_data import UNPROTECTED_ETHER_WITHDRAWAL
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.analysis import solver
from mythril.exceptions import UnsatError
from mythril.laser.smt import UGT
log = logging.getLogger(__name__)
DESCRIPTION = """
Search for cases where Ether can be withdrawn to a user-specified address.
An issue is reported if there is a valid end state where the attacker has successfully
increased their Ether balance.
"""
[docs]class EtherThief(DetectionModule):
"""This module search for cases where Ether can be withdrawn to a user-
specified address."""
name = "Any sender can withdraw ETH from the contract account"
swc_id = UNPROTECTED_ETHER_WITHDRAWAL
description = DESCRIPTION
entry_point = EntryPoint.CALLBACK
post_hooks = ["CALL", "STATICCALL"]
[docs] def reset_module(self):
"""
Resets the module by clearing everything
:return:
"""
super().reset_module()
def _execute(self, state: GlobalState) -> None:
"""
:param state:
:return:
"""
potential_issues = self._analyze_state(state)
annotation = get_potential_issues_annotation(state)
annotation.potential_issues.extend(potential_issues)
def _analyze_state(self, state):
"""
:param state:
:return:
"""
state = copy(state)
instruction = state.get_current_instruction()
constraints = copy(state.world_state.constraints)
constraints += [
UGT(
state.world_state.balances[ACTORS.attacker],
state.world_state.starting_balances[ACTORS.attacker],
),
state.environment.sender == ACTORS.attacker,
state.current_transaction.caller == state.current_transaction.origin,
]
try:
# Pre-solve so we only add potential issues if the attacker's balance is increased.
solver.get_model(constraints)
potential_issue = PotentialIssue(
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=instruction["address"]
- 1, # In post hook we use offset of previous instruction
swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
title="Unprotected Ether Withdrawal",
severity="High",
bytecode=state.environment.code.bytecode,
description_head="Any sender can withdraw Ether from the contract account.",
description_tail="Arbitrary senders other than the contract creator can profitably extract Ether "
"from the contract account. Verify the business logic carefully and make sure that appropriate "
"security controls are in place to prevent unexpected loss of funds.",
detector=self,
constraints=constraints,
)
return [potential_issue]
except UnsatError:
return []
detector = EtherThief()