[Security notice] Insufficient coverage for Soroban signature payload #1899
dmkozh
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
TL;DR recommendations:
require_auth_for_args, include the authorizer address into payload (e.g.user.require_auth_for_args((user, foo))instead ofuser.require_auth_for_args((foo,)))Summary
Due to a design oversight the standard signature payload for Soroban authorization entries is missing the address of the signer. In majority of scenarios this is not an issue, because the signer is still included in the final signature payload via the call tree (for example, the transfer source is always a part of the
transferfunction payload). However, there are scenarios where the signer address is omitted from the call arguments. For example, the SAC admin functions such asmintfetch the admin address directly from storage and callrequire_authfor it - admin address won't end up in the final signature payload at all. In these scenarios, if multiple accounts reuse the same signer key, then there is a potential for replaying the transactions that have already been authorized by one of the accounts.To the best of our understanding, the most obvious vulnerable scenarios have not occurred on-chain. However, there is still a risk that there are some scenarios that are hard to detect. Also, until this has been fixed at the protocol level, there is always a risk of users being vulnerable due to sharing the private keys between the accounts.
Potential impact examples
Unsafe SAC admin rotation
Ais an admin of a SAC token contract. The public keyAis also its own only signer.2 G-account
Bis another account thatAowns.Bhas two signer public keys:B(weight1) andA(weight1).Balso has default operation thresholds, so either keyA, or keyBcan be used to authorize any Soroban operation.Amints some of their token totoaddress:SAC.mint(to, 1000).Auses Soroban auth payload withAddresscredentials and signature expiring 1 day from now.Aswitches the SAC admin to becomeB:SAC.set_admin(B)SAC.mint(to, 1000)call by taking signature from step 3 and switching the address in credentials fromAtoB. This happens because nonce used in signature has been previously associated withA.Notice, that every nuance is important in the above sequence. For example, there is no vulnerability if
Buses signature threshold of2(instead of1), or if admin change happens after the signatures fromAhave expired, or if there is no admin rotation at all etc.Unsafe
require_auth_for_argsusagecustom_tokencontract)transfer(from, to, amount)function instead of callingfrom.require_auth()(equivalent tofrom.require_auth_for_args((from, to, amount))they callfrom.require_auth_for_args((to, amount))AandBthat both share the same signer keyKwith weight 1 and medium signature threshold of 1.custom_token.transfer(A, to, 1000)using Soroban auth payload withAddresscredentialscustom_token.transfer(B, to, 1000)Mitigation
A protocol change is planned for protocol 27 that will ensure that credentials owner is included in the default signature payload and there is no way to accidentally miss it.
As protocol 27 will not be available for the next few months, the following precautions can be taken in the meantime:
For account owners:
For contract developers:
require_authis sufficientrequire_auth_for_argsinclude the authorizer address into payload (e.g.user.require_auth_for_args((user, foo))instead ofuser.require_auth_for_args((foo,)))require_auth_for_args(similarly to the example above). This might be a reasonable precaution for the new contracts, but use your judgement for updating the existing contracts (as the probability of the users to be vulnerable is rather low)Beta Was this translation helpful? Give feedback.
All reactions