Surveillance de fichiers

Discussion dans 'Tutoriels' démarrée par François D, Fév 12, 2017.

  1. François D

    François D Member

    Pas toujours évident de s'apercevoir que des pages ou des scripts ont été modifiés pas des méchants, surtout quand il s'agit de modifications invisibles qui n'apparaissent que dans la source d'une page. J'ai déjà goûté.

    Voici un petit script de surveillance qu'une tâche cron pourra exécuter. Elle liste simplement la liste des fichiers modifiés depuis X et s'il y en a, un email est envoyé pour prévenir.

    Les éléments à renseigner :

    USERCPANEL = Votre utilisateur CPANEL

    nbminutes = Nombre de minutes en arrière à vérifier. Exemple '-120' = 2 heures. Le script listera les fichiers modifiés dans les dernière 2 heures.

    from_address = Email expéditeur (compte sur le serveur).
    mailpass = Mot de passe du email (pour permettre l'envoi)
    to_address = Email de destination
    emailsubject = Sujet du email

    dossiers = les dossiers à surveiller. Si vous n'avez qu'un site principal sur votre hébergement World, c'est tout probablement public_html. Le script se charge d'ajouter /home/USERCPANEL/ au début.

    Vous pourriez ajouter public_ftp aussi, ça ne mange pas de pain.

    Si vous avez des domaines supplémentaires, ils sont généralement dans /home/USERCPANEL/domainesup.com, ajouter simplement domainesup.com

    dossiersexclus = Les dossiers à exclure de la surveillance. Utile pour éviter de recevoir des emails parce que le cache de votre site s'est regénéré. Mettre simplement /LEDOSSIERAEXCLURE
    Le script se chargera d'ajouter /home/USERCPANEL/DOSSIERSURVEILLE/DOSSIEREXCLUS

    C'est tout....

    Le code
    Code:
    #!/usr/bin/python
    # -*- coding: iso-8859-15 -*-
    
    import smtplib
    from email.mime.text import MIMEText
    
    ########################## VARIABLES
    
    # USERCPANEL
    USER = 'VOTREUTILISATEURCPANEL'
    
    # -120 = 120 minutes - cherche les changements effectués dans les 2 heures précédentes
    nbminutes = '-120'
    
    # Email Expéditeur
    from_address = 'votreemail@domaine.com'
    
    # SI BESOIN - Mot de passe du email expéditeur (pour l'envoi)
    mailpass     = 'motdepasseemail'
    
    # Email Destination
    to_address   = 'votreemail@domaine.com'
    
    # Sujet
    emailsubject = 'Changements dans les fichiers'
    
    # Dossiers à surveiller - public_html = site principal, DOMAINE1 = site supplémentaire.
    dossiers = ['public_html',
                'DOMAINE1.com',
                'DOMAINE2.com',
    ]
    
    # Dossiers à exclure.
    # Garder en mémoire que le chemin correspondra à /home/UTILISATEURCPANEL/DOSSIERSURVEILLE/DOSSIEREXCLUS
    # Par exemple, pour exclure /home/UTILISATEURCPANEL/public_html/wp-content/cache mettre seulement '/wp-content/cache'
    dossiersexclus = ['/wp-content/cache',
                      '/assets/cache',
                      '/cache',
                      '/templates_c',
                      '/.ftpquota',
                      '/.well-known',
    ]
    
    # FIN DES VARIABLES - NE PAS MODIFIER LE RESTE
    ########################################
    
    # Fonction email
    def send_mail(from_address, to_address, subject, text):
       msg = MIMEText(text)
       msg['subject'] = subject
       msg['From'] = from_address
       msg['To'] = to_address
    
       s = smtplib.SMTP('localhost')
       #s = smtplib.SMTP('localhost:587')
       #s.ehlo()
       #s.starttls()
       #s.login(from_address, mailpass)
       s.sendmail(from_address, to_address, msg.as_string())
       s.quit
    
    import os
    import subprocess
    import re
    import string
    
    # Fonction lisant les lignes de la commande find,
    def check_lines(output, userpath):
       new_output = ""
       # Sépare ligne par ligne
       lines = output.split('\n')
       for line in lines:
          exclude_it = False
          # Exclusions
          #
          for ex_path in dossiersexclus:
             #if (re.match(userpath + ex_path , line) != None):
             matchObj = re.match(r'(.*)'+ex_path+'(.*?).*' , line, re.M|re.I)
             if matchObj:
                exclude_it = True
                break
          # Exclus les fichiers error_log
          if (re.match(r'(.*error_log$)', line) != None):
             exclude_it = True
          if (exclude_it == False) and (line !=''):
             new_output += line + '\n'
       return new_output
    
    
    message = ""
    # Excution de la commande find sur les éléments à surveiller
    for fwatch in dossiers:
       userpath = "/home/" + USER + "/" + fwatch
       # Commande find
       p = subprocess.Popen(["find", userpath, "-name", "*", "-mmin", nbminutes, "-print"], stdout = subprocess.PIPE)
       output, err = p.communicate()
       if (output != ""):
          processed_output = check_lines(output, userpath)
          if (processed_output != ''):
             message += "Dossier: " + fwatch + '\n'
             message += processed_output + '\n'
    
    # Si changement, envoi du email - sinon, rien.
    if (message != ""):
      send_mail(from_address, to_address, emailsubject, message)
    
    # FIN
    
    Sauvegardez le fichier au nom (exemple) surveillance.sh et envoyez sur votre serveur en ftp (identifiant principal) dans un dossier (exemple) /backups ou /scripts

    Modifiez les permission en 744.

    Dans votre CPANEL, ajoutez une tâche cron
    Code:
    0 */2 * * * /home/USERCPANEL/backups/surveillance.sh >/dev/null 2>&1
    
    Toutes les 2 heures, le fichier sera exécuté et s'il y a eu des changements non prévus, un email vous sera envoyé avec la liste des fichiers concernés.

    Testé sur un World avec des domaines supplémentaires.
     
    Dernière édition: Fév 18, 2017
    David77 aime votre message.
  2. François D

    François D Member

    J'ai mis en place ce truc aujourd'hui et déjà une découverte.

    J'ai reçu un email parce qu'une image d'un de mes wordpress avait été modifiée dans les 2 dernières heures. Particulièrement bizarre.

    wp-includes/images/icon-download.png

    Pour m'apercevoir que cette image ne fait pas partie de Wordpress et que c'était en fait un fichier texte créé par un bout de code inséré dans le fichier
    wp-includes/images/user.php

    En renommant le icon-download.png en icon-download.txt et en voyant le contenu, ça faisait peur.

    Pourtant, mon bidule était SUPPOSEMENT à jour. (Il y a tellement de trous dans ce machin que ça devrait être interdit).

    Heureusement, j'ai pu corriger avant de me faire harakiri par Planethoster. :D
     
  3. David77

    David77 Member

    Ton script examine les fichiers du ou des dossiers spécifiés uniquement, ou est-ce qu'il analyse les sous-dossiers aussi ?
     
  4. François D

    François D Member

    Il fait les sous-dossiers.
     
  5. David77

    David77 Member

    Il ne consomme pas trop de ressources ?
    Je le testerai bien mais entre mon domaine principal et mes domaines supplémentaires..... lol
     
  6. François D

    François D Member

    C'est un simple "find". C'est comme faire un dir ou un ls mais avec une période limitée. Il ne lit pas le contenu des fichiers, simplement la liste des fichiers modifiés depuis X .

    Pour 17 domaines (la plupart avec peu de choses dedans mais certains à plus de 500Mo de données et quelques milliers de fichiers), c'est presque instantané.

    En comparaison, nos scripts de backup sont des usines à gaz. :)
     
  7. David77

    David77 Member

    Alors je vais tester cela ces prochains jours...
    D'autant plus que je reçois beaucoup d'alertes de tentatives brute force sur mes sites...
    Autant vérifier que rien n'est modifié
     
  8. David77

    David77 Member

    Par contre pourquoi le mot de passe smtp ?
    Tu peux détailler cette partie et son fonctionnement ?
     
  9. François D

    François D Member

    Je ne suis moi-même pas un grand fan de cette section même si elle est logique. (A noter que je n'ai pas écrit tout le script, seulement quelques sections pour les besoins).

    En gros, une fonction de mail est créée (def send_mail) et utilise le serveur pour envoyer le mail. Puisque le serveur requiert les identifiants pour les emails sortants....

    Le serveur peut sûrement s'en passer pour un simple email mais n'étant pas expert en python, faudra que quelqu'un d'autres y jette un oeil.
     
  10. David77

    David77 Member

    Moi non plus...

    Bon en regardant cette ligne :
    Code:
    s = smtplib.SMTP('localhost:587')
    On voit que l'envoi se fera via le port 587 du SMTP, qui requiert une authentification certes, mais n'est pas sécurisé (identique au port 25 pour faire simple)

    A tester avec le port 465 qui lui est sécurisé en SSL (d'aprés la configuration donnée par le cPanel)

    Je testerai cela aprés avoir créé une adresse email spécifique pour ce script
     
  11. David77

    David77 Member

    Est-ce qu'il y a un moyen de vérifier le bon fonctionnement du script et de son exécution ?
    Je l'ai mis en place juste aprés mon précédent message mais je n'ai aucun email... Alors peut être est-ce normal mais dans le doute.....
     
  12. François D

    François D Member

    C'est normal s'il ne trouve aucun fichier modifié dans les dernière X minutes.

    Pour le tester, comme n'importe quel scripts qu'on peut exécuter via ssh,

    Se connecter en ssh, naviguer au fichier et l'exécuter.
    Exemple
    Code:
    $ cd /scripts
    $ ./watchfiles.sh
    
    Pour voir s'il envoi un email, peut-être commenter le if (message......) :
    Code:
    # Si changement, envoi du email - sinon, rien.
    # if (message != ""):
      send_mail(from_address, to_address, emailsubject, message)
    
    Il enverra un email vide.

    Ou faire n'importe quel changement (modifier un fichier ou uploader)..
    Et rééxécuter...

    J'ai ajouté dans la liste des exclusions :
    Code:
                     '/*/.ftpquota',
    
     
  13. David77

    David77 Member

    Etant en mutualisé je n'ai pas accès au SSH
    Je vais ajouter un fichier et voir ce que ça donne
     
  14. François D

    François D Member

    Oh... bah en théorie, si des scripts peuvent être exécutés... ça devrait fonctionner.

    Peut-être qu'il serait bon de ne pas mettre
    Code:
    ....... >/dev/null 2>&1
    
    dans la tâche cron pendant le test. S'il y a une erreur, elle sera envoyée par email.
     
  15. David77

    David77 Member

    C'est exactement ce que j'ai fait en plus de l'ajout d'un fichier ds public_html ;):)
     
  16. François D

    François D Member

    Bon en réalité, j'ai testé sur mon "World" et il n'a pas besoin du mot de passe pour envoyer le mail.

    J'ai laissé les lignes mais en commentaire. (Premier post modifié).
    Code:
    # Fonction email
    def send_mail(from_address, to_address, subject, text):
       msg = MIMEText(text)
       msg['subject'] = subject
       msg['From'] = from_address
       msg['To'] = to_address
    
       #s = smtplib.SMTP('localhost:587')
       s = smtplib.SMTP('localhost')
       #s.ehlo()
       #s.starttls()
       #s.login(from_address, mailpass)
       s.sendmail(from_address, to_address, msg.as_string())
       s.quit
    
     
  17. David77

    David77 Member

    ok alors j'ai une erreur étrange...
    Code:
    /bin/bash: /home/user/surveillance/surveillance.sh: /usr/bin/python^M: bad interpreter: No such file or directory
    Pourtant le fichier est bien là où c'est mis....
     
  18. David77

    David77 Member

    Bon alors en cherchant un peu j'ai pu fixer l'erreur... En passant le fichier au format Unix et en l'encodant en UTF8 ;)

    Par contre j'ai encore quelques questions :D

    Dans l'email reçu j'ai ça :
    Code:
    username: public_html
    
    Suivi d'une liste de dossiers et fichiers ayant comme racine "public_html"
    Est-ce normal ce "username" au début de l'email ? Il s'agit du 1er dossier du début de boucle ? Si c'est bien le cas peut être le renommer en "Domaine analysé" ou un truc du genre...

    Ensuite dans ton fichier d'exemple, pour les exclusions tu mets :
    Code:
    dossiersexclus = ['/wp-content/cache',
                      '/assets/cache',
                      '/cache',
                      '/templates_c',
                      '/*/.ftpquota',
    ]
    Le "/*/.ftpquota" signifie que le script va exclure tous les dossiers ".ftpquota" quelque soit l'endroit où il se trouve ?
    On peut donc faire de même avec les dossiers "cache" et "tmp" ?
    Du style :
    Code:
    dossiersexclus = ['/*/cache',
                      '/*/templates_c',
                      '/*/tmp',
    ]
    Comme dans ton exemple tu as plusieurs dossiers "cache" que tu prends soin de bien spécifier, je me pose la question :oops::oops:
     
  19. François D

    François D Member

    Effectivement, ça devrait être "Path" ou "Dossier" et non "username". Le script original était conçu pour une utilisation par utilisateur.

    Le .ftpquota est un fichier que je vois à la racine de tous mes domaines. Visiblement installé automatiquement par Planethoster/CPANEL. J'avais l'intention de demander plus tard à quoi il servait. Sur de l'illimité, je me pose la question de l'intérêt.

    Lorsque le script passe, ce fichier apparaît régulièrement comme étant modifié (surtout après des transferts par ftp). J'ai donc rajouté cette exclusion pour arrêter de le voir dans la liste. le /*/.ftpquota est pour ne pas le réécrire pour tous les domaines ET sous-domaines (domaine.com/sous_domaine).

    En théorie, effectivement, le résultat devrait être le même avec ce que tu as mis pour les dossiers :
    Code:
    dossiersexclus = ['/*/cache',
                     '/*/templates_c',
                      '/*/tmp',
    ]
    
    Le script original ne semblait pas supposer qu'on puisse avoir plusieurs dossiers à différents étages.
     
  20. David77

    David77 Member

    Je vais tenter cette modification... Elle semble logique après tout :rolleyes:
    Sous Joomla, par exemple, tu as un dossier cache à la racine et dans le sous-dossier de l'administration....
     
  21. David77

    David77 Member

    Bon alors aprés essai, cette modification pour les dossiers exclus ne marchent pas :(
    Il faut donc mettre un à un les dossiers à exclure même si il y en a plusieurs :(
     
  22. François D

    François D Member

    Script modifié...

    Changer :
    Code:
             if (re.match(userpath + ex_path , line) != None):
                exclude_it = True
                break
    
    pour
    Code:
             #if (re.match(userpath + ex_path , line) != None):
             matchObj = re.match(r'(.*)'+ex_path+'(.*?).*' , line, re.M|re.I)
             if matchObj:
                exclude_it = True
                break
    
    (Premier post modifié).

    Les exclusions peuvent maintenant être :
    Code:
    dossiersexclus = ['/wp-content/cache',
                      '/cache',
                      '/template_c',
                      '/tmp',
                      '/.ftpquota',
    ]
    
    Ils seront exclus peu importe leur position dans l'arborescence. ;)
     
  23. David77

    David77 Member

    Au top, tu as pu le tester ?
    J'essaie cette modification dès ce soir dans ce cas ;)
    Ça peut être super intéressant dans le cas de plusieurs domaines avec des CMS qui utilisent des systèmes de cache similaires (même intitulé de dossier)
     
  24. François D

    François D Member

    Oui, testé!

    Au lieu de chercher l'adresse complète (exemple : /home/tralala/x/y/cache), il cherche */cache* dans l'adresse /home/tralala/x/y/cache

    Il reste un seul minuscule petit bug (qui ne l'empêche pas de fonctionner normalement). C'est que des fois, je reçois un email avec la racine d'un domaine comme si ce dernier avait été modifié.

    Dossier :
    /home/cedomaine.tld

    Je soupçonne qu'en voyant le .ftpquota (parce que cela arrive après des transferts), même s'il est exclu, il affiche la racine juste avant. Mais à part ça, cela semble fonctionner comme on le souhaite.
     
  25. David77

    David77 Member

    Oui j'avais remarqué la même chose sur des dossiers n'ayant pas ftpquotat
    Après peut-être que le dossier racine à sa date de modification qui change ponctuellement sur certaines modifications....
     
  26. François D

    François D Member

    J'ai changé dans le mien

    Code:
          if (exclude_it == False) and (line !=''):
    
    pour
    Code:
          if (exclude_it == False) and (line !='') and os.path.isdir(line) == False:
    
    De cette façon, il n'affichera QUE les fichiers modifiés et non un dossier parce qu'à la limite, à moins d'un changement de permission sur un dossier...

    Mais je ne pense pas que ce soit une très grande idée. Si on ajoute un dossier vide, il n'apparaîtra pas. En même temps, ajouter un dossier vide, c'est complètement con donc... Sauf s'il est ajouté par un tiers mais s'il le fait, ce n'est pas pour le laisser vide non plus.

    A déterminer.
     
  27. David77

    David77 Member

    Moui... A garder dans un coin comme modif mais celui de base est déjà vraiment pas mal ;)
     
  28. David77

    David77 Member

    Grâce à ton script je viens de voir qu'à la racine de tous mes domaines a été ajouté
    Code:
    /.well-known/acme-challenge
    Une idée de ce que c'est ????
     
  29. François D

    François D Member

    Créé par le système automatique de certificat SSL. Tu peux l'enlever mais il reviendra au renouvellement du certificat.
     
  30. David77

    David77 Member

    Dans ce cas je suis rassuré...
    Autant les laisser lol
     

Partager cette page