# Attacker can steal the amount collected so far in the GroupBuy for NFT purchase.

GroupBuy.purchase() trusts user-supplied _market contract, letting an attacker route raised ETH to themselves and return a fake vault address that passes ownership checks.

- Payout: Contest reward
- Severity: high
- Program: Tessera
- Credited: Trust
- Disclosed: 2023-04-18
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-12-tessera-findings/issues/47
- Library page: https://bugbountylibrary.com/r/c4%3A2022-12-tessera%3AH-08
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** GroupBuy.sol purchase() / MarketBuyer integration
**Root cause:** The _market address passed to purchase() is never validated or whitelisted; the contract trusts whatever IMarketBuyer(_market).execute() returns as the vault and only checks that this returned address owns the NFT.
**Steps:**
1. Group raises funds for NFT X in a pool.
2. Attacker calls `purchase()` supplying a malicious contract as `_market`.
3. Malicious `execute{value:_price}(_purchaseOrder)` forwards/keeps the ETH and returns an address the attacker controls that holds NFT X (or just returns the current NFT owner).
4. Ownership check `IERC721(_nftContract).ownerOf(_tokenId) != vault` passes since returned address owns the NFT.
5. Attacker keeps the raised funds (`minReservePrices[_poolId] * filledQuantities[_poolId]`).
**How found:** Code reading of purchase(): noticed _market is a user-supplied parameter with no whitelist/registry check, and that the only post-call validation is that the returned vault owns the NFT — trivially satisfiable by an attacker-controlled contract.
**Exploitation:** Attacker deploys a fake MarketBuyer whose execute() either buys the NFT into their own wallet or simply returns the existing NFT owner address, then calls purchase() with it; the contract's ownership verification passes and the pooled ETH is diverted to the attacker.
**Insight:** When a function takes a contract address as a parameter and trusts its return value as an identity/ownership proof, check whether that address is constrained (whitelist/registry). Any 'verify X owns Y' check is bypassable if the attacker controls X's resolution.
