Possible vulnerability in GSN implementation — Ackee Blockchain
The Gas Station Network (“GSN”) helps to execute gas-free transactions, but an incorrect implementation of its functionalities could make any project extremely exploitable. We describe an issue we addressed at Ackee Blockchain during a security assessment of one of our clients.
If we look at GSN documentation a GSN compatible project needs to replace all occurrences of msg.sender
with _msgSender
function.
https://docs.opengsn.org/contracts/#receiving-a-relayed-call
This step is generating a possible risk because with Solidity structure (msg.sender
) every developer can rely on its value. But it’s replaced by a custom function (_msgsender()
) with an arbitrary body (depends on implementation) where we can't say the same.
function _msgSender() internal override virtual view returns (address ret) {
if (msg.data.length >= 20 && isTrustedForwarder(msg.sender)) {
// At this point we know that the sender is a trusted forwarder,
// so we trust that the last bytes of msg.data are the verified sender address.
// extract sender address from the end of msg.data
assembly {
ret := shr(96,calldataload(sub(calldatasize(),20)))
}
} else {
ret = msg.sender;
}
}
As can be seen on code snippet, if:
- transaction
msg.data
length is higher or equal 20, - and
msg.sender
is a trusted forwarder,
it will return the last 20 bytes of msg.data
.
But there can be literally anything!
We’ve discovered this during an audit where the project had setTrustedForwarder
function set as a public, so anyone could have set up anyone as a trusted forwarder.
We have created an exploit that was based on altering the msg.data
. Especially, appending the victim's address to the end, so _msgSender
would return a different address than is the original caller.
function executeAttack(address VICTIM_ADDRESS) public returns (bool success, bytes memory returndata) {
... bytes memory data_extension = abi.encodePacked(VICTIM_ADDRESS);
bytes memory data = abi.encodePacked(data_base, data_extension);
(success, returndata) = address(mainContract).call(data);
}
This exploit allowed us to act as any user we wanted and additionally thanks to the architecture of withdrawal functions of the project also stealing funds from any user of the contract.
The issue was immediately reported and hotfixed by the team by using onlyOwner
modifier on setTrustedForwarder
function.
Is it enough?
The vulnerability still exists. The owner of the contract could be an initiator of the attack (consciously or by mistake), so there is still some risk. Even the owner shouldn’t have such a big power, otherwise it should be announced to contract users.
To sum up, actual design of _msgSender
function from GSN is quite unhappy, because even when it is well implemented, it can in some cases significantly affect trust model of the contract.
Originally published at https://ackeeblockchain.com on November 6, 2021.