Dozwolone ścieżki dowiązań symbolicznych

Domyślnie OxPHP odrzuca każde żądanie, które rozwiązuje się do ścieżki poza kanonicznym DOCUMENT_ROOT. Dowiązanie symboliczne wewnątrz DOCUMENT_ROOT wskazujące na zewnętrzny katalog zwraca 404, a rozwiązywanie ścieżki zapisuje w logach Blocked request: resolved path escapes document root.

To właściwe ustawienie domyślne: zatrzymuje przechodzenie po katalogach (directory traversal), ataki TOCTOU z podmianą dowiązania symbolicznego oraz przypadkowe ujawnienie plików konfiguracyjnych lub sekretów leżących o poziom wyżej. Blokuje jednak także uzasadnione wzorce, które frameworki stosują od lat: php artisan storage:link w Laravelu, pakiety zasobów Symfony, współdzielone wolumeny przesyłanych plików montowane w wielu kontenerach.

SYMLINK_ALLOW_PATHS to jawna zgoda: wskazujesz ścieżki w systemie plików, do których dowiązania symboliczne pod DOCUMENT_ROOT mogą się rozwiązywać. Wszystko, czego nie ma na liście, zachowuje ścisłe zachowanie z odpowiedzią 404.

Konfiguracja

bash
# Absolute paths, comma-separated SYMLINK_ALLOW_PATHS=/var/www/storage,/opt/shared/assets # Relative paths resolve against DOCUMENT_ROOT SYMLINK_ALLOW_PATHS=../storage,../shared/uploads # Mixed SYMLINK_ALLOW_PATHS=/opt/shared/cdn,../storage/app/public

Gdy zmienna nie jest ustawiona (ustawienie domyślne), żadne dowiązanie symboliczne nie może opuścić DOCUMENT_ROOT.

Przykład dla Laravela

bash
DOCUMENT_ROOT=/app/public SYMLINK_ALLOW_PATHS=../storage/app/public

Wtedy php artisan storage:link tworzy public/storage -> ../storage/app/public wewnątrz projektu. Adresy URL wskazujące na /storage/<file> rozwiązują się przez dowiązanie symboliczne do /app/storage/app/public/<file>, co kanonizuje się do ścieżki, którą lista dozwolonych autoryzuje. Bez żadnej zmiany w kodzie aplikacji.

Jak to działa

Podczas uruchamiania każdy wpis jest rozwiązywany:

  • Wpisy bezwzględne — sprawdzane względem listy blokowania (patrz niżej), następnie przepuszczane przez realpath(3) (czyli std::fs::canonicalize). Jeśli realpath zawiedzie (cel nie istnieje), serwer odmawia uruchomienia.
  • Wpisy względne — łączone z kanonicznym DOCUMENT_ROOT, a następnie poddawane działaniu realpath.

Powstałe kanoniczne ścieżki są zapisywane jako lista dozwolonych. Duplikaty są po cichu usuwane.

W momencie żądania warstwa routingu kanonizuje rozwiązaną ścieżkę pliku i weryfikuje, czy spełnia jeden z warunków:

  1. leży wewnątrz DOCUMENT_ROOT, albo
  2. jest dokładnie równa jednemu z wpisów listy dozwolonych (cele będące plikami), albo
  3. zaczyna się od jednego z wpisów listy dozwolonych, po którym następuje / (cele będące katalogami).

Ta sama kontrola uruchamiana jest po raz drugi jako zabezpieczenie przed TOCTOU wewnątrz ścieżki serwowania plików statycznych, po pamięci podręcznej tras, a przed jakimkolwiek wywołaniem systemowym odczytu.

Lista blokowania

Niewielki zbiór ścieżek nigdy nie może pojawić się w SYMLINK_ALLOW_PATHS; literówki i nieporozumienia w przeciwnym razie drastycznie poszerzyłyby powierzchnię ataku. Serwer odmawia uruchomienia, jeśli którykolwiek wpis rozwiązuje się do ścieżki z listy blokowania.

Zabronione jako dokładne dopasowanie:

text
/ /etc /proc /sys /dev /var /home /tmp /root /usr /srv

Zabronione jako prefiks (wpis leży pod jednym z tych katalogów):

text
/etc /proc /sys /dev /tmp /root /usr
Note

/var, /home i /srv są dozwolone wyłącznie jako dokładne dopasowanie: samo /srv jest odrzucane, ale /srv/myapp/storage jest dozwolone, tak samo jak dozwolone są /var/www/storage oraz /home/<any>/.... Wpisy są sprawdzane dwukrotnie: raz względem surowej ścieżki podanej przez administratora (tak aby ścieżka w stylu macOS /etc -> /private/etc nie mogła przemycić zablokowanej ścieżki przez realpath), raz względem postaci kanonicznej (obrona w głąb przeciwko ucieczkom celu dowiązania symbolicznego).

Sama lista blokowania jest zaszyta w kodzie; nie ma zmiennej środowiskowej, która by ją rozszerzała. Ustawienie domyślne to konserwatywne minimum wychwytujące błędy na poziomie literówek; administratorzy potrzebujący surowszych zasad powinni nakładać je z zewnątrz (uprawnienia systemu plików, ograniczenia montowania w kontenerach, profile AppArmor/SELinux).

Tryby awaryjne

Błąd konfiguracji Wynik
Cel wpisu nie istnieje na dysku Serwer odmawia uruchomienia, błąd wskazuje wpis oraz canonicalize
Wpis pasuje do listy blokowania (surowy lub kanoniczny) Serwer odmawia uruchomienia, błąd wskazuje wpis oraz regułę listy blokowania
Pusta zmienna środowiskowa lub zawierająca tylko białe znaki Traktowana jako nieustawiona — ścisłe zachowanie domyślne
Zduplikowane wpisy Po cichu usuwane po kanonizacji
Podczas uruchamiania nie istnieje jeszcze żadne dowiązanie symboliczne Lista dozwolonych jest zarejestrowana, ale bezczynna do momentu pojawienia się dowiązania symbolicznego; żadna kontrola przy uruchamianiu nie wymaga dowiązania

Uwagi dotyczące bezpieczeństwa

  • Lista dozwolonych działa na zasadzie jawnej zgody — bezpieczne ustawienie domyślne „bez ucieczek" jest zachowane, gdy zmienna nie jest ustawiona
  • Wpisy są kanonizowane podczas uruchamiania, więc .. oraz pośrednie dowiązania symboliczne w ścieżce wpisu są zwijane przed zapisaniem
  • Kanonizacja w czasie działania zamyka okno TOCTOU związane z podmianą dowiązania symbolicznego — zweryfikowana ścieżka to ta sama ścieżka, która zostaje odczytana
  • Cele będące plikami dopasowywane są dokładnie; cele będące katalogami dopasowywane są po prefiksie katalogu. Umieszczenie na liście pliku /etc/passwd i tak zostałoby odrzucone przez listę blokowania, ale bardziej ogólnie: umieszczenie na liście pojedynczego pliku /opt/shared/license.key nie daje niejawnie dostępu do plików sąsiednich
  • Wyniki walidacji ścieżek są buforowane per żądany adres URL (jeden realpath na unikalny adres URL do momentu usunięcia z pamięci podręcznej), więc koszt działania jest amortyzowany

Zobacz także