Les avantages de Kirby

Certains CMS souffrent de leur popularité et sont particulièrement ciblés par les bots de hackeurs qui cherchent à déceler de potentielles failles de sécurité. La popularité de Kirby est relativement modeste, cela devient un avantage sur le plan de la sécurité car Kirby intéressent peu les hackeurs.

Un autre avantage de Kirby sur le plan de la sécurité c'est que ce CMS n'utilise pas, par défaut, de base de données. Il y a donc un angle d'attaque en moins (SQL injection) par rapport aux CMS classiques utilisant une base de données.

Enfin, Kirby permet facilement de modifier son architecture de fichier. Vous pouvez par exemple déplacer l'endroit où vos données /content et vos utilisateurs /site/accounts sont placés dans votre arborescence de fichiers.

Voici par exemple comment, au niveau du fichier index.php qui se trouve à la racine de votre projet, indiquer à Kirby que vous avez déplacé le dossier /content dans le dossier /site

<?php

// /index.php

require __DIR__ . '/kirby/bootstrap.php';

//echo (new Kirby)->render();
$kirby = new Kirby();

$kirby = new Kirby([
       'roots' => [
           'content' => __DIR__ . '/site/content',
           'media' => __DIR__ . '/media',
       ],
   ]);

echo $kirby->render();
Dans cette nouvelle configuration, il faut préciser à Kirby à quel endroit le contenu doit aller chercher ses médias. Ici, on indique l'endroit originel c'est à dire à la racine du projet.

Les mises à jour

Ce conseil peut paraître évident mais il faut effectuer des mises à jour régulièrement afin de pallier d'éventuelles failles de sécurité.

Il faut biensure mettre à jour Kirby mais pas seulement, il faut aussi penser à mettre à jour :

- Les librairies tièrces (JS, PHP ...) que vous avez intégré vous-même dans vos projets.
- Les plugins Kirby si vous en utilisez.
- Les versions de PHP sur votre serveur.
- Les packages de l'OS qui fait tourner votre serveur.

Si vous êtes sur un hébergeur mutualisé ou un service cloud type AWS, vous n'aurez pas à vous préoccuper des mises à jour serveur, celles-ci seront gérées par les équipes de votre infrastructure d'hébergement.

Le serveur web

Droit et permissions sur le système de fichier

Si vous administrez vous-même votre serveur, vous devrez être vigileant sur les droits et permissions attribués sur vos dossiers et fichiers. Je ne suis pas administrateur system et personnellement, je préfère déléguer cette partie à un service tier. Mes serveurs VPS sont gérés à l'aide du service Runcloud.

Mais à minima, sachez que les permissions pour faire tourner votre serveur web doit être attribué à un utilisateur dédié non root et que les droits sur les fichiers et dossiers peuvent partir de ce paramétrage :

Dossiers en 755 (user=execution/écriture/lecture, group=execution/lecture , other=execution/lecture)
Fichiers en 644 (user=écriture/lecture, group=lecture , other=lecture)

Voici des commandes que vous pouvez utiliser dans votre terminal pour mettre en place ces permissions :

# dans le terminal

# Cherche tous les dossiers depuis la racine de votre projet et affecte les droits 755
sudo find /home/www/your_project/ -type d -exec chmod 755 {} +

# Cherche tous les fichiers depuis la racine de votre projet et affecte les droits 644
sudo find /home/www/your_project/ -type f -exec chmod 644 {} +

Fichier de configuration du serveur web

Quelque soit le serveur web que vous utilisez pour faire tourner votre projet web, celui-ci propose un fichier de configuration dans lequel vous pouvez ajouter des règles pour renforcer la sécurité. C'est le cas par exemple du fichier .htaccess pour le serveur web Apache ou du fichier Caddyfile pour le serveur web Caddy. Chaque serveur web à sa synthaxe donc référez-vous à leur documentation.

Apache étant encore le serveur le plus répandu, nous allons voir quelques règles que vous pouvez ajouter à votre fichier .htaccess

Bloquer l'accès directe (via leur URL) à vos fichiers de contenu (txt, md ..) situés dans le dossier /content

# .htaccess

RewriteRule ^content/(.*)\.(txt|md|mdown)$ index.php [L]

Bloquer l'accès directe (via leur URL) aux fichiers php du dossier /site

# .htaccess

RewriteRule ^site/(.*) index.php [L]

Bloquer l'accès directe (via leur URL) aux fichiers php du dossier /kirby

# .htaccess

RewriteRule ^kirby/(.*) index.php [L]

Bloquer le listing de votre arborescence de dossiers/fichiers si votre hébergeur ne l'a pas désactivé

# .htaccess

Options -Indexes

Utilisation d'un certificat SSL

L'utilisation du protocole https est maintenant devenu la norme. Il s'agit de chiffrer les échanges (requêtes, réponses) ayant lieu via le protocole http entre le navigateur et les serveurs web. Ces certificats s'achètent et s'installent sur les serveurs web mais vous pouvez également utiliser des certificats qui sont attribués gratuitement par des services comme Let's Encrypt. Des serveurs comme Caddy l'on même implémenté de façon complètement transparente et automatisée.

L'usage de Git

Si vous utilisez des dépôts Git publique sur Github, Bitbucket, Gitlab ... pour versionner votre code, ne laissez pas traîner des informations sensibles comme vos clés et mot de passe d'accès aux APIs ou vos comptes utilisateurs contenus dans le fichier /site/accounts

Pour cela vous pouvez utiliser un fichier .gitignore placé à la racine de votre projet versionné. Il vous permettera d'exclure les dossiers et fichiers à ne pas versionner.

Pour les clés et password d'API, stockez-les dans des variables d'environnement afin qu'elles ne soient pas lisibles en clair dans votre code. Pour les fichiers et dossiers sensibles ou les fichiers utiles en développement mais pas en production, indiquez-les dans le fichier .gitignore.

Voici un exemple de fichier .gitignore pour un projet Kirby que vous pourrez adapter selon vos besoins :

# .gitignore

# System files
# ------------

Icon
.DS_Store

# Temporary files
# ---------------

/media/*
!/media/index.html

# Lock files
# ---------------

.lock

# Editors
# (sensitive workspace files)
# ---------------------------
*.sublime-workspace
/.vscode
/.idea

# NodeJS
# --------

/node_modules


# -------------SECURITY-------------
# NEVER publish these files via Git!
# -------------SECURITY-------------

# Cache Files
# ---------------

/site/cache/*
!/site/cache/index.html

# Accounts
# ---------------

/site/accounts/*
!/site/accounts/index.html

# Sessions
# ---------------

/site/sessions/*
!/site/sessions/index.html

# License
# ---------------

/site/config/.license

# Configuration
# ---------------

/site/config/config.php
Dans ce .gitignore nous avons indiqué de ne pas versionner certains dossiers et fichiers de Kirby. Certains fichiers seront re-générés automatiquement par Kirby comme les médias ou les fichiers de sessions. En revanche le fichier de configuration config.php devra être re-créé manuellement sur le projet en production si vous utilisez ce dépôt pour le déployer.

Le fichier de configuration de Kirby

Voici quelques instructions que vous pouvez ajouter dans votre fichier de configuration Kirby pour renforcer la sécurité du projet.

Mode debug à false

Pensez à désactiver le mode debug en production pour ne pas divulger des informations sensibles à un éventuel hackeur.

// /site/config/config.php

return [
    'debug'  => false,
];

Modifier le slug du panel

Vous pouvez modifier le slug du panel pour y accéder selon une URL secrète.

// /site/config/config.php

return [
  'panel' => [
    'slug' => 'mon-url-top-secret'
  ]
];

Votre panel sera alors accessible depuis l'URL : https://monsiteweb.com/mon-url-top-secret

Renforcer l'authentification

Plus vous utiliserez des mots de passe complexes et longs, plus il sera difficile pour les scripts de brute-force de les deviner. Kirby suggère ce type de mot de passe lorsque vous créez un nouveau compte utilisateur, n'hesitez pas à utiliser ces suggestions. Si vous souhaitez choisir un mot de passe fort et facile à mémoriser, utilisez la technique de la pass-phrase. Il s'agit de choisir comme mot de passe une petite phrase mémorable. Si vous y ajoutez des majuscules/minuscules/chiffres/caractères spéciaux, c'est encore mieux !

Exemple: Les 12 nuages en forme de grenouilles !

Vous pouvez tester la force de votre mot de passe en ligne si vous avrez des doutes.

Kirby a par défaut un système anti brute-force sur l'authentification du Panel. Il retardera les demandes de connexion avec des informations d'identification invalides et bloquera l'accès au Panel par adresse IP et par utilisateur après 10 échecs de connexion dans une heure. Vous pouvez contrôler ces valeurs dans le fichier de configuration :

// /site/config/config.php

return [
  'auth' => [
    'trials' => 4,
    'timeout' => 3600*10
  ]
];

Modifier la méthode de login au panel

Kirby met à notre disposition différentes méthodes de login au panel :

Par mot de passe
C'est la méthode par défaut. Vous devez saisir l'email du compte et un mot de passe.

Par code
Vous saisissez l'email du compte et Kirby envrerra à cette adresse un code que vous devrez saisir pour vous identifier.

Pour mettre en place cette méthode :

// /site/config/config.php

<?php

return [
    'auth' => [
        'methods' => 'code'
    ]
];

Vous pouvez aussi proposer la méthode par mot de passe et par code, au choix de l'utilisateur :

// /site/config/config.php

<?php

return [
    'auth' => [
        'methods' => ['code', 'password']
    ]
];

Par double facteurs
Cette authentification se fait en deux étapes. Première étape, vous vous identifiez via le mail du compte et un mot de passe. Vous recevez alors un code sur votre adresse mail que vous devrez saisir dans la seconde étape de l'authentification.

Pour mettre en place cette méthode :

// /site/config/config.php

<?php

return [
    'auth' => [
        'methods' => [
            'password' => ['2fa' => true]
        ]
    ]
];

Validation et assainissement des données

La validation et l'assainissement des données permettent de prévenir les attaques de type cross-site scripting (XSS). Ces attaques consistent à injecter des scripts dans des champs de formulaire qui pourront s'exécuter dans un second temps de façon non désirée via votre code ou une action de l'utilisateur. Les données provenant des formulaires du Panel sont assainient automatiquement par Kirby. En revanche, les données provenant des formulaires que vous créez dans vos snippets et templates ne le sont pas. Ce sera à vous d'effectuer cet assainissement.

La validation

La validation des données consistent à valider que les données saisies dans les champs de vos formulaires correspondent bien aux données attendues. Par exemple si vous proposez un champ de saisie d'email, vous devez vérifier que la donnée saisie correspond bien à un email valide.

Dans le Panel, certains types de champs ont déjà un système de validation par défaut géré par Kirby et les champs obligatoires peuvent facilement être implémentés en ajoutant l'option required: true lors de leur déclaration dans les fichiers blueprints. Si cela ne suffit pas, vous pouvez renforcer ou étendre vos validations de données en interceptant la valeur des champs au moment de la mise à jour d'une page via un hook Kirby. Si la validation n'est pas remplie, vous pouvez geler la sauvegarde des données et afficher une modale d'erreur dans le Panel.

Exemple :

// /site/config/config.php

// Hook
'page.update:after' => function ( $newPage , $oldPage ) {

   // Date d'émission du devis >= Date d'échéance du devis
   if ( V::date( $newPage->actionDate()->value() , '>=' , $newPage->quoteDeadlineDate()->value() ) ) :
      throw new Exception('La date d\'émission du devis ne peut pas être ultérieure à la date de fin de validité du devis.');
   endif;
}

Dans vos formulaires front, c'est à vous de gérer la validation des données saisies dans les champs par l'utilisateur. Je vous conseille de toujours le faire côté serveur (la validation côté front n'est pas assez fiable). Vous pouvez effectuer vos validations dans un controller Kirby et utiliser des fonctions d'aides que kirby mets à votre disposition.

L'assainissement

L'assainissement des données consiste à s'assurer que les données saisies dans les champs de formulaire ne contiennent pas des scripts ou des instructions qui pourront s'éxecuter ou être interprétés par les navigateurs. Par exemple une instruction JavaScript.

Pour cela, Kirby propose des fonctions d'échapemment qui remplacent chaque caractère dangereux par une séquence d'échappement qui indique au navigateur qu'il doit interpréter le caractère comme du texte et non comme un caractère spécial.

Il s'agit sur les chaines de caractère de la fonction esc() et pour les champs de la méthode escape()

Attention ! vous ne pouvez pas utiliser la même méthode d'échappement pour le texte HTML, les attributs ou les URL. Vous devez contextualiser votre échapemment en indiquant à Kirby le type d'échappement dont vous avez besoin.

Pour plus de précisions et des exemples, consultez cette page de la documentation Kirby.

Les formulaires

Les bots malveillants savent remplir et soumettre un formulaire pour spamer ou corrompre vos données. Pour éviter cela, vous pouvez combiner plusieurs techniques :

CSRF

Pour éviter les attaques de type CSRF (Cross Site Request Forgery) qui consistent à vous faire éxecuter une action à votre insus en utilisant vos droits et votre session ouverte, Kirby propose la fonction csrf() pour générer un token (jeton) qui pourra être testé. Ainsi on pourra s'assurer que l'origine d'une requête http n'est pas falsifiée.

Honey pot

La technique du "Honey pot" n'est pas fiable à 100% mais vous pouvez l'ajouter aux autres techniques pour déjouer les bots malveillants. Cette technique consiste à créer des champs de formulaire cachés qui seront remplies par les bots mais pas les utilisateurs humains. A vous de bloquer ensuite la validation du formulaire si un de ces champs est saisie.

Test humain

Le test humain est un petit test simple que vous pouvez demander à l'utilisateur juste avant de valider un formulaire et que seul un humain sera en mesure de comprendre. Vous pouvez utiliser un Captcha à base d'images à reconnaître mais vous pouvez aussi demander le résultat d'une question simple comme une petite addition et proposer les réponses dans un champ de type select. Les bots répondront au hasard ou ne seront pas répondre alors que les humains répondront presque toujours avec la bonne réponse si le test est suffisament simple. Si la réponse est bonne, vous autorisez la validation du formulaire, sinon pas.

Mise en application de ces trois méthodes dans un exemple concret

Voici un exemple de code commenté mettant en place la soumission d'un formulaire de contact minimaliste. Le formulaire est codé dans un template /site/templates/contact.php et les données et le formulaire sont validés dans un controller /site/controllers/contact.php

Le formulaire dans le template :

// /site/templates/contact.php

...
<form method="post">

   <div>
      <label for="fullname">Fullname</label><br>
      <input type="text" id="fullname" name="fullname" value="<?= !empty( get('fullname') ) ?  trim( htmlspecialchars( esc( get('fullname') ) ) ) : ''; ?>" required>
   </div>

   <div>
      <label for="massage">Message</label><br>
      <input type="text" id="message" name="message" value="<?= !empty( get('message') ) ?  trim( htmlspecialchars( esc( get('message') ) ) ) : ''; ?>" required>
   </div>

    <!-- Honey pot -->
    <input type="hidden" name="website" value="">

    <!-- test humain -->
   <div>
      <label for="test">Combien font 3 + 4 ?</label><be>
      <input type="input" name="test" value="">
   </div>

    <!-- Génération du token -->
    <input type="hidden" name="login_csrf" value="<?= csrf() ?>">

    <div>
       <button type="submit" name="login" value="login">Envoyer</button>
    </div>

</form>

<?php if ( !empty( $error ) ) : ?>
    <div>
        <p><?= $error ?></p>
    </div>
<?php endif ?>

...

La validation du formulaire dans le controller :

// /site/controllers/contact.php

<?php

return function( $kirby ) {

$login_form_token = '';
$login_form_token = get( 'login_csrf' );

// On teste que la requête soit bien de type POST et que le token est le bon
if ( $kirby->request()->is('post') && csrf( $login_form_token ) === true ) :

   try {
       // On teste si les valeurs sont bien saisies
       if ( $fullname = get( 'fullname' ) && $message = get( 'message' ) ) :   
          $error = ''; 
       else :
          $error = 'Certains champs sont vides';
       endif;
       // On teste si le bot est tombé dans le pot de miel, si oui on redirige la page vers la homepage comme si de rien n'était !
       if ( !empty( get( 'website' ) ) :   
          go('/'); 
       endif;
       // On teste si le test humain est bon
       if ( get( 'test' ) === '7' && empty( $error ) ) :   
          // Tout est ok, on fait ce qu'on est censé faire ...
          // Par exemple envoyer un email à l'admin avec les données du formulaire
       else :
          $error = 'Il y a une erreur sur le test de calcul, veuillez saisir une autre réponse';
       endif;

   } catch( Exception $e ) {
       $error = $e;
   }

endif;

    return [
        'error' => $error,
    ];

};

Conclusion

Nous avons vu queqlues points pour sécuriser un projet Kirby. La sécurité est un vaste sujet et il y a probablement d'autres actions à mener pour renforcer cette sécurité mais disons que vous avez dans cet article une base solide sur laquelle vous appuyer si vous ne savez pas par où commencer.

La vidéo YouTube

Tournage à venir ...