Track mpm-itk problems with truss

Some background

I've some security needs on a shared hosting web server at work and I've ended up installing Apache-mpm-itk in place of my old vanilla Apache server. MPM-ITK is a piece of software (a set of patches in fact) you apply onto Apache source code to change it's natural behavior.
Out of the box, Apache spawns a handful of children httpd belonging to user www:www or whatever your config is using. Only the parent httpd belongs to root.
Hence, every single httpd must be able to read (and sometimes write) web site files. Now imagine you exploit a vulnerability into a php CMS, successfully injecting a php shell. Now through this php shell, you are www on the server, you can do everything www can, and it's bad, because you can even hack the other web sites of the server that have no known vulnerability.
With MPM-ITK, Apache spawns a handfull of master processes running as root, and accordingly to your config files, each httpd serving a particular virtual host or directory will switch from root to a user:group of your choice. So, one httpd process currently serving files from web site "foo" cannot access file from web site "bar": an attacker exploiting a vulnerability of one particular web site won't be able to hack every other web site on the server.

More background

That's a world you could dream of. In real world, that's not so simple. In particular, you'll start having troubles as soon as you make use of fancy features, especially when you fail to provide a dedicated virtual host per user.
On the shared server we host about 35 vhosts for 250 web sites, and we can't afford to provide every user with his dedicated vhost. The result is a given virtual host with a default value for the fallback user:group (say www:www), and each web site configured via Directory to use a different dedicated user.

When a client GET a resource (web page, img, css...) it generally keeps the connection opened with the httpd process. And it can happen that a resource linked from a web page sits into another directory, belonging to another user. The httpd process has already switched from root to user1 to serve the web page, it can't switch to user2 to serve the linked image from user2's directory. So Apache drops the connection, spawns a new httpd process, switches to user2, and serves the requested resource.
When it happens, you can read things like this into your Apache error log:

[warn] (itkmpm: pid=38130 uid=1002, gid=80) itk_post_perdir_config(): 
initgroups(www, 80): Operation not permitted
[warn] Couldn't set uid/gid/priority, closing connection.

That's perfectly "legal" behavior, don't be afraid, unless you read hundreds of new warning every minute.
If you host various web sites, belonging to various users, into the same vhost, you're likely to see many of these triggered by the /favicon.ico request.

Where it just breaks

When things are getting ugly is the moment a user tries to use one of your available mod_auth* variant to add some user authentication (think .htaccess). Remember, I host many web sites in a single vhost, each one into its own directory with its own user:group.

Suddenly every single visitor trying to access the protected directory or subdirectory is disconnected. Their http client reports something like this:

the server unexpectedly dropped the connection...

and nothing else is available. The error, server-side, is the same initgroups error as above, and it does not help at all. How would you solve this? truss is your friend.

Where I fix it

One thing I love about FreeBSD is the availability of many powerful tools out of the box. When I need to track down a strange software behavior, I feel very comfortable on FreeBSD (it doesn't mean I'm skilled). truss is one of my favorites, it's simple, straightforward and powerful.
What you need to use truss is the PID of your target. With Apache + MPM-ITK, processes won't stay around very long, and you can't tell which one you will connect to in advance. So the first step is to buy yourself some precious seconds so that you can get the PID of your target before the httpd process dies. Remember, it dies as soon as the .htaccess file is parsed. Being in production, I could not just kill everything and play alone with the server, so I choose another way. I've created a php script that would run for few seconds before ending. Server side, I've prepared a shell command that would install the .htaccess file I need to test, and start truss while grabbing the PID of my target. On FreeBSD, something like this should do the trick:

cd /path/to/user1/web/site
mv .htaccess_inactive .htaccess && truss -p $(ps auxw|awk '/^user1/ {print $2}')

First http GET request, the .htaccess file is not present, an httpd process switches from root to user1, starts serving the php script. I launch my command server-side: it puts .htaccess in place, gets the PID of my httpd process, and starts truss.
The php script ends and returns its result, client-side I refresh immediately (second GET request), so that I stay on the same httpd process. My client is disconnected as soon as the httpd process has parsed the .htaccess file. At this point, truss should already be dead. I've the complete trace of the event. The best is to read the trace backward from the point where httpd process issue an error about changing UID or GID:

01: setgroups(0x3,0x80a8ff000,0x14,0x3,0x566bc0,0x32008) 
    ERR#1 'Operation not permitted'
02: getgid()					 = 80 (0x50)
03: getuid()					 = 8872 (0x22a8)
04: getpid()					 = 52942 (0xcece)
05: gettimeofday({1364591872.453335 },0x0)		 = 0 (0x0)
06: write(2,"[Fri Mar 29 22:17:52 2013] [warn"...,142) = 142 (0x8e)
07: gettimeofday({1364591872.453583 },0x0)		 = 0 (0x0)
08: write(2,"[Fri Mar 29 22:17:52 2013] [warn"...,85) = 85 (0x55)
09: gettimeofday({1364591872.453814 },0x0)		 = 0 (0x0)
10: shutdown(51,SHUT_WR)				 = 0 (0x0)

Line 01 is the one I'm looking for: the httpd process is trying to change groups and fails, line 02 to 05 it's gathering data for the log entry, line 06 it's writing the error to the log file. 07 & 08: same deal for the second line of log.

From that point in time, moving up shows that it tried to access an out-of-directory resource, and that resource is an html error page! Of course, it makes sense, and it's an hard slap on the head (RTFM!).

01: stat("/user/user1/public_html/bench.php",{ 
    mode=-rw-r--r-- ,inode=4121,size=7427,blksize=7680 }) = 0 (0x0)
02: open("/user/user1/public_html/.htaccess",0x100000,00) = 53 (0x35)
03: fstat(53,{ mode=-rw-r--r-- ,inode=4225,size=128,blksize=4096 }) = 0 (0x0)
04: read(53,"AuthType Basic\nAuthName "Admin "...,4096) = 128 (0x80)
05: read(53,0x80a8efd88,4096)			 = 0 (0x0)
06: close(53)					 = 0 (0x0)
07: open("/user/user1/public_html/bench.php/.htaccess",0x100000,00) 
    ERR#20 'Not a directory'
08: getuid()					 = 8872 (0x22a8)
09: getgid()					 = 80 (0x50)
10: stat("/usr/local/www/apache22/error/HTTP_UNAUTHORIZED.html.var",{ 
    mode=-rw-r--r-- ,inode=454787,size=13557,blksize=16384 }) = 0 (0x0)
11: lstat("/usr/local/www/apache22/error/HTTP_UNAUTHORIZED.html.var",{ 
    mode=-rw-r--r-- ,inode=454787,size=13557,blksize=16384 }) = 0 (0x0)
12: getuid()					 = 8872 (0x22a8)
13: setgid(0x50,0x805d43d94,0x64,0x800644767,0x101010101010101,0x808080808080
    8080) = 0 (0x0)

line 13 shows the beginning of setgid process, and 10/11 shows the culprit. Up from here is the regular processing of the .htaccess file.

RTFM

When you use mod_auth* to present visitors with authentication, the server issues an error, and most of the time, this error is sent to the client with a dedicated header, and a dedicated html document (think "404"). When the error is about authentication (error 401), most clients hide the html part, and present the user with an authentication popup.
But the html part is almost always a physical file somewhere in the server directory tree. And it's this particular file the httpd process was trying to reach, issuing an initgroups command, and dying for not being allowed to switch users.
I've found in my Apache config the definition of ErrorDocument:

ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var
ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var
ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var
...

and replaced them all by a file-less equivalent, so Apache won't have any error file to read and will just send a plain ASCII error body (it saves bandwidth too):

ErrorDocument 400 "400 HTTP_BAD_REQUEST"
ErrorDocument 401 "401 HTTP_UNAUTHORIZED"
ErrorDocument 403 "403 HTTP_FORBIDDEN"
...

I've restarted Apache, and authentication from mod_auth* started to work as usual.
Same approach applies to almost any mpm-itk problem when it's related to a connection loss with Couldn't set uid/gid/priority, closing connection error log. You locate the resource that makes your server fail, and you find a way to fix the issue.

Related posts

Pas malin

Voilà, je viens de donner un accès illimité à mes données personnelles, mes contacts, mes photos, mes sms, mon historique de navigation, et j'en passe, à une entreprise américaine qui n'a pas grand respect pour la vie privée et dont les pratiques me répugnent presque autant que celles de Facebook. Je suis géolocalisable une bonne partie du temps, j'ai autorisé des logiciels à accéder et modifier mes données de manière non documentée. J'ai mis pas mal d'argent dans un appareil moderne qui consomme tellement qu'il a autant d'autonomie qu'un modèle de 10 ans d'âge avec sa batterie d'origine, en prime je tue des ours polaires parce qu'il n'est pas démontable (sans doute pas réparable) et que la batterie n'est pas remplaçable par l'utilisateur. Il n'est pas extensible et sera sans doute périmé dans moins de 3 ans. Son client mail par défaut ne sait pas faire de l'authentification CRAM-MD5, et bien sûr je ne peux rien synchroniser directement à partir de mon ordinateur, tout doit être stocké sur et transféré via des serveurs américains.
Bref, j'ai un Google Nexus 4.

Related posts

Bonjour vie privée

Je pourrais me lancer dans une grande dissertation sur la manière de protéger sa vie privée sur internet, et sur tous les mômes qui s'en foutent aujourd'hui, et pleureront demain. Mais comme rien n'est plus pédagogique qu'une démonstration, surtout avec une fille à poil, en voici une.
Vous connaissez presque tous, j'en suis sûr, le site bonjourmadame.fr. Pour les autres, sachez jusque que ce site présente une fois par jour une photographie de jeune femme sexy (comprendre nue), les contributeurs (et/ou l'administrateur) n'hésitant pas à supprimer la signature du photographe, et à convertir de temps en temps le résultat en noir et blanc parce que ça donne une caution artistique.
Parfois, un fan du concept un peu zélé va poster une photo de sa copine. C'est déjà un peu limite car rien ne dit si la copine est d'accord, ou même si les gens qui pilotent le site s'en soucient. Mais le fan est prudent, il ne veut pas qu'on reconnaisse sa copine, alors il lui coupe la tête dans un recadrage un peu barbare. Le bougre croit qu'il est bordé, que plus rien ne peut lui être reproché. Ce qu'oublie notre bonhomme c'est que la protection de la vie privée ça ne se fait pas par dessus la jambe, et qu'un coup de ciseaux dans une photo ou tout autre type de document ne suffit pas toujours.
Ainsi, certains logiciels conservent dans les méta données du fichier un aperçu de l'image d'origine. Moi je trouve ça particulièrement marrant, mais la copine sûrement moins.

bonjour vie privee, ou comment finir à poil dans les EXIF d'une photo
Sur la capture ci-dessus, j'ai bien sûr flouté la jeune fille. Bonjour vie privée !

edit du 2 février 2013 : il a récidivé avec la même jeune et pulpeuse demoiselle. Cette fois en nu intégral.

Related posts

Nuxeo, round 2

J'ai décidé de donner une seconde chance à Nuxeo. Je m'étais heurté fin 2011 et début 2012 à quelques difficultés qui m'avaient bien refroidi : bug divers, implémentation bancale avec MySQL, problème de proxying derrière mod_ssl, etc.
Fort d'une récente expérience de montage apache+mod_ssl+tomcat qui s'était terminé brillamment avec une application proprement cachée derrière un proxy apache+ssl, je me suis mis en tête de reproduire le montage avec Nuxeo. J'ai donc téléchargé et installé la dernière version de Nuxeo, purgé la base MySQL existante, et lancé la configuration.
Premier point : mea maxima culpa. Le support de MySQL semble bien moins mauvais que ce que j'avais constaté il y a presque un an. En effet, en tournant autour de ma configuration SSL, j'ai été amené à mettre mon nez dans les logs de Nuxeo, constatant que ces derniers contenaient des indications d'erreurs liées à MySQL. En corrigeant le tir, j'ai obtenu un comportement de l'application qui devrait être meilleur. Je reste au conditionnel, car l'année passée, tout avec bien fonctionné en apparence pendant quelques semaines, avant que je commence à constater des dysfonctionnements.
Deuxième point : Ha. Finalement j'ai retrouvé le drag & drop. j'ai perdu le drag & drop. Peut être avais-je rêvé en testant Nuxeo DM 5.4, mais en testant la version 5.6 je ne trouve pas le moyen de glisser-déposer des fichiers d'un répertoires à l'autre pour les ranger simplement. C'est très frustrant, le glisser-déposer de Nuxeo était un avantage majeur face à Alfresco.
Troisième point : j'ai raté mon coup avec le SSL. But initial de ma manipulation, la reconfiguration de la chaîne apache+mod_ssl+tomcat n'a pas donné satisfaction, donc je fonctionne avec les mêmes artifices qu'initialement (redirection des requêtes http vers l'https, notamment).
Quatrième point : lovely WebDAV. J'aime le WebDAV, c'est un protocole sympa, passe partout, supporté out-of-the-box par mon système. Je m'en sers presque tout les jours au boulot et à la maison. J'ai même configuré un programme dans le copieur connecté du bureau pour envoyer les scan de documents que je fais directement sur un serveur WebDAV. Bref, je viens de découvrir que Nuxeo permet d'accéder à mon espace de travail directement en WebDAV. Je peux ajouter des documents, des dossiers, les ranger/déplacer, et même les modifier dans une suite bureautique directement à partir du bureau de mon système.
⌘K, https://monserveur/nuxeo/site/dav/, login/mot de passe, et hop, montage sur le bureau. La vie est belle.
Aller, je vais essayer de m'en servir plusieurs fois par semaine, et voir où cela me mène. Verdict dans un mois.

Related posts

Utiliser auditd sur Mac OS X et FreeBSD – 3

Cet article est la suite de Utiliser auditd sur Mac OS X et FreeBSD - 2

Exploiter les résultats de l'audit

Dans le premier article j'ai mentionné très succinctement une commande qui permet de lire les logs d'audit en temps réel. Et finalement la consultation de ces logs est sans doute la chose la plus intéressante. Personne n'a envie d'activer l'audit sur une machine pour le simple plaisir de remplir son disque dur. Tout ceci doit avoir une raison d'être : pouvoir ensuite (ou en temps réel) exploiter les logs d'auditd. Continue reading

Related posts

Utiliser auditd sur Mac OS X et FreeBSD – 2

Cet article est la suite de Utiliser auditd sur Mac OS X et FreeBSD - 1

Configuration : quid des utilisateurs comme www ?

J'expliquais précédemment que le système d'audit ne peut suivre un utilisateur que si ce dernier se connecte à la machine (par ssh par exemple), et que quelques soient les ruses utilisées (su, sudo...) c'est toujours l'UID réelle de l'utilisateur qui est suivie.

À cause de cela, il est totalement impossible de traquer des évènements appartenant à des utilisateurs qui ne peuvent pas se connecter au système. Ainsi, une ligne comme celle-ci dans audit_user ne permettra de traquer aucun évènement :

# pour freebsd remplacer _www par www
_www:fc,fd,ex:

Il serait pourtant très intéressant de savoir ce que fait Apache dans votre dos, parfois sous la direction de méchants pirates.
De nos jours, La plupart des serveurs utilisent des protocoles comme l'HTTP qui permettent aux utilisateurs d'accéder aux ressources sans être authentifiés au niveau du système. Les utilisateurs en question n'existent d'ailleurs pas au niveau du système. Néanmoins, les applications web permettent de faire énormément de choses via les langages comme le PHP, y compris d'interagir avec le système, en lançant des commandes, en créant des fichiers, etc.

Si on souhaite qu'auditd puisse contrôler les actions de www, il faut recourir à un artifice. Attention tout de même, ce qui suit est plus proche d'un hack que d'une méthode vraiment propre, et je ne peux pas garantir son fonctionnement correct dans un environnement de production.

En premier lieu il faut installer un programme spécifique, qui va permettre de forcer auditd à suivre les actions pour une UID donnée, sans que l'utilisateur correspondant ait besoin de se connecter à la machine.

curl -LO http://www.freebsd.org/~csjp/setaudit.c
cc -o setaudit -lbsm setaudit.c 

Cela vous donne, si tout se passe bien, le binaire setaudit, que l'on va utiliser sous cette forme :

setaudit -a UID_WWW -m FLAGS /programme/de/lancement/d/apache

Pour suivre les évènements d'exécution de commande sous respectivement FreeBSD et Mac OS X, cela donnera :

setaudit -a www -m ex /usr/local/etc/rc.d/apache22 restart
setaudit -a _www -m ex /usr/sbin/apachectl restart

Ainsi, toutes les commandes lancées par Apache (donc sous l'UID www) sont enregistrées par auditd.
Comme le masque des évènements (les flags de l'argument -m) décrit ce que l'on doit suivre, il est même inutile de renseigner le fichier audit_user.

Pour résumer :

  • Si vous souhaitez que l'audit suive les utilisateurs qui se connectent à la machine, vous pouvez régler les classes d'évènements qui seront soumises à l'audit pour l'ensemble des utilisateurs dans le fichier audit_control, et gérer les cas particuliers dans audit_user.
  • Si vous souhaitez que l'audit suive des UID "interne", pour traquer tout signe de compromission sur un serveur web, un serveur de mail, etc. il faudra recourir à setaudit pour activer l'audit au moment du lancement du service correspondant.
  • Faites attention à la liste de flags que vous choisissez. Le volume des logs d'audit peut grossir très rapidement.

Troisième partie de l'article ->

Related posts

Utiliser auditd sur Mac OS X et FreeBSD – 1

FreeBSD et les versions récentes de Mac OS X sont livrées avec un système d'audit intégré, dérivé du Basic Security Module de SUN, et disponible en logiciel libre sous le nom d'OpenBSM. Ce système d'audit se décompose en deux parties : un ensemble d'appels systèmes dédiés et de librairies d'un côté, et des logiciels "utilisateur" de l'autre. Sauf précision contraire, les explications présentées ici sont valables à la fois pour FreeBSD 7 et 8, et Mac OS X 10.6. Dans cette série d'articles je ne vais aborder que l'aspect "utilisateur" du système d'audit : comment l'activer, comment le configurer, comment exploiter les résultats.
Le système d'audit fourni par OpenBSM a pour but la surveillante des actions des utilisateurs. Un certain nombre d'évènements peuvent être mis sous surveillance, pour tout ou partie des utilisateurs. Quand le système d'exploitation détecte qu'un utilisateur sous surveillance génère un des évènements à surveiller, il averti le démon auditd qui se charge de reporter cet évènement dans un fichier de log. Continue reading

Related posts

Rendre des logs anonymes

En tant qu'administrateur système, je gère un certains nombre de serveurs qui voient passer des données personnelles, soit en tant que simple intermédiaire (passerelle de messagerie) soit en tant que destination finale (connexion/authentification des utilisateurs). Ces serveurs stockent alors sous forme de fichier de log des quantités phénoménales de données nominatives comme des identifiants, des adresses email, des adresses IP. Ces données sont bien évidemment confidentielles, et seule une réquisition judiciaire peut m'autoriser à en divulguer tout ou partie. Je suis moi-même autorisé à les exploiter dans le cadre de mon travail, mais uniquement à des fins statistiques, ou pour vérifier le bon fonctionnement des systèmes.
Il peut pourtant arriver qu'on soit tenté de fournir des extraits de log à certaines personnes. Un jour ce sera pour un prestataire ou un support technique, un autre jour ce sera pour alimenter des chercheurs universitaires qui ont besoin de données brutes. Il est alors impératif de faire disparaître toute trace de données personnelles et nominatives de ces fichiers, tout en s'assurant que le destinataire des fichiers sera en mesure de faire son travail.
Les données doivent être anonymes, mais pour autant elles doivent rester intègres et cohérentes. Par exemple, il faut que l'on puisse suivre le trajet d'un message électronique entre le serveur d'entrée, l'antispam, la passerelle interne, et le serveur de stockage final, sans pour autant connaître ni l'adresse IP du serveur de provenance, ni les adresses email de l'expéditeur et des destinataires.
Après quelques recherches infructueuses, j'ai décidé de créer moi-même le script dont j'ai besoin pour rendre des log de serveurs de mails anonymes.
En Perl, le module IP::Anonymous fourni une méthode cryptographique puissante basée sur Crypto-PAN. Cette méthode permet de rendre les adresse IPv4 anonymes tout en préservant une très grande cohérence des données. Voyez cet exemple avec à gauche les IP réelles, et à droite les IP anonymes obtenues par chiffrement :

192.168.0.15 -> 54.42.0.48
192.168.0.20 -> 54.42.0.43
192.168.1.15 -> 54.42.1.56
192.168.1.20 -> 54.42.1.43

En me basant sur un exemple de script Perl utilisant IP::Anonymous, j'ai créé un script qui permet en plus de rendre anonyme les adresses email, les message-IDs, et les noms de machines. Ainsi, et à condition de rester en IPv4, le script final permet de rendre totalement anonyme des logs Postfix :

   1: #!/usr/bin/perl -wT
   2: 
   3: # script brodé à partir d'un exemple
   4: # trouvé en ligne sur le forum CPAN
   5: # http://cpanforum.com/posts/1304
   6: 
   7: # à utiliser sous la forme :
   8: # logs_anonymes.pl fichier.log > sortie_anonyme.log
   9: 
  10: use strict;
  11: $|=1;
  12: 
  13: use IP::Anonymous;
  14: use Digest::MD5;
  15: 
  16: # 32 integers entre 0 et 255 (jot -r 32 0 255)
  17: my @key = (135,229,13,26,29,84,46,37,231,95,211,
  18:            196,243,11,87,5,23,217,173,214,66,241,
  19:            164,67,132,149,161,151,114,137,238,75);
  20: 
  21: my $obj = new IP::Anonymous(@key);
  22: my $md5 = Digest::MD5->new;
  23: 
  24: # seed pour le md5 
  25: my $seed = '766326241f69b1244792587f556d02b8';
  26: 
  27: while(defined(my $line=<>)) {
  28:     chomp $line;
  29:     # les IP
  30:     if($line =~ /\d{1,3}(?:\.\d{1,3}){3}/) {
  31:         $line =~
  32:             s/(\d{1,3}(?:\.\d{1,3}){3})/$obj->anonymize($1)/eg;
  33:     }
  34:     # les adresses email et messageID
  35:     if($line =~ /[=< ][^@ ]*@/) {
  36:         $line =~
  37:             s/([=< ])([^@=< ]*@)/$1.$md5->add($seed)->add($2)->hexdigest."@"/eg;
  38:     }
  39:     # les hostnames
  40:     if($line =~ /([=< @])(([a-zA-Z0-9]***TRONQUÉ***|ZW)/i ) {
  41:         $line =~
  42:             s/([=< @])((?:(?:[a-zA-Z0-9]***TRONQUÉ***|ZW)/$1.$md5->add($seed)->add($2)->hexdigest.".".$3.".".$4/egi;
  43:     }
  44:     # suppression des HELO
  45:     if($line =~ /(helo=<[^>]*>)/i ) {
  46:         $line =~
  47:             s/(helo=<[^>]*>)//gi;
  48:     }
  49:     
  50:     print $line."\n";
  51: }

Attention : certaines lignes du script ci-dessus sont tronquées volontairement. Cliquez ici pour obtenir le code du script fonctionnel : logs_anonymes.pl.

Pour utiliser ce script, il vous faudra installer IP::Anonymous, comme ceci par exemple :

$ sudo cpan
cpan[1]> install IP::Anonymous

Il vous faudra aussi personnaliser le contenu de @key (lignes 17 à 19), en utilisant par exemple la commande jot pour générer une liste de 32 entiers aléatoires compris entre 0 et 255 :

$ jot -r -s , 32 0 255

@key doit être gardée secrète, donc votre script devra être lisible seulement par vous.
Pour finir il faudra personnaliser le contenu de $seed (ligne 25), en saisissant la chaîne de caractères qui vous plait.
Aux lignes 40 et 42 figure une liste quasi complète des TLD, obtenue auprès de l'IANA.

Pour utiliser le script, que vous aurez rendu exécutable, il suffit de l'invoquer comme cela :

$ /chemin/de/logs_anonymes.pl fichier.log > sortie_anonyme.log

Testé sous Mac OS X 10.6 et FreeBSD 8.

Enjoy.

Related posts