Submitted by immeas, also found by AkshaySrivastav, cryptonue, saian, minhquanym, 0xDecorativePineapple, gzeon, ey88, wait, pauliax, HollaDieWaldfee, jayphbee, bin2chen, evan, reassor, fs0c, hihen, hansfriese, 0x52, and aviggiano
https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L67-L68
https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L81-L88
First, lets go over how a buy happens.
A buyer can buy NFTs at a higher price and then once the auction ends they can use refund() to return the over payments. The effect is that they bought the NFTs at the lowest price (Lowest Price Dutch Auction).
Now, lets move on to what happens when the sale ends:
The sale is considered ended when the last NFT is sold which triggers the payout to the seller and fee collector:
Earlier theres also a check that you cannot continue buying once the currentId has reached finalId:
However, it is still possible to buy 0 NFTs for whichever price you want even after the sale has ended. Triggering the end of sale snippet again, since newId will still equal temp.finalId.
The attacker, saleReceiver (or feeReceiver), buys 0 NFTs for the delta between totalSale and the balance still in the contract (the over payments by buyers). If there is more balance in the contract than totalSales this can be iterated until the contract is empty.
The attacker has then stolen the over payments from the buyers.
A buyer can mitigate this by continuously calling refund() as the price lowers but that would incur a high gas cost.
saleReceiver or feeReceiver can steal buyers over payments after the sale has ended. Who gains the most depends on circumstances in the auction.
PoC test in test/LPDA.t.sol:
VS Code, Forge
I can think of different options of how to mitigate this:
mehtaculous (Escher) disagreed with severity 
