Освоение искусства безопасности прогрессивных веб‑приложений
Прогрессивные веб‑приложения (PWA) стали де‑факто стандартом для предоставления приложений‑подобного опыта в открытом вебе. Их ключевые преимущества — возможность работы офлайн, push‑уведомления и устанавливаемость — одновременно открывают новые поверхности атаки, с которыми традиционные сайты обычно не сталкиваются. В этой статье мы подробно рассматриваем жизненный цикл безопасности PWA, объединяя жёсткую настройку сети, защиту в runtime и лучшие практики эксплуатации. К концу вы получите чек‑лист, который можно применить к любому проекту, будь то простой новостной читатель или сложная платформa электронной коммерции.
TL;DR: Защитите своё PWA с помощью HTTPS, строгой CSP, изолированных сервис‑воркеров, проверяйте все входные данные и внедряйте непрерывный мониторинг.
1. Транспортный уровень – первая линия обороны
1.1 Принудительное использование HTTPS везде
Все современные браузеры требуют HTTPS для регистрации сервис‑воркера. Однако некоторые ресурсы (например, сторонняя аналитика) могут всё ещё загружаться по HTTP, вызывая предупреждения о смешанном контенте и открывая дверь атакам типа «человек посередине» (MITM).
Ключевые шаги:
- Получите доверенный TLS‑сертификат (Let’s Encrypt, Cloudflare и пр.).
- Перенаправьте все запросы
http://наhttps://через конфигурацию сервера. - Включите HSTS (HTTP Strict Transport Security), чтобы заставить браузеры использовать HTTPS в течение заданного периода.
# Пример конфигурации NGINX
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# TLS‑настройки …
}
Примечание: HSTS устраняет атаки с понижением уровня защиты, но требует тщательного планирования, потому что после установки браузеры откажутся подключаться по HTTP на указанный срок.
1.2 Привязка сертификата (по желанию)
Для особо чувствительных PWA — банковских приложений, систем хранения медицинских данных — рассмотрите привязку сертификата с помощью Public Key Pinning Extension for HTTP (HPKP). Несмотря на то, что HPKP устарел из‑за рисков при развёртывании, аналогичный уровень безопасности можно получить через заголовок Expect‑CT, который заставляет использовать Certificate Transparency.
Expect-CT: max-age=86400, enforce, report-uri="https://example.com/report"
2. Жёсткая настройка сервис‑воркеров
Сервис‑воркеры – сердце офлайн‑возможностей PWA. Они работают в фоновом потоке с повышенными привилегиями, что делает их прекрасной целью для эксплуатации.
2.1 Ограничение области действия (scope)
Указывайте максимально узкий scope при регистрации сервис‑воркера. Это предотвратит перехват запросов за пределами предназначенной области.
navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
.then(reg => console.log('SW зарегистрирован с областью:', reg.scope));
2.2 Проверка целостности с помощью Subresource Integrity (SRI)
Если вы загружаете скрипт сервис‑воркера с CDN, защитите его с помощью SRI, чтобы убедиться, что полученный код совпадает с ожидаемым хешем.
<script src="https://cdn.example.com/sw.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>
2.3 Политика безопасности контента (CSP) для сервис‑воркеров
Строгий CSP может помешать воркеру загружать вредоносные скрипты или подключаться к непредвиденным источникам.
Content-Security-Policy: script-src 'self' https://trusted.cdn.com;
connect-src 'self' https://api.example.com;
Совет: Используйте директиву
worker-src(поддерживается в новых браузерах), чтобы явно указать разрешённые источники скриптов воркеров.
2.4 Управление состоянием и проверка кеша
Никогда не доверяйте записям кеша слепо. Всегда проверяйте актуальность закешированных ответов, особенно для токенов аутентификации или персональных данных.
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (url.pathname.startsWith('/api/')) {
// Обход кеша для динамических данных
event.respondWith(fetch(event.request));
return;
}
// Стратегия «cache‑first» для статических ресурсов
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request))
);
});
3. Защита в runtime – CSP, Permissions Policy и sandbox
3.1 Полноценный CSP
Хорошо продуманный CSP снижает риск Cross‑Site Scripting (XSS) и Data Injection атак. Ниже пример политики, адаптированный под типичное PWA:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'sha256-XYZ' https://analytics.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com wss://socket.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
script-srcсодержит хеш для встроенных скриптов, избавляясь отunsafe-inline.frame-ancestors 'none'отключает кликджеккинг.connect-srcразрешает только нужные API и WebSocket‑конечные точки.
3.2 Permissions Policy (ранее Feature‑Policy)
Ограничьте мощные возможности браузера, которые ваше PWA может случайно унаследовать.
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=()
3.3 Атрибут sandbox для встроенного контента
Если ваше PWA встраивает сторонние iframe, изолируйте их с помощью sandbox, чтобы запретить исполнение скриптов и навигацию.
<iframe src="https://maps.example.com"
sandbox="allow-scripts allow-same-origin"
loading="lazy"></iframe>
4. Аутентификация и авторизация
4.1 Токен‑базированная аутентификация с JWT
Для безсостоятного доступа используйте JSON Web Tokens (JWT), но не храните их в localStorage — используйте Secure, HttpOnly cookies, чтобы уменьшить риск кражи через XSS.
Set-Cookie: token=eyJhbGciOi...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
4.2 Ротация refresh‑токенов
Реализуйте вращающиеся refresh‑токены, чтобы ограничить последствия компрометации токена.
- Клиент отправляет refresh‑токен.
- Сервер проверяет и выдаёт новый access‑токен и новый refresh‑токен.
- Старый refresh‑токен помечается как недействительный в базе.
4.3 Защита от CSRF
Несмотря на использование same‑site куки, применяйте анти‑CSRF токены для запросов, изменяющих состояние.
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
На сервере проверяйте соответствие токена сессии.
5. Безопасное хранилище данных
PWA могут хранить данные через IndexedDB, Cache API и Web Storage. У каждого из этих механизмов свои особенности безопасности.
| Хранилище | Подходящее использование | Советы по безопасности |
|---|---|---|
| Cache API | Статические ресурсы, офлайн‑fallback | Кешировать только неизменяемые файлы. Использовать версии кешей для очистки устаревших данных. |
| IndexedDB | Структурированные данные, пользовательские настройки | Шифровать чувствительные поля на клиенте с помощью Web Crypto API. |
| LocalStorage / SessionStorage | Небольшие, не‑чувствительные данные | Не хранить секреты. Доступен всем скриптам страницы. |
5.1 Пример: шифрование данных в IndexedDB
async function encryptAndStore(key, value) {
const cryptoKey = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(value);
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
cryptoKey,
encoded
);
const db = await openDB('secure-store', 1, {
upgrade(db) { db.createObjectStore('secrets'); }
});
await db.put('secrets', { iv, ciphertext }, key);
}
6. Мониторинг, аудит и реагирование на инциденты
Безопасность — это не разовый набор настроек, а непрерывный цикл.
6.1 Автоматическое сканирование
- Используйте OWASP ZAP или Burp Suite для поиска XSS, CSRF и некорректных заголовков.
- Интегрируйте Lighthouse в CI/CD, чтобы проверять соответствие PWA‑требованиям.
6.2 Логирование в runtime
Перехватывайте ошибки сервис‑воркера и отправляйте их в безопасный эндпоинт для анализа.
self.addEventListener('error', event => {
fetch('https://logs.example.com/collect', {
method: 'POST',
body: JSON.stringify({ message: event.message, stack: event.error.stack })
});
});
6.3 План реагирования на инциденты
- Обнаружение: Тревога от аномального трафика или CSP‑отчётов.
- Сдерживание: Аннулируйте скомпрометированные JWT, отзовите API‑ключи.
- Устранение: Исправьте уязвимый код, обновите кеш сервис‑воркера.
- Восстановление: Разверните новую версию, продолжайте мониторинг.
- Пост‑мортем: Зафиксируйте коренную причину, обновите чек‑лист безопасности.
7. Визуальный обзор – уровни защиты в PWA
flowchart TB
subgraph "Транспортный уровень"
H["\"HTTPS\""]
S["\"HSTS\""]
end
subgraph "Сервис‑воркер"
SW["\"Ограничение области\""]
CS["\"Проверка кеша\""]
end
subgraph "Runtime"
CSP["\"Content Security Policy\""]
PP["\"Permissions Policy\""]
SAN["\"Sandboxed iFrames\""]
end
subgraph "Аутентификация и данные"
JWT["\"Secure Cookies\""]
REF["\"Refresh Rotation\""]
ENC["\"IndexedDB Encryption\""]
end
subgraph "Мониторинг"
SCAN["\"OWASP ZAP\""]
LOG["\"Runtime Logging\""]
end
H --> SW --> CSP --> JWT --> SCAN
S --> SW
CS --> PP
PP --> ENC
LOG --> SCAN
Диаграмма демонстрирует, как каждый контроль безопасности усиливает предыдущий, формируя архитектуру «защита в глубину».
8. Чек‑лист – быстрый справочник по защите PWA
- Обслуживание всего контента по HTTPS с включённым HSTS.
- Регистрация сервис‑воркеров с максимально узким
scope. - Применение Subresource Integrity для всех внешних скриптов.
- Настройка строгой CSP, включая
worker-srcи хеши скриптов. - Отключение ненужных функций через Permissions‑Policy.
- Хранение токенов аутентификации в Secure, HttpOnly cookie; не в localStorage.
- Ротация refresh‑токенов и проверка CSRF‑токенов в запросах, изменяющих состояние.
- Шифрование конфиденциальных данных в IndexedDB.
- Автоматическое сканирование OWASP ZAP на каждом PR.
- Организация реального времени логирования ошибок сервис‑воркера.
- Наличие плана реагирования на инциденты.
9. Часто задаваемые вопросы
В: Нужно ли сервис‑воркер для обеспечения безопасности PWA?
О: Нет. Такие меры, как HTTPS, CSP и защищённые cookie, работают независимо. Однако большинство атак в runtime происходят именно через сервис‑воркер, поэтому ему требуется дополнительное укрепление.
В: Можно ли использовать unsafe-inline в CSP для стилей?
О: По возможности избегайте. Лучше добавить хеш или nonce к встроенным стилям или перенести их во внешние CSS‑файлы.
В: Как часто менять TLS‑сертификаты?
О: Let’s Encrypt выдаёт сертификаты на 90 дней — автоматизируйте их обновление. Для самоподписных или коммерческих сертификатов старайтесь не превышать 1 год срока действия.
10. Заключение
Прогрессивные веб‑приложения стирают границу между нативными и веб‑приложениями, предоставляя мощные возможности, но одновременно расширяя поверхность атаки. Слоями транспортной безопасности, ужесточением областей сервис‑воркеров, строгой CSP и надёжными практиками аутентификации вы сможете защитить своё PWA без потери производительности.
Помните: безопасность — это путь, а не цель. Обновляйте зависимости, регулярно проводите аудит кода и следите за новыми угрозами. Применив изложенные здесь рекомендации, ваше PWA будет устойчиво против большинства типичных веб‑угроз.