3
Security
s.zotov edited this page 2026-04-12 20:46:43 +00:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Пользователи и безопасность

Управление пользователями

Создание (admin only)

  • Логин: уникальный, непустой
  • Пароль: минимум 8 символов
  • Роль: admin или user
  • Проекты: массив доступных проектов (сейчас всегда ['vipavenue'])

Роли

Возможность admin user
Сборка писем
Рендер превью
FTP загрузка
Spell/Link check
Изменение settings
Изменение config (Yonote, URLs)
Управление пользователями

Пользовательские настройки

Сохраняются в профиле на сервере (PUT /api/auth/preferences):

  • theme — 'light' / 'dark'
  • activePage — последняя активная страница
  • previewZoom — масштаб превью (40-100)

Аудит безопасности (апрель 2026)

1. Pug Template Injection (CRITICAL → FIXED)

Проблема: Пользовательский текст #{process.cwd()} в поле блока попадал в let.pug и выполнялся на сервере при Pug-компиляции. Потенциально: #{require('child_process').execSync('rm -rf /')}.

Фикс: Экранирование #{ и !{ в пользовательском pug перед записью в файл:

const pug = String(body.pug || '').replace(/([#!])\{/g, '$1\\{')

Не ломает миксины — +spacerLine(40) не использует интерполяцию.

Preheader дополнительно очищается от \r\n\\для предотвращения выхода из строки+preheader("...")`.

2. XSS через iframe превью (HIGH → FIXED)

Проблема: <script>alert('XSS')</script> в HTML письма выполнялся в iframe без sandbox. Алерт выполнялся в контексте aspekter.ru — доступ к cookies, DOM основной страницы.

Фикс:

  • sandbox="allow-same-origin" на iframe — скрипты заблокированы, DOM доступен
  • Click detection переписан: вместо инъекции <script> в iframe, listener навешивается через contentDocument.addEventListener()

3. FTP Path Traversal (HIGH → FIXED)

Проблема: Параметр folder в FTP-эндпоинтах не проверялся. folder: "../../etc" позволял писать/читать/удалять файлы в любой директории FTP-сервера.

Фикс: Валидация во всех 3 эндпоинтах (upload, list, delete):

if (/\.\./.test(folder) || folder.startsWith('/')) return send(400, { error: 'Недопустимый путь папки' })

4. Незащищённые настройки (HIGH → FIXED)

Проблема: Любой залогиненный юзер мог подменить:

  • FTP-креды на свой сервер (перехват картинок)
  • feedUrl (SSRF)
  • Yonote-токен (DoS / exfiltration)

Фикс: Проверка роли admin:

if (req.user?.role !== 'admin') return send(403, { error: 'Только admin может менять настройки' })

На PUT settings и PUT config.

5. SSRF (MEDIUM → FIXED)

Проблема: isPublicUrl() не блокировал:

  • IPv6: http://[::1]/
  • Hex IP: http://0x7f000001/
  • Zero: http://0.0.0.0/
  • DNS rebinding

Фикс:

function isPublicUrl(url) {
  if (!/^https?:\/\//i.test(url)) return false
  if (/^https?:\/\/(localhost|127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|0\.|0x|\[|::)/i.test(url)) return false
  try { const u = new URL(url); if (u.hostname === '0.0.0.0' || u.hostname.includes(':') || u.hostname.includes('[')) return false } catch { return false }
  return true
}

6. Render DoS (MEDIUM → FIXED)

Проблема: Неограниченные параллельные рендеры → каждый спавнит Node-процесс с таймаутом 120 сек → OOM.

Фикс: MAX_CONCURRENT_RENDERS = 3, обёрнуто в try/finally:

activeRenders++
try {
  // ... render logic ...
} finally { activeRenders-- }

Первая реализация без try/finally приводила к застреванию счётчика.

7. Info Disclosure (LOW → FIXED)

Проблема: stderr от рендер-процесса возвращался юзеру (пути файлов, версии, стектрейсы).

Фикс: Статическое сообщение: 'Ошибка генерации. Проверьте PUG-шаблон.'

8. Nginx — блокировка сканеров

location ~* (\.env|\.git|\.php|\.sql|passwd|wp-admin|wp-login|phpmyadmin|cgi-bin) {
    return 444;
}

444 — nginx закрывает соединение без ответа.

Что НЕ закрыто (low priority)

  • Typograf/speller прокси без авторизации (могут использовать сервер как прокси)
  • /uploads/ доступен без авторизации (by design — картинки для email)
  • CSRF при отсутствии Origin+Referer (SameSite=Strict спасает)
  • X-Forwarded-For spoofing (за nginx, low risk)
  • DNS rebinding в SSRF (маловероятно — feedUrl меняет только admin)