Disable Functions Bypass - dl Function

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

dl() lets PHP load a shared extension at runtime. If you can make it load an attacker-controlled module, you can register a new PHP function that internally calls execve, system, or any other native primitive and therefore bypass disable_functions.

This is a prawdziwy prymityw, but on modern targets it is far less common than older writeups suggest.

Dlaczego to obejście jest dziś rzadkie

Główne przeszkody to:

  • dl() must exist and must not be disabled
  • enable_dl must still allow dynamic loading
  • The target SAPI must support dl()
  • The payload must be a valid PHP extension compiled for the same target ABI
  • The extension must be reachable from the configured extension_dir

The official PHP manual is the most important reality check here: dl() is only available for CLI and embed SAPIs, and for the CGI SAPI when run from the command line. That means the technique is usually not available in normal PHP-FPM/mod_php web requests, so check the SAPI before spending time building a payload.

Also note that enable_dl is an INI_SYSTEM setting and, as of PHP 8.3.0, PHP documents it as przestarzałe, so you usually cannot flip it at runtime from attacker-controlled PHP code.

If dl() is not viable, go back to the broader list of module/version dependent bypasses.

Szybkie rozpoznanie z footholda

Zanim coś zbudujesz, zbierz dokładne parametry, które moduł musi spełniać:

<?php
phpinfo();
echo "PHP_VERSION=" . PHP_VERSION . PHP_EOL;
echo "PHP_SAPI=" . php_sapi_name() . PHP_EOL;
echo "ZTS=" . (PHP_ZTS ? "yes" : "no") . PHP_EOL;
echo "INT_BITS=" . (PHP_INT_SIZE * 8) . PHP_EOL;
echo "enable_dl=" . ini_get("enable_dl") . PHP_EOL;
echo "extension_dir=" . ini_get("extension_dir") . PHP_EOL;
echo "disabled=" . ini_get("disable_functions") . PHP_EOL;
?>

Co Cię interesuje:

  • PHP_SAPI: jeśli to fpm-fcgi lub apache2handler, dl() zwykle jest ślepszym zaułkiem dla web exploitation
  • extension_dir: payload musi być ładowany stąd
  • PHP Version, architecture, debug/non-debug, oraz ZTS/non-ZTS: Twój moduł musi się z nimi zgadzać
  • disable_functions: potwierdź, czy dl jest nieobecne, ponieważ jest zablokowane, czy dlatego, że SAPI tego nie obsługuje

Praktyczne ograniczenia eksploatacji

1. Zwykle potrzebujesz dostępu do zapisu w extension_dir

To największe wąskie gardło.

dl() przyjmuje nazwę pliku rozszerzenia, a PHP ładuje go z extension_dir. W praktyce oznacza to, że zwykły arbitralny upload pliku do /var/www/html/uploads nie wystarczy. Nadal potrzebujesz ścieżki, aby umieścić .so/.dll tam, skąd PHP faktycznie ładuje rozszerzenia.

Realistyczne sytuacje, w których to staje się eksploitable:

  • CTFs lub celowo słabe laboratoria, gdzie extension_dir jest zapisywalny
  • shared-hosting lub błędy w konfiguracji kontenera, które ujawniają zapisywalną ścieżkę rozszerzeń
  • oddzielny primitive do arbitralnego zapisu plików, który już sięga extension_dir
  • Post-exploitation scenariusze, gdzie już eskalowałeś wystarczająco, by upuścić pliki tam

2. Moduł musi odpowiadać buildowi celu

Dopasowanie samego PHP_VERSION nie wystarcza. Rozszerzenie musi też pasować pod kątem:

  • OS i architektury CPU
  • oczekiwań libc/toolchain
  • ZEND_MODULE_API_NO
  • build debug vs non-debug
  • ZTS vs NTS

Jeśli to nie będzie pasować, dl() zawiedzie lub spowoduje crash procesu.

3. open_basedir nie jest tu główną obroną

Gdy już możesz umieścić moduł w extension_dir i wywołać dl(), kod rozszerzenia wykonuje się wewnątrz procesu PHP. W tym momencie istotną barierą nie było open_basedir, lecz możliwość dostarczenia poprawnego shared object do ścieżki ładowania rozszerzeń.

Budowanie złośliwego rozszerzenia

Klasyczna ścieżka nadal jest ważna:

  1. Odtwórz build ofiary tak dokładnie, jak to możliwe
  2. Użyj phpize, ./configure i make, aby zbudować shared extension
  3. Eksportuj funkcję PHP, np. bypass_exec($cmd), która opakuje natywne wykonanie komend
  4. Prześlij skompilowany moduł do extension_dir
  5. Załaduj go za pomocą dl() i wywołaj eksportowaną funkcję

Atak jest stary, ale wciąż istotny, ponieważ changelogi PHP 8.x wciąż zawierają poprawki crashów specyficznych dla dl(). Primitive nadal istnieje; trudna część to znalezienie deploymentu, gdzie jest osiągalna i gdzie możesz wgrać pasujący moduł.

Minimalny przepływ pracy

Na maszynie atakującego

mkdir bypass && cd bypass
phpize
./configure
make

Wynikowa biblioteka współdzielona (.so) zwykle znajduje się w modules/.

Jeśli kompilujesz na innym środowisku niż target, traktuj wygenerowany plik jako szkic, dopóki nie zweryfikujesz, że ABI pasuje do victim.

Ładowanie i użycie rozszerzenia

Jeśli target rzeczywiście obsługuje dl() i moduł znajduje się w extension_dir, po stronie runtime wszystko jest proste:

<?php
if (!extension_loaded('bypass')) {
dl('bypass.so'); // use the correct filename for the target platform
}
echo bypass_exec($_GET['cmd']);
?>

Na Windows nazwa pliku będzie zazwyczaj .dll, natomiast na systemach Unix-like zwykle .so.

Notatki atakującego

  • Nie zakładaj, że to działa zdalnie tylko dlatego, że function_exists("dl") zwraca true w jakiejś dokumentacji lub starym writeupie; zweryfikuj działający SAPI
  • Nieudana próba dl() może zakończyć proces PHP (worker), jeśli moduł jest niekompatybilny
  • Od PHP 8 funkcje wyłączone są usuwane z tabeli funkcji, więc enumeracja z poziomu userland może różnić się od starszych wpisów
  • Jeśli nie możesz zapisać do extension_dir, ta technika zwykle jest mniej praktyczna niż FPM/FastCGI, LD_PRELOAD lub omówione wcześniej obejścia specyficzne dla modułu

References

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks