Open code423n4 opened 1 year ago
hansfriese marked the issue as satisfactory
hansfriese marked the issue as primary issue
Your write-up and jurisdictional tax argument is appreciated as a good read, though I argue that
This function is called by the S1 Citizen contract to emit BYTES to callers based on their state from the staker contract.
Doesn't mean anything regarding a direct call to BYTES2 claiming being a bug. In fact, we even documented this in the contest README.
BYTE2.getReward, which is intended to be the primary entry-point for a staking caller to claim earned BYTES 2.0 tokens.
Given the fact that this guard doesn't result in anyone actually losing money, I don't think this is a medium severity issue. This is a QA concern at best.
As a side note: this guard was intentionally removed to save gas when calling getReward
. We are of the mind that there is no point in optimizing for jurisdictional tax concerns at the expense of global gas concerns. Given the push nature of payments in Ethereum, if I wanted to troll you for tax purposes, I have infinite options at my disposal regardless of any mitigation in this contract. I am not an accountant. I've consulted enough accountants to know that this remains a grey area with actively-evolving tax policy and the situation you described may not necessarily be interpreted that way by, for example, the United States IRS.
TimTinkers marked the issue as disagree with severity
Thanks for your detailed explanation.
hansfriese changed the severity to QA (Quality Assurance)
hansfriese marked the issue as grade-a
TimTinkers marked the issue as sponsor acknowledged
Lines of code
https://github.com/code-423n4/2023-03-neotokyo/blob/main/README.md?plain=1#L79 https://github.com/code-423n4/2023-03-neotokyo/blob/main/README.md?plain=1#L122 https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/s1/NTCitizenDeploy.sol#L1621-L1625 https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/NeoTokyoStaker.sol#L1414-L1416 https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/BYTES2.sol#L109-L110 https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/BYTES2.sol#L114-L129
Vulnerability details
The access control mechanism is badly implemented on
BYTES2.getReward()
. Due to that, anyone can triggerNeoTokyoStaker.claimReward
for any staker at any time. This behaviour/flow isn't documented and this new flow was actually made to replace the old one, which could only claim onmsg.sender
's behalfImpact
Acting over someone's money without their consent.
Proof of Concept
1) Notice that
NeoTokyoStaker.claimReward
can only be called byBYTES
:2) Notice with the following comment that it's expected that
BYTES2.getReward()
should be access controlled:3) Notice that the whole
BYTES2.getReward()
method isn't access controlled. The call toNeoTokyoStaker.claimReward
will be made from theBYTES2
contract's context, meaning thatmsg.sender == BYTES
will betrue
for any caller insideNeoTokyoStaker.claimReward
:4) Notice that, in the tests,
getReward()
is always called throughNTCitizenDeploy
, whereas nothing prevents a direct call toNTBytes2_0
, but we never see such a direct call being made in those tests.5) Inject the following coded POC:
6) Inject the following
console.log
s fromhardhat
to see what's happening:7) Run
npx hardhat test
and see the following output:For a better readability on the fact that the balances are increasing every time with the Attacker's calls, here are the values grouped together:
What's happening here is that an attacker is spamming
BYTES2.getReward()
over 1000 transactions to trigger the reward-claiming mechanism for Bob.While the assets aren't at risk (Bob is still receiving the right amount of rewards, albeit not when he choses to), this is an unexpectedly opened flow that can be very annoying to users and that can be damaging in unforeseen ways.
A staker might have their own reasons and restrictions (taxes, accounting, strategy) for not claiming their reward before a certain date. Imagine the following:
BYTES2.getReward()
with Alice's address during December 31st 2023, forcing her to claim her rewardsWhile the above might look funny, this is still in the domain of acting over someone's money without their consent.
Another scenario, more technical, might be one of a bot written by Alice which would be waiting for a certain threshold of rewards to be crossed before claiming it and moving it strategically to maximize her profits. However, the unexpected multiple claimings from unknown entities would prevent her from doing what she wanted to do with her bot.
Tools Used
Manual review
Recommended Mitigation Steps
Direct calls to
BYTES2.getReward()
are used at multiple places in the code (in bothNTCitizenDeploy
andNeoTokyoStaker
), which are fine with the flows documented. However, giving the possibility for anyone to claim on behalf of anyone is wrong, especially if the stakers aren't aware of this possibility. If one wanted to argue that this was a feature and not a bug, this should've been fully documented.Consider only allowing Citizens, the Staker contract and
msg.sender == _to
to callBYTES2.getReward()
: