Улучшение безопасности конфигураций Apache

                
                Улучшение безопасности конфигураций Apache
Статья разбирает практические приёмы жёсткой настройки Apache для уменьшения поверхности атак. В ней перечислены основные меры: ограничение подключаемых модулей, политика «запрещено по умолчанию», запрет листинга каталогов и .htaccess, отключение CGI, скрытие версии сервера и ненужных методов, установка строгих заголовков безопасности, фильтрация подозрительных запросов и ограничение размеров пакетов, настройка TLS-сессий и принудительный редирект на HTTPS, а также грамотное ведение логов и чистка лишних заголовков. Это дополнение к посту о деплое проекта на Django, но напрямую к Django не относится — здесь описаны улучшения безопасности именно конфигурации Apache.

1. Контроль набора модулей (уменьшаем площадь атаки)

 

Каждый загруженный модуль — это новый потенциальный вектор атаки (уязвимость в самом модуле, его неправильная настройка). Чем меньше модулей — тем меньше дыр.

 

Исторически «стреляли» mod_status, mod_autoindex, CGI и прочие. При лишних модулях можно: узнать внутренности сервера, обойти авторизацию, получить RCE в цепочке с уязвимым приложением/интерпретатором, или просто расширить поверхность DoS.

 

Принцип минимизации доверенной базы (TCB). Чем меньше кода выполняется с правами веб-сервера, тем меньше вероятность уязвимости и тем короче цепочка эксплуатации.

 

LoadModule access_compat_module modules/mod_access_compat.so
LoadModule actions_module        modules/mod_actions.so
LoadModule alias_module          modules/mod_alias.so
LoadModule allowmethods_module   modules/mod_allowmethods.so
LoadModule auth_basic_module     modules/mod_auth_basic.so
LoadModule authn_core_module     modules/mod_authn_core.so
LoadModule authn_file_module     modules/mod_authn_file.so
LoadModule authz_core_module     modules/mod_authz_core.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_host_module     modules/mod_authz_host.so
LoadModule authz_user_module     modules/mod_authz_user.so
LoadModule autoindex_module      modules/mod_autoindex.so
LoadModule dir_module            modules/mod_dir.so
LoadModule env_module            modules/mod_env.so
LoadModule headers_module        modules/mod_headers.so
LoadModule include_module        modules/mod_include.so
LoadModule isapi_module          modules/mod_isapi.so
LoadModule log_config_module     modules/mod_log_config.so
LoadModule mime_module           modules/mod_mime.so
LoadModule negotiation_module    modules/mod_negotiation.so
LoadModule setenvif_module       modules/mod_setenvif.so
LoadModule socache_shmcb_module  modules/mod_socache_shmcb.so
LoadModule ssl_module            modules/mod_ssl.so

 

Держите список кратким. Всё, что не нужно — выкинуть. «Вдруг пригодится» — плохая стратегия в безопасности.

 

2. Политика «запрещено по умолчанию» на корне

 

Закрываем всё, затем точечно открываем нужные каталоги.

 

Незаметные каталоги/файлы (резервные копии, временные, забытые dev-папки) могут внезапно стать доступными анонимно.

 

Default-deny > Default-allow. Ошибиться, добавив лишний Require all granted, проще, чем случайно забыть закрыть то, что не должно светиться.

 

<Directory />
    AllowOverride none
    Require all denied
</Directory>

 

Сначала запрещаем всё, затем даём ровно столько доступа, сколько нужно сайту.

 

3. Твёрдая конфигурация каталога сайта: индексы, .ht*, управляемые доступы

 

Отключаем автолистинг, «перекрываем» .ht*, и не позволяем .htaccess портить нашу политику.

 

  • Options Indexes → утечка структуры проекта и статических файлов.
  • Разрешённый .htaccess → локальный RCE/обход правил через коммит одного файла.
  • Скрытые/служебные файлы → прямое чтение секретов.

 

.htaccess — удобен, но небезопасен: разброс политик, медленнее I/O, выше риск конфиг-инъекций при компрометации деплоя.

 

<Directory "${SRVROOT}/htdocs">
    Options -Indexes +FollowSymLinks
    AllowOverride None
    Require all granted
    <FilesMatch "^\.(?!well-known/).+">
        Require all denied
    </FilesMatch>
    <FilesMatch "\.(env|ini|cfg|conf|bak|swp)$">
        Require all denied
    </FilesMatch>
</Directory>

<Files ".ht*">
    Require all denied
</Files>

 

Закрываем индексы, запрещаем .htaccess и «невидимки», оставляем только то, что реально нужно пользователю.

 

4. Полный запрет на CGI

 

CGI — классика уязвимостей: исполнение внешних интерпретаторов по HTTP—запросу.

 

CGI скрипт с багом == RCE. Даже если вы не используете CGI, оставленный «на всякий случай» каталог — РИСК.

 

Принцип «отключить интерпретаторы в вебе» максимально упрощает модель угроз.

 

<Directory "${SRVROOT}/cgi-bin">
    AllowOverride None
    Options None
    Require all denied
</Directory>
<LocationMatch "^/cgi-bin/?">
    Require all denied
</LocationMatch>

 

Нет CGI — нет класса атак, завязанных на него.

 

5. Минимизируем утечки метаданных и векторы трассировки

 

Скрываем версию, отключаем TRACE, не даём подсказок сканерам.

 

Банальный баннер-граббинг подбирает эксплойт под вашу версию. TRACE помогает в XST и трюках с заголовками.

 

Security by configuration: не даём нападающему лишнюю телеметрию, убираем редко нужные методы.

 

TraceEnable off
ServerTokens Prod
ServerSignature Off
HostnameLookups Off

 

Чем меньше о вас знает сканер — тем сложнее прицелиться.

 

6. Жёсткие заголовки безопасности (XFO, XCTO, XSSP, Referrer, Permissions)

 

Ограничиваем встраивание, MIME-сниффинг, реферы и доступ к устройствам.

 

  • Clickjacking через <iframe> без X-Frame-Options.
  • MIME-сниффинг → загрузка JS как «текста» превращается в исполняемый скрипт.
  • Лишние реферы → утечка токенов/URL с секретами.
  • Permissions-Policy → ограничиваем потенциальные злоупотребления API.

 

Безопасность фронтенда — это политика браузера. Заголовки — ваш компактный CSP-лайт.

 

<IfModule headers_module>
    RequestHeader unset Proxy early
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>

 

Добавьте ещё полноценную CSP в продакшене. X-XSS-Protection устарел, но вреда не делает — реальную пользу даёт CSP.

 

7. Блокирование кодированных обходов путей

 

Запрещаем декодирование закодированных слешей и отлавливаем попытки traversal/эксплойтов через подозрительные URI.

 

%2f, %5c, двойная/тройная кодировка и «..» могут пробить проверку маршрутов и вытащить файлы за пределами DocumentRoot или попасть в неожиданный location.

 

URI-нормализация — боль. Проще явно пресечь нестандартные формы и двойные кодировки.

 

AllowEncodedSlashes NoDecode
SetEnvIfNoCase Request_URI "(?i)(?:/cgi-bin\b|/bin/sh\b)" BAD_URI=1
SetEnvIfNoCase Request_URI "(?i)(?:\.\.|%2e%2e|%25%32%65%25%32%65|%252e%252e|%2f%2e%2e|%5c%2e%2e)" BAD_URI=1
SetEnvIfNoCase Request_URI "(?i)(?:%2f|%5c)" BAD_URI=1

<Location "/">
  <RequireAll>
    Require all granted
    Require not env BAD_URI
  </RequireAll>
  <LimitExcept GET POST HEAD>
    Require all denied
  </LimitExcept>
</Location>

 

Это легковесный «WAF-лайт». Он не заменяет полноценный WAF, но отрубит массу мусорных и опасных запросов.

 

8. Белый список методов

 

Ограничиваем методы до безопасного минимума (чтение/простые формы).

 

Методы вроде PUT, DELETE, OPTIONS, PROPFIND и экзотика могут неожиданно «сработать» через модуль/баг приложения и дать запись/удаление.

 

Least functionality: включаем только то, что реально нужно маршрутам.

 

<LimitExcept GET POST HEAD>
    Require all denied
</LimitExcept>

 

Если приложению понадобится, вы знаете, где открыть точечно, а не «везде».

 

9. Ограничение размеров запросов (анти-DoS/анти-smuggling)

 

Защищаем парсер заголовков и апстрим от огромных/грязных запросов.

 

Большие заголовки/количество полей — классический low-rate DoS. Также меньше шансов на хитрые атаки по переполнению/смещению (HTTP Request Smuggling любит нестандартные заголовки).

 

Rate-limiting != size-limiting. Нужны оба. Здесь — ограничение по размеру.

 

LimitRequestLine       8190
LimitRequestFieldSize  8190
LimitRequestFields     100

 

Значения разумные для большинства фреймворков. Если у вас нестандартные большие JWT в куках — пересмотрите дизайн, а не границы.

 

10. TLS: сессии и OCSP stapling

 

Стабильные и быстрые TLS-сессии и проверка отзыва сертификатов (stapling).

 

Технически пробиться сложнее, но отсутствие stapling → клиенты сами ходят к OCSP-серверу (утечки, нестабильность). Плюс без кэша сессий можно легче задушить CPU пиками TLS-рукопожатий.

 

OCSP stapling — сервер прикладывает свежий статус сертификата к рукопожатию; кэши — снижают стоимость повторных соединений.

 

<IfModule ssl_module>
    SSLRandomSeed startup builtin
    SSLRandomSeed connect builtin
    SSLSessionCache        "shmcb:${SRVROOT}/logs/ssl_scache(512000)"
    SSLSessionCacheTimeout 300
    SSLUseStapling On
    SSLStaplingCache "shmcb:${SRVROOT}/logs/stapling_cache(128000)"
</IfModule>

 

Это базовый гигиенический минимум TLS.

 

11. Принудительный переход на HTTPS

 

Любой HTTP-запрос — редирект на HTTPS. Убираем смешанный трафик.

 

MitM, перехват куки, подмена контента, down-grade атаки. HTTP — открытка без конверта.

 

HTTPS-везде + HSTS — стандарт индустрии. Сначала редирект, затем HSTS (с preload — по ситуации).

 

<VirtualHost *:80>
    ServerName  example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
    ErrorLog  "logs/redirect_error.log"
    CustomLog "logs/redirect_access.log" combined
</VirtualHost>

 

Далее еще разберемся с этим подробнее.

 

12. Логи как средство безопасности (детект и разбор инцидентов)

 

Без логов вы слепы: не увидите скан, перебор, попытки LFI/RCE, неожиданные методы.

 

Атакующий уходит «в тень»: без корректного формата и раздельных логов расследование затянется или станет невозможным.

 

Observability — часть безопасности. Логи доступа + ошибок, отдельные файлы под редиректы/SSL/приложение.

 

ErrorLog "logs/error.log"
LogLevel warn

<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b" common
    <IfModule logio_module>
        LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
    </IfModule>
    CustomLog "logs/access.log" combined
</IfModule>

 

Лично я периодически скармливаю логи ИИ для проверки. Да, костыль, в будущем автоматизирую.

 

13. Анти-прокси-мусор в запросах

 

Сносим подозрительный заголовок Proxy на ранней стадии — бывает полезно против некоторых кривых прокси-цепочек/багов.

 

Некоторые уязвимости на границах прокси/бэкендов вспыхивают от неожиданных служебных заголовков в сыром виде.

 

Гигиена заголовков: всё, что не нужно приложению, лучше очистить/зарезервировать.

 

RequestHeader unset Proxy early

 

Мелочь, а приятно. Особенно в зоопарке из CDN, слойных прокси и бэкендов.


Итого
 

Вот такой код должен +- получиться:

 

Define SRVROOT "C:/Apache24"

ServerRoot "${SRVROOT}"

Listen 80

LoadModule access_compat_module modules/mod_access_compat.so
LoadModule actions_module        modules/mod_actions.so
LoadModule alias_module          modules/mod_alias.so
LoadModule allowmethods_module   modules/mod_allowmethods.so
LoadModule auth_basic_module     modules/mod_auth_basic.so
LoadModule authn_core_module     modules/mod_authn_core.so
LoadModule authn_file_module     modules/mod_authn_file.so
LoadModule authz_core_module     modules/mod_authz_core.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_host_module     modules/mod_authz_host.so
LoadModule authz_user_module     modules/mod_authz_user.so
LoadModule autoindex_module      modules/mod_autoindex.so
LoadModule dir_module            modules/mod_dir.so
LoadModule env_module            modules/mod_env.so
LoadModule headers_module        modules/mod_headers.so
LoadModule include_module        modules/mod_include.so
LoadModule isapi_module          modules/mod_isapi.so
LoadModule log_config_module     modules/mod_log_config.so
LoadModule mime_module           modules/mod_mime.so
LoadModule negotiation_module    modules/mod_negotiation.so
LoadModule setenvif_module       modules/mod_setenvif.so
LoadModule socache_shmcb_module  modules/mod_socache_shmcb.so
LoadModule ssl_module            modules/mod_ssl.so

LoadFile  "C:/Program Files/Python313/python313.dll"
LoadModule wsgi_module "C:/Program Files/Python313/Lib/site-packages/mod_wsgi/server/mod_wsgi.cp313-win_amd64.pyd"
WSGIPythonHome "C:/Apache24/htdocs/venv"
WSGIPythonPath "C:/Apache24/htdocs/our_project"

<IfModule unixd_module>
   User daemon
   Group daemon
</IfModule>

ServerAdmin admin@example.com
ServerName  example.com

<Directory />
   AllowOverride none
   Require all denied
</Directory>

DocumentRoot "${SRVROOT}/htdocs"

<Directory "${SRVROOT}/htdocs">
   Options -Indexes +FollowSymLinks
   AllowOverride None
   Require all granted
   <FilesMatch "^\.(?!well-known/).+">
       Require all denied
   </FilesMatch>
   <FilesMatch "\.(env|ini|cfg|conf|bak|swp)$">
       Require all denied
   </FilesMatch>
</Directory>

<IfModule dir_module>
   DirectoryIndex index.html
</IfModule>

<Files ".ht*">
   Require all denied
</Files>

ErrorLog "logs/error.log"
LogLevel warn

<IfModule log_config_module>
   LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
   LogFormat "%h %l %u %t \"%r\" %>s %b" common
   <IfModule logio_module>
       LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
   </IfModule>
   CustomLog "logs/access.log" combined
</IfModule>

<Directory "${SRVROOT}/cgi-bin">
   AllowOverride None
   Options None
   Require all denied
</Directory>

<LocationMatch "^/cgi-bin/?">
   Require all denied
</LocationMatch>

<IfModule mime_module>
   TypesConfig conf/mime.types
   AddType application/x-compress .Z
   AddType application/x-gzip     .gz .tgz
</IfModule>

<IfModule proxy_html_module>
   Include conf/extra/proxy-html.conf
</IfModule>

TraceEnable off
ServerTokens Prod
ServerSignature Off
HostnameLookups Off

<IfModule headers_module>
   RequestHeader unset Proxy early
   Header always set X-Frame-Options "SAMEORIGIN"
   Header always set X-Content-Type-Options "nosniff"
   Header always set X-XSS-Protection "1; mode=block"
   Header always set Referrer-Policy "strict-origin-when-cross-origin"
   Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>

AllowEncodedSlashes NoDecode
SetEnvIfNoCase Request_URI "(?i)(?:/cgi-bin\b|/bin/sh\b)" BAD_URI=1
SetEnvIfNoCase Request_URI "(?i)(?:\.\.|%2e%2e|%25%32%65%25%32%65|%252e%252e|%2f%2e%2e|%5c%2e%2e)" BAD_URI=1
SetEnvIfNoCase Request_URI "(?i)(?:%2f|%5c)" BAD_URI=1

<Location "/">
 <RequireAll>
   Require all granted
   Require not env BAD_URI
 </RequireAll>
 <LimitExcept GET POST HEAD>
   Require all denied
 </LimitExcept>
</Location>

LimitRequestLine       8190
LimitRequestFieldSize  8190
LimitRequestFields     100

<IfModule ssl_module>
   SSLRandomSeed startup builtin
   SSLRandomSeed connect builtin
   SSLSessionCache        "shmcb:${SRVROOT}/logs/ssl_scache(512000)"
   SSLSessionCacheTimeout 300
   SSLUseStapling On
   SSLStaplingCache "shmcb:${SRVROOT}/logs/stapling_cache(128000)"
</IfModule>

<VirtualHost *:80>
   ServerName  example.com
   ServerAlias www.example.com
   Redirect permanent / https://example.com/
   ErrorLog  "logs/redirect_error.log"
   CustomLog "logs/redirect_access.log" combined
</VirtualHost>

Include conf/extra/httpd-ssl.conf