.htaccess

Многие PHP-приложения поставляются с файлом .htaccess: WordPress, Laravel, готовые перенаправления на HTTPS и www, правила доступа, папки под паролем. WardPane сам читает и применяет эти файлы, поэтому привычные файлы работают как есть, без Apache на сервере.

Как включить

Откройте сайт, перейдите на вкладку Дополнительно и включите .htaccess в разделе «Совместимость с Apache». По умолчанию выключено.

Обработка .htaccess входит в обработчик PHP. Статический сайт, у которого она была включена до версии 2.0, её сохраняет, новые статические сайты файлы .htaccess не обрабатывают, а приложение Node.js само обрабатывает свои маршруты.

Когда обработка включена, файлы .htaccess в корне документов и во всех подпапках применяются так же, как в Apache: подпапка со своим .htaccess не наследует RewriteRule родительской папки, если в ней не указано RewriteOptions Inherit.

Поддерживаемые директивы

Область Директивы
Перезапись URL RewriteEngine, RewriteBase, RewriteCond, RewriteRule, RewriteOptions Inherit. Флаги L, END, R=code (от 300 до 599; 4xx и 5xx отвечают сразу, без перенаправления), F, G, NC, QSA, QSD, NE, C, S, B, T=type (только обычный тип содержимого, не PHP и не CGI). Проверки -f, -d, -s, -l, =, <, >, -eq и подобные, -ipmatch. Переменные %{HTTP_HOST}, %{HTTPS}, %{REQUEST_URI}, %{REQUEST_FILENAME}, %{QUERY_STRING}, %{HTTP:Header} и другие. \x в подстановке означает буквальный x.
Перенаправления Redirect, RedirectPermanent, RedirectTemp, RedirectMatch, Redirect gone. Коды от 300 до 599; вне 3xx код сам служит ответом, без адреса перехода (например, RedirectMatch 403 ... из чёрного списка 5G).
Доступ Require all/ip/not ip/local/method/valid-user/user, <RequireAll>, <RequireAny>, <RequireNone>, Order, Allow from, Deny from, Satisfy
Пароли AuthType Basic, AuthName, AuthUserFile (bcrypt из htpasswd -B, MD5 по умолчанию, {SHA})
Ответы ErrorDocument, Header set/append/add/merge/unset/setifempty [always], ExpiresActive, ExpiresDefault, ExpiresByType, AddType, AddDefaultCharset, DirectoryIndex
Блоки <Files>, <FilesMatch>, <Limit>, <LimitExcept>, <IfModule>, <IfVersion>

Детали, которые ведут себя как в Apache

  • <IfModule mod_php...> и <IfModule mod_deflate...> считаются отсутствующими модулями: их содержимое пропускается, как на сервере без них.
  • %{REQUEST_FILENAME} означает путь до первого существующего файла, остальное становится PATH_INFO. Для /index.php/2024/hello это index.php, поэтому !-f ложно и WordPress не переписывает адрес.
  • Expires... не трогает ответы с ошибками и ответы, в которых уже есть заголовок Expires (заданный скриптом). Его max-age заменяет стандартный Cache-Control панели для статических файлов (public, max-age=0) и добавляется к заголовку, заданному скриптом или через Header (у Laravel no-cache, private остаётся).
  • \ в аргументе означает пробел внутри слова, как в RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index\.php\ HTTP/.
  • Options +Indexes выдаёт предупреждение: списков содержимого папок нет, и папка без индексной страницы отвечает 404.

Что отклоняется

Отклоняются директивы, которые изменили бы, что именно выполняется, или ослабили бы песочницу PHP:

  • php_value, php_flag
  • AddHandler, SetHandler, ForceType, Action
  • AddType для PHP или CGI

Файлы .htaccess в папках, куда PHP может писать, игнорируются: иначе взломанный скрипт мог бы переписать правила, которые защищают сайт.

Поведение в плане безопасности

Файлы паролей

Файл паролей, указанный в AuthUserFile, должен находиться внутри папки сайта. Относительный путь отсчитывается от папки сайта. Внутри корня документов принимается только файл, имя которого начинается с точки (.htpasswd), так как любой другой можно было бы скачать; папка, которая ссылается на такой файл, не открывается (500).

Проверка паролей защищена от подбора:

  • после 10 неверных паролей за 10 минут адрес (для IPv6 его /64) не проверяется 15 минут;
  • после 300 неверных паролей со всех адресов сайт на 5 минут переходит в режим атаки: пароли проверяются только для адресов без недавних неудач, поэтому ботнет получает одну попытку на адрес, а человек с правильным паролем всё равно входит;
  • успешные проверки кэшируются, потому что браузер отправляет пароль с каждым запросом, а bcrypt намеренно медленный; одинаковые проверки, идущие одновременно, выполняются один раз;
  • bcrypt работает в отдельных потоках и не замедляет другие сайты: не больше 4 проверок одновременно на сайт и 32 в очереди, с ожиданием до 10 секунд, после чего ответ 503 (а не повторный запрос пароля);
  • стоимость bcrypt может быть не больше 12 (htpasswd -B -C 12); более дорогие хэши отклоняются;
  • неудачная проверка никогда не отвечает быстрее 300 мс, поэтому по времени ответа нельзя отличить несуществующего пользователя от неверного пароля.

Пути и блоки файлов

  • Адреса с сегментами . или .. (в том числе в виде %2e) получают 400. Иначе /./admin/ мог бы проскочить мимо .htaccess папки admin.
  • <Files> и <FilesMatch> сравниваются с файлом, который действительно будет отдан: для /admin.php/x это admin.php, каким бы ни был последний сегмент адреса.
  • Header не может менять заголовки, управляющие соединением: Content-Length, Transfer-Encoding, Connection, Upgrade, Alt-Svc и подобные.

Сломанные файлы закрывают доступ

.htaccess, который не удаётся прочитать или разобрать или который больше 256 КБ, закрывает свою папку: каждый запрос к этой папке получает 500, пока файл не исправят, а в журнал пишется ошибка. Apache поступает так же. Если бы папка оставалась открытой, правила доступа из этого файла молча перестали бы работать.

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

  • Ошибка в списке адресов в Allow, Deny или Require ip не прощается, но влияет только на свою секцию и не приводит к 500. Нераспознанная запись в Deny from означает «запретить всем» в этой секции; в Allow from или Require ip она означает «никому». Имя хоста вместо адреса никогда не совпадает (с предупреждением); мусор считается ошибкой в отчёте. localhost понимается (127.0.0.1 и ::1), а /0 означает все адреса.
  • Секция, условие которой нельзя проверить (<Files> или <FilesMatch> с нечитаемым шаблоном, <If>), сохраняет только свои правила доступа, и те могут только ограничивать: побеждает более строгое из решений с ними и без них.
  • Нечитаемый RewriteCond отключает и следующий за ним RewriteRule, который иначе сработал бы там, где не должен. Счёт [S=n] и цепочки [C] остаются верными. Исключение: правило, которое только запрещает ([F], [G], [R=4xx]). Оно остаётся, без своих условий, и запрещает всё, что совпадает с его шаблоном. Если RewriteBase не удаётся прочитать, правила с относительной целью не применяются.

Ограничения для регулярных выражений

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

Как выполняются шаблоны:

  • Шаблон никогда не отклоняется из-за того, сколько он может стоить.
  • Шаблоны, которые может обработать движок с линейным временем, выполняются прямо в запросе, если их оценочная стоимость укладывается в бюджет. Очень большие (скажем, список из 2000 имён ботов) уходят в отдельный рабочий поток.
  • Повторяющийся класс символов вроде ([a-z0-9]{2000,}) из файрволов 7G и 6G или \d{20,} проверяется в запросе линейным просмотром.
  • Всё остальное, что понимает JavaScript (lookahead, большие счётчики, обратные ссылки, вложенные повторы), выполняется в рабочем потоке.
  • Отклоняется только синтаксис, который есть лишь в PCRE, и такая строка не применяется: \K, \G, \Q...\E, притяжательный a++, атомарные группы (?>...), рекурсия (?R), комментарии (?#...), отрицательные POSIX-классы [[:^alpha:]] и несколько других экранирований. Распространённые формы PCRE переводятся: \A, \z, \Z, \e и POSIX-классы вроде [[:alpha:]] работают.

Ограничения:

Ограничение Значение Что происходит
Один шаблон в рабочем потоке 25 мс 500 только для этого запроса; шаблон продолжает использоваться
Все шаблоны одного запроса 50 мс 503 с Retry-After
Шаблонов на файл 500
Размер одного .htaccess 256 КБ Папка отвечает 500, пока файл не исправят
Ожидание свободного рабочего потока 20 мс 503 с Retry-After: 1

Строки, с которыми сравниваются шаблоны, тоже ограничены: адрес до 8190 символов, заголовки до 2 КБ, Cookie до 8 КБ, имена файлов до 255. Более длинные строки получают 414 (431 для заголовка), но только когда к ним действительно применяется шаблон. Строка, слишком длинная для большого шаблона (User-Agent размером 2 КБ против списка из 2000 ботов), сразу получает 431 или 414.

Медленная работа шаблонов засчитывается тому посетителю, который её вызвал. Посетителю (адресу, для IPv6 его /64, с отдельным счётом на каждую /48) засчитываются только тайм-ауты и медленные шаблоны с возвратами; обычные проверки бесплатны, поэтому люди за общим NAT не страдают. Тот, кто вызвал 5 тайм-аутов за 10 минут или исчерпал свой бюджет, получает 429 на этом сайте на 10 минут и передаётся защите сайта (при включённой защите от DDoS это означает бан). Белый список сайта и общие узлы Cloudflare никогда не наказываются.

Большой список ботов стоит каждому запросу несколько миллисекунд (2000 имён занимают около 4 мс для запроса от Chrome). Включите для такого сайта защиту от DDoS, чтобы не пускать потоки запросов.

Проверить файлы .htaccess

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

Не поддерживается

  • <If> и <Else> (с предупреждением; из их содержимого сохраняются только правила доступа, и они работают против доступа при любом условии)
  • списки содержимого папок (Options +Indexes)
  • переменные окружения: SetEnv, SetEnvIf, E=, env=
  • сжатие (mod_deflate)
  • ErrorDocument, указывающий на PHP-скрипт
  • проверки по имени хоста (Require host и другие неизвестные формы Require считаются против доступа; об именах в Allow и Deny см. выше)
  • флаги RewriteRule P (прокси), N и H