Безопасность файлового архива: шифрование, антивирусная проверка и разграничение доступа | AdminWiki

Безопасность файлового архива: шифрование, антивирусная проверка и разграничение доступа

24 сентября 2026 15 мин. чтения
Содержание статьи

Файловый архив держит три группы рисков: данные на диске и в резервных копиях может прочитать посторонний, загруженный файл может принести в инфраструктуру вредоносный код, а ссылка на скачивание уходит за пределы компании и открывает документы тем, кому они не предназначены. Каждую группу закрывает свой слой: шифрование при хранении и передаче, антивирусная проверка с карантином, разграничение доступа. Слои работают только в связке. Шифрование защищает носитель, но не мешает скачать файл по украденной ссылке. Антивирус ловит известные сигнатуры и пропускает свежий сэмпл, если обработка идет в том же процессе, что и веб-сервер.

Дальше практика: команды LUKS и GPG для данных на диске, конфиг TLS 1.3 и HSTS для Nginx, схема проверки загрузок через ClamAV с карантином, разграничение доступа по ролям, токенам и временным ссылкам, изоляция разбора файлов в Docker и аудит через auditd и Falco. В финале чек-лист с периодичностью проверок, который переносится в регламент вашей команды.

СлойЧто закрываетИнструменты
ШифрованиеЧтение диска, снапшотов и бэкапов, перехват трафикаLUKS, fscrypt, GPG, TLS 1.3, HSTS, SSE-KMS
Антивирусная проверкаВредоносные и упакованные файлы внутри архиваClamAV (clamd), карантин, YARA
Разграничение доступаЧужие скачивания, слитые ссылки, лишние праваRBAC, auth_request, JWT, secure_link, pre-signed URL
Изоляция и аудитПобег из песочницы, скрытая активность, подмена файловDocker, gVisor, SHA-256, auditd, Falco

Почему файловый архив стал точкой риска и как её закрыть

Архив отличается от обычного сайта тем, что принимает данные от пользователей и отдаёт их обратно. Каждая операция создаёт вектор: загрузка исполняемых файлов, документов с макросами, вложенных ZIP, предсказуемые имена файлов, открытый листинг каталогов, служебные файлы вроде .env и .git в корне раздачи, ссылки без срока действия, отсутствие логов скачивания.

Цифры по доле утечек через файловые хранилища в публичных отчётах расходятся: разные методики подсчёта, периоды и состав выборки. Опирайтесь на собственные данные: лог доступа веб-сервера, счётчики скачиваний, срабатывания антивируса и список публично доступных объектов в хранилище. Эти четыре метрики показывают реальную картину быстрее любого сводного отчёта. Отдельно проверьте, что раздача статики закрыта от утечек служебных файлов и листинга каталогов: готовые конфиги и права на файлы разобраны в материале о безопасности статического контента.

Три слоя защиты: шифрование, антивирус, доступ

Первый слой - шифрование. При хранении это блочное шифрование разделов через LUKS с алгоритмом AES-256-XTS, шифрование каталогов средствами файловой системы (fscrypt на ext4 и f2fs) и отдельные зашифрованные контейнеры для выгрузок. При передаче - TLS 1.3, HSTS и запрет обычного HTTP. Ключи живут отдельно от данных: KMS, HashiCorp Vault, аппаратный токен или keyfile с правами 600.

Второй слой - антивирусная проверка. Каждый загруженный файл попадает во временный каталог, проверяется демоном ClamAV, при детекте уезжает в карантин с правами 700 и без бита выполнения, при чистом результате переезжает в основное хранилище. Сигнатурные базы обновляет freshclam.

Третий слой - разграничение доступа. Принцип наименьших привилегий, роли RBAC для внутреннего архива, JWT для API и мобильных клиентов, временные ссылки secure_link или pre-signed URL для публичной выдачи. Права проверяются на каждый запрос, а не один раз при входе.

Дальше разбираем каждый слой до уровня конфигов: сначала шифрование для Nginx, Linux и облачных хранилищ, затем конвейер проверки загрузок, затем модели доступа.

Шифрование данных при хранении и передаче

Две задачи решаются разными инструментами. Защита данных на диске останавливает того, кто получил физический доступ к серверу, снапшот или копию бэкапа. Защита канала останавливает перехват трафика и подмену файла по пути к клиенту.

Шифрование файлов при хранении: LUKS, fscrypt и GPG

LUKS шифрует блочное устройство целиком, поэтому подходит для отдельного раздела или тома под архив. Создание контейнера, монтирование и резервная копия заголовка выглядят так:

cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 --key-size 512 /dev/sdb1
cryptsetup open /dev/sdb1 archive_crypt
mkfs.ext4 -L archive /dev/mapper/archive_crypt
mount /dev/mapper/archive_crypt /var/archive
cryptsetup luksHeaderBackup /dev/sdb1 --header-backup-file /root/luks-header.img
cryptsetup luksDump /dev/sdb1

Заголовок LUKS храните отдельно от носителя, иначе его разрушение закрывает доступ к данным навсегда. Парольную фразу вводит администратор при загрузке или считывает скрипт из защищенного файла, если сервер перезагружается без участия человека. Для шифрования отдельных каталогов без выделения раздела используйте нативное шифрование файловой системы (fscrypt на ext4 и f2fs) или eCryptfs для уже настроенных инсталляций, но перед выбором проверьте поддержку в вашем ядре и дистрибутиве: от eCryptfs поставщики постепенно уходят.

Отдельные выгрузки и передаваемые архивы шифруйте на уровне приложения, чтобы их можно было безопасно передать по почте или через файлообменник:

tar -czf - /srv/archive | gpg --symmetric --cipher-algo AES256 --output archive.tar.gz.gpg
gpg --decrypt archive.tar.gz.gpg | tar -xzf -
gpg --list-packets archive.tar.gz.gpg | head -20

Потеря ключа равна потере данных, поэтому схему восстановления проверяют до того, как архив наполнится: восстановите тестовый набор из шифрованного контейнера и запишите в регламент, где лежат ключи, кто имеет к ним доступ и как часто они меняются. Шифрование, аудит доступа и работа с ключами на уровне документооборота подробно разобраны в статье о защите документов при хранении.

В облачных хранилищах шифрование включается на стороне сервиса: SSE-S3 дает AES-256 с управляемыми провайдером ключами, SSE-KMS добавляет отдельный ключ и журнал его использования, а клиентское шифрование до загрузки оставляет ключи у вас. Для бакета с архивом задайте шифрование по умолчанию и запретите доступ по незашифрованному транспорту:

{
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": ["arn:aws:s3:::archive-bucket", "arn:aws:s3:::archive-bucket/*"],
  "Condition": {"Bool": {"aws:SecureTransport": "false"}}
}

Публичный доступ к бакету закрывайте отдельным переключателем, а выдачу файлов организуйте через подписанные ссылки, о которых речь ниже.

Шифрование данных при передаче: TLS 1.3 и HSTS в Nginx

Сертификат от Let's Encrypt выпускается двумя командами, дальше его обновляет systemd-таймер:

apt install certbot python3-certbot-nginx
certbot --nginx -d files.example.com
systemctl list-timers | grep certbot
certbot renew --dry-run

Рабочий server-блок с TLS 1.2 и 1.3, stapling и HSTS:

server {
    listen 443 ssl;
    http2 on;
    server_name files.example.com;

    ssl_certificate     /etc/letsencrypt/live/files.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/files.example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;

    client_max_body_size 2g;
    client_body_temp_path /var/tmp/nginx_upload 1 2;
}

Директива ssl_ciphers управляет только TLS 1.2: наборы для TLS 1.3 задаются через ssl_conf_command Ciphersuites. HSTS с max-age 63072000 удерживает браузер на HTTPS два года. Флаг preload добавляйте после того, как убедитесь, что все поддомены отвечают по HTTPS: отзыв из списка preload не мгновенный, и ошибка в поддомене закроет к нему доступ на время ожидания. Проверка конфигурации и заголовков:

nginx -t
openssl s_client -connect files.example.com:443 -tls1_3 -servername files.example.com
curl -sI https://files.example.com | grep -i strict-transport-security

Оставьте постоянный редирект 301 с 80 порта на 443 и не открывайте ни одного внутреннего эндпоинта по HTTP: скачивание архива по незашифрованному каналу обесценивает шифрование на диске. Полный набор заголовков и настройки HTTPS для Nginx и Apache с проверкой в SSL Labs собран в статье о полной защите веб-серверов.

Сертификаты отслеживайте заранее: certbot certificates показывает даты, а мониторинг внешним чекером ловит ошибку обновления до того, как истечет срок. Если архив живет в облаке, размещайте сервис на площадке с приватной сетью и встроенным TLS, например на облачных серверах Timeweb Cloud, и держите бакет закрытым, а доступ выдавайте подписанными ссылками.

Мини-чек-лист по шифрованию: раздел под архив на LUKS или fscrypt; заголовок LUKS сохранен отдельно; ключи в KMS или Vault с ограниченным доступом; на публичном домене включены TLS 1.3, HSTS и редирект с HTTP; шифрование бакета включено по умолчанию; тест восстановления из шифрованного контейнера пройден.

Антивирусная проверка загружаемых файлов и карантин

Конвейер проверки строится из четырех шагов: загрузка во временный каталог, сканирование, карантин при детекте, перенос в хранилище при чистом результате. Синхронная проверка в момент загрузки дает понятную ошибку пользователю, асинхронная через очередь не тормозит интерфейс. Выбирайте по профилю: для документов до сотни мегабайт синхронная схема работает без заметной задержки.

Настройка ClamAV и интеграция с Nginx

apt install clamav clamav-daemon
systemctl stop clamav-freshclam
freshclam
systemctl start clamav-daemon clamav-freshclam
systemctl status clamav-daemon --no-pager

Порядок важен: пока служба freshclam запущена, ручной запуск обновления базы конфликтует с ней за файлы, поэтому службу останавливают, обновляют базы и возвращают на место. Лимиты проверки задаются в /etc/clamav/clamd.conf:

MaxFileSize 200M
MaxScanSize 500M
StreamMaxLength 200M
MaxThreads 8

По умолчанию лимиты рассчитаны на небольшие файлы, и крупный архив clamd молча пропустит: поднимите MaxFileSize, MaxScanSize и StreamMaxLength под свой профиль загрузок. Проверьте демон тестовым файлом EICAR, он должен дать статус FOUND. Ручной запуск сканирования и просмотр журнала:

clamdscan --fdpass --multiscan --move=/var/quarantine /var/tmp/nginx_upload/1/0000000001
clamdscan --version
tail -f /var/log/clamav/clamav.log

Сам Nginx в открытой сборке антивирус не проверяет: модуля проверки в стандартном пакете нет. Рабочие варианты три. Первый: сторонний модуль nginx-clamav, который собирается из исходников и обращается к clamd через сокет. Второй: OpenResty и Lua-скрипт, который получает путь к телу запроса и отправляет файл в clamd командой INSTREAM. Третий, самый предсказуемый: Nginx буферизует тело в файл и передает управление приложению, а приложение или скрипт-обертка вызывает clamdscan до того, как файл попадет в хранилище.

#!/bin/bash
set -euo pipefail
UPLOAD="$1"
if clamdscan --fdpass --no-summary "$UPLOAD"; then
  install -o www-data -g www-data -m 0640 "$UPLOAD" /var/archive/incoming/
  sha256sum "/var/archive/incoming/$(basename "$UPLOAD")" >> /var/log/archive-hashes.txt
else
  chmod 600 "$UPLOAD"
  mv "$UPLOAD" "/var/quarantine/$(date +%s)-$(basename "$UPLOAD")"
  logger -t archive-scan "infected: $UPLOAD"
fi

Дополнительные детекторы подключают там, где сигнатур ClamAV мало: YARA-правила для внутренних форматов, Kaspersky Scan Engine для расширенной эвристики. Отправку файлов во внешний сервис вроде VirusTotal согласуйте с политикой: содержимое покидает ваш периметр, и для внутренних документов это часто неприемлемо.

Организация карантина и изоляция инфицированных файлов

Карантин - отдельный каталог на отдельном разделе, смонтированный без прав на исполнение и создание устройств:

install -d -o clamav -g clamav -m 0700 /var/quarantine
/dev/sdb2 /var/quarantine ext4 defaults,nosuid,nodev,noexec 0 2
find /var/quarantine -type f -mtime +30 -delete

Строку из fstab добавьте в /etc/fstab, чтобы параметры монтирования применялись после перезагрузки. Срок хранения выбирайте по политике: тридцать дней хватает, чтобы разобрать инцидент и отдать файл на анализ. Удаляйте по таймеру, а не вручную, и настраивайте уведомление в почту или мессенджер на каждое срабатывание. В журнал пишите имя файла, пользователя, источник загрузки, хеш SHA-256 и вердикт движка: без хеша невозможно подтвердить, что позже проанализирован тот же самый файл.

Чек-лист по антивирусной проверке: демон clamd запущен и слушает сокет; базы обновляются по расписанию; лимиты размера подняты под профиль загрузок; тест EICAR пройден; все загрузки проходят сканирование до попадания в хранилище; карантин на отдельном разделе без noexec; срок хранения и уведомления заданы; журнал сканирования уходит в центральный сбор логов.

Разграничение доступа к файлам: роли, токены и временные ссылки

Подход выбирают по сценарию. Внутренний архив компании закрывают ролями и корпоративным входом. Публичную выдачу файлов партнерам отдают временными ссылками. API и мобильные клиенты работают с токенами. Правило одно для всех моделей: доступ запрещен по умолчанию, разрешение выдается явно.

Ролевая модель доступа (RBAC) в Nginx и приложении

Роли описывают наборы разрешений, а пользователь получает роль, а не отдельные галочки. Пример матрицы для архива:

РольЧтениеЗагрузкаУдаление
guestтолько публичные файлынетнет
userсвои и расшаренныеда, с квотойтолько свои
operatorвесь внутренний архивдада
adminвесь архивдада, включая карантин

В Nginx проверка выносится на бэкенд директивой auth_request, поэтому роли читаются из базы или LDAP и меняются без перезагрузки веб-сервера:

location /archive/ {
    auth_request /_auth;
    alias /var/archive/;
}

location = /_auth {
    internal;
    proxy_pass http://127.0.0.1:8080/authz;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URI $request_uri;
    proxy_set_header X-User $remote_user;
}

Бэкенд возвращает 200 для разрешенного запроса и 401 или 403 для отказа, а решение принимает по URI из X-Original-URI и роли пользователя. Тело запроса на авторизацию не передается: экономия трафика и меньше поверхности для атаки. Как сочетать такую схему с базовой аутентификацией, ограничениями по IPv4 и IPv6 и правами Linux, разобрано в статье о правах доступа в Nginx.

Токены доступа к файлам: JWT и проверка в Nginx

JWT подходит для API и мобильных клиентов: claims содержат идентификатор пользователя, роль, область прав и время жизни, подпись защищает от подмены. Держите exp коротким, добавляйте jti для аудита и храните приватный ключ вне репозитория. Генерация на Python:

import jwt, time
claims = {"sub": "u-1042", "role": "user", "scope": "files:read", "exp": int(time.time()) + 300, "jti": "8f1c"}
token = jwt.encode(claims, PRIVATE_KEY, algorithm="ES256")

Директивы auth_jwt входят в NGINX Plus: в открытой сборке их нет. В стандартном Nginx проверку токена делает тот же бэкенд через auth_request, а на периметре подойдет OpenResty с Lua-валидацией подписи. Ключи подписи ротируйте, а старый ключ держите в доверенных только на время жизни выданных токенов. Проверка запроса:

curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN" https://files.example.com/archive/report.pdf

Модуль secure_link проверяет подпись и срок жизни ссылки, не храня состояние на сервере. Конфигурация:

location /files/ {
    secure_link $arg_md5,$arg_expires;
    secure_link_md5 "$secure_link_expires$uri$remote_addr archive-secret";
    if ($secure_link = "") { return 403; }
    if ($secure_link = "0") { return 410; }
    alias /var/archive/;
}

Код 403 означает неверную подпись, 410 - истекший срок. Порядок частей в secure_link_md5 на стороне Nginx и в генераторе подписи должен совпадать буквально, включая секретное слово и remote_addr. Если привязка к IP не нужна, уберите $remote_addr из обоих мест. Генерация ссылки:

import base64, hashlib, time
secret = "archive-secret"
expires = int(time.time()) + 900
uri = "/files/report.pdf"
addr = "203.0.113.10"
raw = f"{expires}{uri}{addr} {secret}".encode()
sig = base64.urlsafe_b64encode(hashlib.md5(raw).digest()).decode().rstrip("=")
print(f"https://files.example.com{uri}?md5={sig}&expires={expires}")

Время жизни подбирайте по типу файла: для разовой выгрузки хватит нескольких минут, для пакетного экспорта партнеру давайте час или сутки и логируйте каждое скачивание. В облаках роль secure_link играют pre-signed URL: команда aws s3 presign с параметром expires-in или generate_presigned_url в boto3, а в GCP gcloud storage sign-url с флагом duration. Помните, что подписанную ссылку можно переслать: чем короче срок и уже права, тем меньше ущерб.

ПодходСценарийОграничение
RBAC через auth_requestВнутренний архив, корпоративный входНужен бэкенд авторизации
JWTAPI, мобильные клиентыОтзыв токена сложнее, чем смену роли
secure_link и pre-signed URLВыдача партнерам, публичные файлыСсылку можно переслать до истечения срока
Одноразовый токен в базеЧувствительные документыТребует состояния и очистки

Чек-лист по доступу: запрет по умолчанию включен; роли описаны матрицей и хранятся централизованно; TTL токенов и ссылок задан; секреты подписи в Vault с ротацией; скачивания логируются с указанием пользователя, IP и файла; публичные ссылки живут минуты, а не месяцы.

Как закрыть возражение «а вдруг через архив что-то протащат в инфраструктуру»

А вдруг через архив что-то протащат в инфраструктуру?

Сигнатурный антивирус не ловит свежие сэмплы и специально собранные под ваш стек файлы, поэтому защита строится с допущением, что вредоносный файл уже внутри. Тогда задача сводится к тому, чтобы он ничего не смог сделать: разбор и проверка идут в одноразовом контейнере без сети, с ограничениями по памяти, процессам и файловой системе, а целостность и активность вокруг архива контролируются логами и правилами обнаружения.

Изоляция обработки файлов в контейнерах и песочницах

docker run --rm \
  --network none \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=512m \
  --memory 512m --cpus 1 --pids-limit 128 \
  --cap-drop ALL --security-opt no-new-privileges \
  -v /var/tmp/upload:/scan:ro \
  clamav/clamav:stable clamdscan --fdpass --no-summary /scan/file.zip

Флаг network none отрезает канал связи с командным сервером и попытки выгрузить данные, read-only запрещает менять образ, лимиты памяти и числа процессов гасят попытки исчерпать ресурсы. Распаковку вложенных архивов ведите в tmpfs с ограничением размера и проверяйте коэффициент сжатия: файл на несколько килобайт, разворачивающийся в гигабайты, останавливается лимитом, а не диском сервера. Для более строгой изоляции запускайте сканер под gVisor или Firejail, а сам контейнер держите на отдельном узле без доступа к внутренней сети.

Изолированный сканер удобно держать на отдельном виртуальном сервере или в кластере, например в облаке Timeweb Cloud с приватной сетью: узел проверки видит только каталог загрузок, а основное хранилище с ним не связано напрямую. На стороне Nginx при этом ограничивают размер тела запроса, чтобы отсекать гигантские загрузки до сканирования:

client_max_body_size 512m;
client_body_timeout 60s;
client_body_buffer_size 128k;
client_body_temp_path /var/tmp/nginx_upload 1 2;

Проверка целостности и аудит действий с файлами

sha256sum report.pdf > report.pdf.sha256
sha256sum -c report.pdf.sha256
sha256sum /var/archive/* > /var/archive/.checksums

Хеш считайте сразу после загрузки и перед выдачей: расхождение показывает подмену файла. Для каталогов храните файл контрольных сумм отдельно и проверяйте его по расписанию. Доступ к каталогу архива фиксируйте через auditd:

auditctl -w /var/archive -p rwxa -k archive_access
ausearch -k archive_access -ts recent | aureport -f -i

Правила сохраните в /etc/audit/rules.d/archive.rules, иначе они исчезнут после перезагрузки. Следите за счетчиком backlog командой auditctl -s: на большом дереве с ключом rwxa события могут теряться, тогда сократите область наблюдения до каталогов загрузок и карантина. Поведенческие правила дополняют аудит и ловят запуск оболочки из веб-процесса:

- rule: Shell Spawned By Web Server
  desc: Web server or scanner spawned a shell
  condition: spawned_process and proc.pname in (nginx, php-fpm, clamd, clamscan) and proc.name in (sh, bash, dash)
  output: "Shell from web path (cmd=%proc.cmdline user=%user.name container=%container.id)"
  priority: WARNING

Логи скачиваний, сканирования и изменений в архиве собирайте в одну точку: journald с отправкой в Loki или Elastic. Минимальный набор полей: пользователь, IP, URI, имя файла, размер, хеш, вердикт сканера и код ответа. Правила для WAF и проверки входных данных в приложении, которое отдает файлы, приведены в материале о защите приложений с динамическим контентом.

Чек-лист безопасности файлового архива

При каждой загрузке: проверяйте размер и реальный тип файла по содержимому, а не по расширению; прогоняйте файл через clamd до попадания в хранилище; при детекте переносите в карантин; считайте SHA-256 и записывайте в журнал; выставляйте права 0640 и владельца сервиса; запрещайте бит выполнения в каталоге архива.

Ежедневно: обновление баз ClamAV через freshclam; проверка ошибок в журнале clamd; просмотр новых файлов в карантине и уведомления по ним; ротация журналов сканирования.

Еженедельно: тест восстановления файла из резервной копии архива; проверка списка публично доступных объектов в хранилище; контроль сроков сертификатов командой certbot certificates и внешним чекером; ревизия прав на каталоги и файлы раздачи.

Ежемесячно и ежеквартально: ротация ключей шифрования и секретов подписи в KMS или Vault; ревизия ролей и выданных временных доступов; проверка TTL подписанных ссылок; аудит правил Falco и счетчика auditd; обновление Nginx, ClamAV и базовых образов контейнеров; проверка, что сканер по-прежнему запускается без доступа к сети.

Запустите проверки по расписанию: systemd-таймеры для обновления баз и очистки карантина, cron для контрольных сумм, алерты на рост числа срабатываний. Начните с двух шагов, которые дают эффект сразу: включите TLS 1.3 с HSTS на домене раздачи и заверните все загрузки через сканирование с карантином. Остальные слои добавляйте по мере того, как закрываете предыдущие.

Поделиться:
Сохранить гайд? В закладки браузера