Comment j’ai réparé l’article sur la sécurisation SSH
Vous avez été plusieurs à me remonter l’information par différents canaux, la première partie de la série sur la sécurisation d’un serveur Linux était HS, et même son affichage dans les pages de catégories ou de recherche flinguait l’affichage du site (allant de l’affichage partiel à la page blanche). J’ai cherché longtemps, et j’ai souffert pour trouver d’où venait le problème. Reste que même si j’ai résolu l’affaire, je peux pour l’instant seulement l’expliquer partiellement, et non pas dans les moindres détails.
Première étape : la base de données
Comme au travail un collègue a eu un problème avec des champs mal remplis (tenter de convertir une date « NULL » ne fonctionne pas en PHP, qu’on se le dise), je me suis dit que ça pouvait venir de là. Sachant que l’édition de l’article fonctionne, j’ai filtré le contenu pour ne garder que les autres champs :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
MariaDB [c0blog]> select ID,post_author,post_date,post_date_gmt,post_title,post_excerpt,post_status,comment_status,ping_status,post_password,post_name,to_ping,pinged,post_modified,post_modified_gmt,post_contmenu_order,post_type,post_mime_type,comment_count from wp_posts where post_title like '%curisation%' and post_status = 'publish'\G *************************** 1. row *************************** ID: 1631 post_author: 1 post_date: 2015-03-27 18:00:28 post_date_gmt: 2015-03-27 17:00:28 post_title: Quelques bonnes pratiques de sécurisation d'un serveur Linux, partie 1 post_excerpt: post_status: publish comment_status: open ping_status: closed post_password: post_name: quelques-bonnes-pratiques-de-securisation-dun-serveur-linux-partie-1 to_ping: pinged: post_modified: 2016-03-01 23:09:53 post_modified_gmt: 2016-03-01 22:09:53 post_content_filtered: post_parent: 0 guid: https://blog.seboss666.info/?p=1631 menu_order: 0 post_type: post post_mime_type: comment_count: 3 *************************** 2. row *************************** ID: 1663 post_author: 1 post_date: 2015-06-28 10:30:54 post_date_gmt: 2015-06-28 08:30:54 post_title: Quelques bonnes pratiques de sécurisation d'un serveur Linux, partie 4 post_excerpt: post_status: publish comment_status: open ping_status: closed post_password: post_name: quelques-bonnes-pratiques-de-securisation-dun-serveur-linux-partie-4 to_ping: pinged: post_modified: 2015-09-19 14:32:14 post_modified_gmt: 2015-09-19 12:32:14 post_content_filtered: post_parent: 0 guid: https://blog.seboss666.info/?p=1663 menu_order: 0 post_type: post post_mime_type: comment_count: 3 *************************** 3. row *************************** ID: 1774 post_author: 1 post_date: 2015-04-26 10:30:45 post_date_gmt: 2015-04-26 08:30:45 post_title: Quelques bonnes pratiques de sécurisation d'un serveur Linux, partie 2 post_excerpt: post_status: publish comment_status: open ping_status: closed post_password: post_name: quelques-bonnes-pratiques-de-securisation-dun-serveur-linux-partie-2 to_ping: pinged: post_modified: 2015-04-18 10:56:59 post_modified_gmt: 2015-04-18 08:56:59 post_content_filtered: post_parent: 0 guid: https://blog.seboss666.info/?p=1774 menu_order: 0 post_type: post post_mime_type: comment_count: 0 *************************** 4. row *************************** ID: 1777 post_author: 1 post_date: 2015-06-04 18:00:04 post_date_gmt: 2015-06-04 16:00:04 post_title: Quelques bonnes pratiques de sécurisation d'un serveur Linux, partie 3 post_excerpt: post_status: publish comment_status: open ping_status: closed post_password: post_name: quelques-bonnes-pratiques-de-securisation-dun-serveur-linux-partie-3 to_ping: pinged: post_modified: 2015-06-01 16:55:51 post_modified_gmt: 2015-06-01 14:55:51 post_content_filtered: post_parent: 0 guid: https://blog.seboss666.info/?p=1777 menu_order: 0 post_type: post post_mime_type: comment_count: 3 *************************** 5. row *************************** ID: 1781 post_author: 1 post_date: 2015-08-07 18:00:40 post_date_gmt: 2015-08-07 16:00:40 post_title: Quelques bonnes pratiques de sécurisation d'un serveur Linux, partie 5 post_excerpt: post_status: publish comment_status: open ping_status: closed post_password: post_name: quelques-bonnes-pratiques-de-securisation-dun-serveur-linux-partie-5 to_ping: pinged: post_modified: 2015-07-25 17:54:30 post_modified_gmt: 2015-07-25 15:54:30 post_content_filtered: post_parent: 0 guid: https://blog.seboss666.info/?p=1781 menu_order: 0 post_type: post post_mime_type: comment_count: 4 *************************** 6. row *************************** ID: 1863 post_author: 1 post_date: 2015-10-20 18:00:29 post_date_gmt: 2015-10-20 16:00:29 post_title: Quelques bonnes pratiques de sécurisation d'un serveur Linux, partie 6 post_excerpt: post_status: publish comment_status: open ping_status: closed post_password: post_name: quelques-bonnes-pratiques-de-securisation-dun-serveur-linux-partie-6 to_ping: pinged: post_modified: 2015-10-11 22:58:09 post_modified_gmt: 2015-10-11 20:58:09 post_content_filtered: post_parent: 0 guid: https://blog.seboss666.info/?p=1863 menu_order: 0 post_type: post post_mime_type: comment_count: 1 6 rows in set (0.00 sec) |
Bon, rien de choquant, ça ne vient pas de là. Je me rabats ensuite sur la table wp_postmeta :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
MariaDB [c0blog]> desc wp_postmeta; +------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+---------------------+------+-----+---------+----------------+ | meta_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | post_id | bigint(20) unsigned | NO | MUL | 0 | | | meta_key | varchar(255) | YES | MUL | NULL | | | meta_value | longtext | YES | | NULL | | +------------+---------------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec) MariaDB [c0blog]> select * from wp_postmeta where post_id = 1631; +---------+---------+---------------+--------------+ | meta_id | post_id | meta_key | meta_value | +---------+---------+---------------+--------------+ | 27240 | 1631 | _edit_last | 1 | | 27241 | 1631 | _edit_lock | 1457096027:1 | | 29025 | 1631 | _thumbnail_id | 1655 | | 29037 | 1631 | dsq_thread_id | 4603376308 | +---------+---------+---------------+--------------+ 4 rows in set (0.00 sec) |
Gné ? Petite recherche, dsq_thread_id est un vestige de Disqus, petit sagouin qui ne fait pas le ménage correctement (spoiler : il est loin d’être le seul à laisser du bordel en partant). On le vire :
1 2 |
MariaDB [c0blog]> delete from wp_postmeta where meta_key = 'dsq_thread_id'; Query OK, 241 rows affected (0.05 sec) |
Quand même. Mais malheureusement, rien de neuf sous le soleil, au moins ai-je nettoyé un peu la base de données. Pour info, je suis toujours en mode DEBUG au niveau du WordPress, mais aucun message ou presque ne vient me titiller les yeux, à part quelques messages de déprécation de la part d’une fonction du cœur de WordPress (depuis PHP 5.3, les gars, sérieux ?), et du plugin d’insert de code (celui pour les boutons sociaux). Merci PHP 7 🙂
J’essaie malgré tout de récupérer plus de messages en ajoutant une ligne dans mon fichier de conf du pool FPM :
1 |
php_admin_value[error_log] = "/var/www/clients/client0/web1/log/php_error.log" |
Toujours rien (remarquez au passage l’héritage d’ISPConfig). PHP n’étant pas plus bavard, je change mon fusil d’épaule, et laisse pousser un peu la barbe.
Deuxième étape : le microscope géant
En effet, je me suis rabattu sur un outil qui commence à me servir pas mal au travail, strace. Il faut vraiment se faire la main dessus pour comprendre à quel point il peut vous sauver la vie. Et là, c’était pas loin. Je remercie une fois de plus Djerfy à qui je dois cette routine :
1 2 3 |
strace $(ps fauxw | egrep "fpm" | egrep "web1 " | awk '{print"-p " $2 " -s 512 -v -vv -f -ff -o /tmp/strace_fpm.log"}' | tr "\n" " ") Process 3505 attached root@vox:/tmp# |
Euh, pardon ? Autant les fois précédentes, il fallait arrêter le strace à la main, autant là, il m’a carrément pété dans les pattes. Assez violent, et ça explique les messages d’erreurs d’Nginx qui dit que la communication a été rompue (sans plus de détails). J’ai donc un fichier /tmp/strace_fpm.log.3505 à analyser, et il fait plus 12Mo le bougre (ls -lh l’arrondit même à 13). Vu que le process FPM a été flingué, je me rend directement à la fin du fichier pour tenter de savoir pourquoi il s’est terminé :
1 2 3 4 5 6 7 8 9 10 11 |
tail strace_fpm.log.3505 lstat("/var/www/clients/client0", {st_dev=makedev(253, 0), st_ino=658365, st_mode=S_IFDIR|0755, st_nlink=10, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=8, st_size=4096, st_atime=2016/04/23-14:11:56, st_mtime=2016/03/28-17:55:41, st_ctime=2016/03/28-17:55:41}) = 0 lstat("/var/www/clients", {st_dev=makedev(253, 0), st_ino=658364, st_mode=S_IFDIR|0755, st_nlink=3, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=8, st_size=4096, st_atime=2016/04/23-14:11:55, st_mtime=2016/01/10-17:09:01, st_ctime=2016/01/10-17:09:01}) = 0 lstat("/var/www", {st_dev=makedev(253, 0), st_ino=531001, st_mode=S_IFDIR|0755, st_nlink=8, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=8, st_size=4096, st_atime=2016/04/23-14:11:51, st_mtime=2016/03/28-14:41:01, st_ctime=2016/03/28-14:41:01}) = 0 lstat("/var", {st_dev=makedev(253, 0), st_ino=523265, st_mode=S_IFDIR|0755, st_nlink=16, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=8, st_size=4096, st_atime=2016/04/23-14:11:49, st_mtime=2016/03/04-17:43:52, st_ctime=2016/04/22-08:53:15}) = 0 access("/var/www/clients/client0/web1/web/wp-content/plugins/crayon-syntax-highlighter/langs/swift/swift.txt", F_OK) = 0 brk(0x1432000) = 0x1432000 brk(0x1453000) = 0x1453000 mmap(NULL, 131072, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9bf75c9000 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} --- +++ killed by SIGSEGV +++ |
Wait, WHAT ? J’apprends sur la bible StackOverflow qu’un SIGSEGV, déjà pas vraiment un signal qu’on aime voir (essaie d’aller faire des crasses dans des zones mémoires où il n’a pas le droit d’aller, ce que le PROT_READ|PROT_WRITE|PROT_EXEC semble d’ailleurs indiquer), dans le contexte qui nous intéresse veut dire que PHP plante à cause d’une récursion infinie. Mais que diable PHP essaie donc de faire là, qu’est-ce qui peut bien l’y pousser, et surtout pourquoi uniquement avec cet article-là ?
Troisième étape : le contenu
Retour au back-office de WordPress, et concentrons-nous sur le contenu de l’article, puisque c’est au final la seule véritable variable de l’équation. J’avais déjà regardé de près si des caractères un peu bizarres avaient pu se glisser dans l’histoire, mais rien de probant sur l’instant (raison pour laquelle j’avais laissé le contenu de côté au début de l’enquête). Tentative désespérée en guise de confirmation : je vide le contenu de l’article. La page s’affiche bien entièrement, mais sans le contenu puisqu’il n’y en a plus.
Poisse. L’article est assez long, entrecoupé de nombreux blocs de codes contenant aussi bien des commandes que des extraits de fichiers de configuration. J’entreprends alors de recopier l’article paragraphe par paragraphe pour tenter de localiser le morceau problématique. Et je finis par tomber sur THE bloc problématique : le détail du « jail » sshd.conf. Question, pourquoi lui en particulier ? Et surtout, pourquoi ça fonctionnait avant, quand est-ce qu’il a décidé de faire chier son monde (j’ai des mails qui remontent déjà à Fevrier) ? Quelque chose a-t-il été modifié sans prévenir (à la faveur d’une migration quelconque, un mysqldump mal paramétré ?) ? Beaucoup de questions donc, et au final, une première partie de réponse assez courte.
La solution, c’est simple comme un coup de clic
En réfléchissant bien ce paragraphe fautif est assez imbuvable en termes d’expressions régulières, et bien des parseurs se casseraient les dents dessus. Une réflexion que vient confirmer un test d’Arowan en parallèle, à qui j’ai transmis le contenu de l’article pour qu’il l’essaie sur son blog. Ça semble fonctionner chez lui, mais je constate qu’a priori il n’a pas de coloration syntaxique. S’en suit le petit échange sur Twitter :
J’ai donc trouvé le couple coupable : Crayon Syntax Highlighter (au demeurant très bon plugin de coloration syntaxique, que m’avais recommandé Julien Hommet d’ailleurs), et mon extrait de configuration. En effet, pour rappel voilà à quoi ressemble ce bloc :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# Fail2Ban filter for openssh # [INCLUDES] # Read common prefixes. If any customizations available -- read them from # common.local before = common.conf [Definition] _daemon = sshd failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error) for .* from <HOST>( via \S+)?\s*$ ^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>\s*$ ^%(__prefix_line)sFailed \S+ for .*? from <HOST>(?: port \d*)?(?: ssh\d*)?(: (ruser .*|(\S+ ID \S+ \(serial \d+\) CA )?\S+ %(__md5hex)s(, client user ".*", client host ".*")?))?\s*$ ^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>\s*$ ^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because not in any group\s*$ ^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$ ^%(__prefix_line)sReceived disconnect from <HOST>: 3: \S+: Auth fail$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because a group is listed in DenyGroups\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$ ignoreregex = # DEV Notes: # # "Failed \S+ for .*? from <HOST>..." failregex uses non-greedy catch-all because # it is coming before use of <HOST> which is not hard-anchored at the end as well, # and later catch-all's could contain user-provided input, which need to be greedily # matched away first. # # Author: Cyril Jaquier, Yaroslav Halchenko, Petr Voralek, Daniel Black |
Vous les sentez les expressions regulières de l’enfer ? Donc comme pour vous l’afficher aujourd’hui, j’ai simplement usé d’un simple clic pour cocher une case :
Ce « Don’t Highlight » indique au plugin de ne pas interpréter le fichier. Et après avoir recollé le reste de l’article, je peux confirmer que c’était le seul fautif. L’article est donc de retour en ligne, je peux souffler et prévenir par mail les personnes qui m’avaient remonté le souci. Sur Twitter, les gens ont pu suivre dimanche en temps réel les évolutions.
RCA : pas complet
La cause profonde, je la laisse aux plus furieux et barbus d’entre vous, quand je vois la liste de regex, pas surprenant que le parsing foire. Mais malgré tout, ça reste surprenant puisque dans mon environnement Docker de test (monté et raconté à l’occasion d’un billet d’humeur), avec du PHP 5.6, ça ne le fait pas. J’imagine donc une évolution dans certaines fonctions liées aux chaines de caractères, typiquement une fonction preg_match() qui pourrait se voir « violée » par une telle syntaxe. Evolutions qui ne plaisent pas à tous les parseurs de code en manque d’attention. L’utilisation plus poussée du JIT pourrait aussi être une piste à considérer dans ce contexte.
Malheureusment, à voir le dépôt Github le développement du plugin n’est plus très actif, j’hésite donc à remonter le bug. Et je ne suis pas assez bon en PHP pour me refaire la lecture du plugin et tenter de corriger le comportement. Mais le plantage du thread FPM peut déjà expliquer l’absence de message d’erreur remonté dans les logs PHP.
Aussi, je n’ai pas non plus le niveau pour aller tâter du GDB. Alors si vous avez du temps à perdre, n’hésitez pas, essayez par vous-même. Honnêtement, je garde cette enquête en tête, mais là, j’ai juste envie de souffler un peu. En écoutant du BabyMetal 😀
Coucou,
Concernant le plugin Crayon Syntax Highlighter, je l’avais aussi utilisé au début sur mon blog Mercure News (https://mercu.re). Mais j’ai très vite changé, car comme tu l’as dit, il n’est pas maintenu. Un autre problème que j’avais avec était qu’il ne produit pas du code valide W3C. J’ai un article en brouillon sur mon blog qui parle de PrismJS (http://prismjs.com/) et de comment l’intégrer dans son blog wordpress. Je vais le finir au plus vite et le programmer :).
N’hésite pas à remonter un bug ! Toujours rapporter un bug !
En plus, son développement est bien actif: dernière version il y a 14 jours et 35 commits depuis le début de l’année.
Il est même plutôt vivant.
Intéressant de connaître toutes les étapes qui t’ont permis de trouver / corriger le pb !
C’était donc le plugin qui impactait tes articles – ce qui est étonnant, c’est que tu avais quelques fois un problème pour l’affichage des pages d’archives… peut-être est-ce était lié, du fait que le thread fpm se plantait ?
Dans tous les cas, tu as réussi, bravo 🙂
salut, pour revenir sur la commande strace (inspirée de Djerfy), moi aussi j’avais eu aussi besoin de tracer les appels systèmes relatifs à tout ce qui concerne les fichiers (open/close/*stat/..) de nginx, et pour ça j’avais utiliser la ligne de commande suivante : ps aux|egrep ‘(nginx|php|fpm|cgi)’|grep -v ‘grep -E’|cut -c10-14|xargs -n 1 echo -n ‘ -p’ le tout passé à strace qui est non seulement bien crade comme syntaxe (beaucoup de pipe assez illisibles), mais surtout avec un découpage risqué si le nom du compte qui lance la commande a plus de 8 caractères (bon, en général ça tourne sous… Lire la suite »
oups, pour la 2e ligne il fallait lire :
[…]
au final, j’ai pu changer tout ça en lisant un peu le man avec la commande suivante (et 1 seule pipe) :
ps -C fcgiwrap -C nginx -C php-fpm7.0 -o pid=|xargs -n 1 echo -n ‘ -p’
et voici…
(désolé pour l’erreur de copier/coller)
Très sympa, merci du partage, j’ai appris des trucs !
Tcho !
Intéressant, je crois moi aussi que j’ai déjà eu ça, je testerai merci !