However, there is no nonce field in the StructHash.Order. That means that the same hashed order can be reused multiple times. What's also important is that the order is used only to verify if it was signed by the user, however it's not checked if the order that user signed is the same as the params passed by the operator, because ExecuteParams sent to V3Automation.execute() use other set of params to indicate the action to perform:
struct ExecuteParams {
Action action;
Protocol protocol;
INonfungiblePositionManager nfpm;
uint256 tokenId;
uint128 liquidity; // liquidity the calculations are based on
// target token for swaps (if this is address(0) no swaps are executed)
address targetToken;
uint256 amountIn0;
// if token0 needs to be swapped to targetToken - set values
uint256 amountOut0Min;
bytes swapData0;
//[...]
// user signed config
StructHash.Order userOrder;
bytes orderSignature;
}
And later in the function, params.userOrder is not used, only the params prepared by the operator:
In the test above, emptyUserConfig - being really empty, i.e. consisting of only 0s, because it's not being set - is being signed and the order successfully executes, even though the action adds liquidity. That means that the deadline, liquidity to get, recipient of the funds, amount , etc. - basically all parameters that user signs are all independent from what is being send on-chain, and can be totally different.
To summarize, there are multiple occassions, where no nonce and actually no verification of execute params confirmity to uder signed order can be exploited:
Purposeful action
private operator key compromise
Accidental action
backend api breach
software failure leading to sending other orders than users signed
sending the same order multiple times due to local database failure
Lines of code
https://github.com/code-423n4/2024-06-krystal-defi/blob/f65b381b258290653fa638019a5a134c4ef90ba8/src/V3Automation.sol#L79-L81 https://github.com/code-423n4/2024-06-krystal-defi/blob/f65b381b258290653fa638019a5a134c4ef90ba8/src/StructHash.sol#L274-L296
Vulnerability details
Impact
User signed orders can be replayed
Proof of Concept
All orders managed by Krystal team are signed by the user, executed by operator and the signature is verified on-chain:
However, there is no nonce field in the StructHash.Order. That means that the same hashed order can be reused multiple times. What's also important is that the order is used only to verify if it was signed by the user, however it's not checked if the order that user signed is the same as the params passed by the operator, because ExecuteParams sent to
V3Automation.execute()
use other set of params to indicate the action to perform:And later in the function,
params.userOrder
is not used, only the params prepared by the operator:Actually, this behaviour is shown in test/integration/V3Automation.t.sol:
In the test above,
emptyUserConfig
- being really empty, i.e. consisting of only 0s, because it's not being set - is being signed and the order successfully executes, even though the action adds liquidity. That means that thedeadline
,liquidity
to get,recipient
of the funds,amount
, etc. - basically all parameters that user signs are all independent from what is being send on-chain, and can be totally different.To summarize, there are multiple occassions, where no nonce and actually no verification of execute params confirmity to uder signed order can be exploited:
Tools Used
Manual Review
Recommended Mitigation Steps
Introduce
Assessed type
Invalid Validation