# Access restrictions on CompoundToNotionalV2.notionalCallback can be bypassed

CompoundToNotionalV2.notionalCallback trusts an attacker-supplied sender parameter, letting anyone pass sender=this and grief victims' approved tokens into the contract.

- Payout: Audit contest
- Severity: high
- Program: Notional
- Found by: cmichel
- Disclosed: 2021-10-01
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2021-08-notional-findings/issues/69
- Library page: https://bugbountylibrary.com/r/c4%3A2021-08-notional%3AH-04
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:auth-bypass

**Target:** CompoundToNotionalV2.notionalCallback (Solidity contract)
**Root cause:** The callback's authorization check compares a caller-supplied `sender` argument to address(this) instead of using msg.sender, so any external caller can satisfy it by passing sender=this.
**Steps:**
1. Victim approves CompoundToNotionalV2 for a token (first tx of legitimate flow).
2. Attacker front-runs the victim's actual transaction.
3. Attacker calls `notionalCallback(sender=address(this), account=victim, callbackData=...)` with a malicious `cTokenBorrow` contract.
4. Malicious cTokenBorrow.underlying() returns victim's approved token; transferFrom(account, address(this), amount) pulls victim funds.
5. Malicious repayBorrowBehalf is a no-op, so tokens stay stuck in the contract.
**How found:** Code reading of the callback's require(sender == address(this)) check, noticing sender is a function parameter the attacker controls rather than msg.sender.
**Exploitation:** Attacker front-runs a victim's approval-then-call sequence, invoking notionalCallback directly with sender=this and a malicious cTokenBorrow whose underlying() returns the victim's approved token and whose repayBorrowBehalf is a no-op, causing the victim's tokens to be transferred into the contract and burned.
**Insight:** When a callback authenticates via a passed-in `sender`/`from` argument, check whether that argument is attacker-chosen; compare against msg.sender instead. Also watch approve-then-call flows as front-running griefing surface.

