Table of Contents
- Пользователи и безопасность
- Управление пользователями
- Аудит безопасности (апрель 2026)
- 1. Pug Template Injection (CRITICAL → FIXED)
- 2. XSS через iframe превью (HIGH → FIXED)
- 3. FTP Path Traversal (HIGH → FIXED)
- 4. Незащищённые настройки (HIGH → FIXED)
- 5. SSRF (MEDIUM → FIXED)
- 6. Render DoS (MEDIUM → FIXED)
- 7. Info Disclosure (LOW → FIXED)
- 8. Nginx — блокировка сканеров
- Что НЕ закрыто (low priority)
Пользователи и безопасность
Управление пользователями
Создание (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)