Veille sécuritaire avec flux RSS (part 1)

Veille sécuritaire

Il existe sur Internet, de nombreux sites de veille sécuritaire permettant de vous maintenir au courant des dernières vulnérabilités découvertes. Si vous utilisez une application open-source (forum, blog, cms …), des visites régulières sur ces sites ou abonnement aux flux RSS, vous permettent de maintenir à jour votre application et minimiser les risques de piratages.

Mais, que vous utilisiez une application web open-source (ou non) à jour, cela n’empêche pas les tentatives d’attaques, c’est pourquoi je vous propose ici un petit script python pour effectuer vous même votre veille sécuritaire.

Cet article est la 1ère partie d’une série de 3 :

  • Part 1: Configuration des logs apache, création de la BDD puis présentation et configuration du script de traitement.
  • Part 2: Script de génération du flux RSS avec présentation détaillées des informations dans le flux.
  • Part 3: Amélioration du script avec des expressions régulières plus poussés, rajout d’options …

Il est intéressant de noter que la Partie 1 est la plus importante, les 2 autres parties pouvant être réalisées sans attendre la suite de l’article si vous savez programmer.

Logs apache, BDD & script de traitement

Configuration des logs apache

Ce script de veille se base sur les fichiers de logs apache, il faut donc vous assurer dans un premier temps que les directives de logs sont bien renseignées au niveau de votre configuration.
L’article ne portant pas sur la configuration d’apache, je ne vais pas m’étendre dessus, voici donc les lignes nécessaires à la génération des logs.

LogFormat %V %{canonical}p %h %{X-Forwarded-For}i %{PHPSESSID}C %t \"%r\" %gt;s %b \"%{Referer}i\" \"%{User-Agent}i\"
ErrorLog /var/log/apache/my_error_log

Ceci est un exemple de format de log, vous pouvez le modifier sans problème :

  • %V: Nom du serveur
  • %{canonical}p: port
  • %h: IP du visiteur
  • %{X-Forwareded-for}i: IP du proxy
  • %{PHPSESSID}C: ID de session (pratique pour retracer le chemin d’un pirate potentiel)
  • %t: Date
  • %r: Requête
  • %>s: Status de la requête
  • %b: Taille de la requête
  • %{Referer}i: Referer (lien précédent)
  • %{User-Agent}i: Informations sur le navigateur du visiteur

Pour plus d’infos, je vous invite à consulter la documentation officielle concernant les directives logs d’apache.
Une fois votre fichier modifié, n’oubliez pas de le recharger. Un simple reload doit normalement suffire.

/etc/init.d/apache2 reload

Création de la BDD MySQL

Les informations traitées par le script seront enregistrées en BDD pour pouvoir garder une trace, voici le code SQL pour la création des tables concernées.

--
-- Création de la table des attaques potentielles recensées
--
 
CREATE TABLE `veille_vuln` (
	`id_vuln` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
	`ip` VARCHAR(15),
	`url` text,
	`log` text,
	`temps` datetime,
	`id_type` tinyint(3) UNSIGNED,
	PRIMARY KEY (`id_vuln`),
	UNIQUE KEY (`ip`,`url`(150),`log`(150),`temps`,`id_type`)
) ENGINE=MyISAM DEFAULT CHARSET utf8;
 
--
-- Création de la table des types d'attaques surveillées
--
 
CREATE TABLE `veille_vuln_type` (
	`id_type` tinyint(3) UNSIGNED NOT NULL DEFAULT '0',
	`nom` VARCHAR(45),
	`link` VARCHAR(250),
	PRIMARY KEY (`id_type`)
) ENGINE=MyISAM DEFAULT CHARSET utf8;
 
--
-- Insertion des types de vulnérabilités surveillées
--
 
INSERT INTO `veille_vuln_type` (`id_type`, `nom`, `link`) VALUES 
('1', 'XSS', 'http://fr.wikipedia.org/wiki/Cross_site_scripting'),
('2', 'Unicode', ''),
('3', 'Include', ''),
('4', 'SQL Injection', 'http://fr.wikipedia.org/wiki/Injection_SQL'),
('5', 'Mot sensible', '');

Script veille-secu

Une fois les logs apache configurés et la BDD créée, nous allons nous attaquer au script de traitement.
En début de script, appel des différentes librairies nécessaires.

#!/usr/bin/python
 
# Importation des librairies necessaires
import re, cgi, time, MySQLdb, sys
from getopt import getopt,GetoptError

Ensuite, la partie à configurer pour :

  • Effectuer la connexion à la BDD
  • Indiquer le fichier pointer
  • Gérer vos expressions régulières.

Le fichier pointer va vous permettre de conserver la dernière ligne de logs traitée par le script afin de ne pas repasser sur tout le fichier et utiliser des ressources mémoires inutilement.
Juste après ces infos, deux bloc d’expressions régulières qui permettront de traiter la ligne de log pour la découper puis de détecter une éventuelle tentative d’attaque.

#---------------------------------#
#	PARTIE A CONFIGURER
#---------------------------------#
 
# Declaration des variables generales (chemin absolu)
file_pointer = '/path/to/your/pointer'
 
# Declaration des variables de connexion a la BDD MySQL
bdd_host	= '127.0.0.1'
bdd_user	= 'mon_user'
bdd_pass	= 'mon_pass'
bdd_db		= 'ma_bdd'
 
# Declaration des regexp de traitement des logs
pattern_ip 	= re.compile('[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
pattern_date 	= re.compile('[0-9]{2}\/...\/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2}')
pattern_url 	= re.compile('(GET|POST|PUT|DELETE|HEAD|PROPFIND|LOCK|OPTIONS)\ [^ ]*\ ')
 
# Declaration des regexp de detection d'attaques potentielles
p_faille_xss 	=  re.compile('((\%3C)|<)[^\n]+((\%3E)|>)', re.IGNORECASE)
p_faille_unicode = re.compile('\.exe|\/syntaxe\/winnt\/system32\/', re.IGNORECASE)
p_faille_inc 	= re.compile('(http|ftp):\/\/|\/etc\/(passwd|shadow)|\/(sbin|bin)\/|\.\.\/', re.IGNORECASE)
p_faille_sql 	= re.compile('(\ |%20)(union|or)(\ |%20)', re.IGNORECASE)
p_faille_word 	= re.compile('(root|htaccess|passwd|\.log|\.conf)', re.IGNORECASE)

La suite du fichier concerne le traitement des options et des arguments qu’il est possible de passer au script, ici une seule option « –help » et un seul argument « le fichier ». Nous verrons dans la partie 3 des cas beaucoup plus poussés pour faire de ce script un mini IDS et limiter au maximum les faux-positifs.

#-----------------------------------------------------#
#	NE PAS MODIFIER CETTE PARTIE
#-----------------------------------------------------#
 
# Declaration des options et arguments
optionmap = [
	["-h", "--help", "Affiche l'aide"],
	["-f", "--logfile", "Specifie le fichier de log a traiter (chemin absolu)"],
]
 
# Traitement des options et arguments
args = []
params = []
try:
	args, params = getopt(sys.argv[1:], "".join([o[0][1] for o in optionmap]), \
		[x[2:] for x in reduce(lambda x,y: x+y, [z[1:-1] for z in optionmap])])
	args = [a for a,b in args]
 
	log = 0
	mode = None
 
	for option in ["--help", "-h"]:
		if option in args:
			args.remove(option)
			mode = "help"
 
	for option in ["--logfile", "-f"]:
		if option in args:
			args.remove(option)
			log = params.pop(0)
 
# Traitement des errreurs
except GetoptError, e:
	sys.stderr.write("option invalide: ")
	sys.stderr.write(str(e)+"\n")
	mode = "help"
 
# Affichage de l'aide si le mode "help" est defini ou si le fichier de log est equivalent a 0
if mode == "help" or log == 0:
	sys.stderr.write("Syntax: veille-secu \n")
	for m in optionmap:
		sys.stderr.write(m[0] + "\t" + m[1] + "   \t: " + m[-1] + "\n")
		for o in m[2:-1]:
			sys.stderr.write("\t" + o + "\n")
	sys.exit(1)

Connexion à la BDD avec les informations renseignées en début de fichier.

# Connexion a la BDD
cnx = MySQLdb.connect(
	host	= bdd_host,
	user	= bdd_user,
	passwd	= bdd_pass,
	db		= bdd_db
)
c = cnx.cursor()

Comme expliqué précédemment, nous allons ici vérifier l’existence et la valeur du pointer pour ne pas reprendre le fichier complet puis ouvrir le fichier de log au niveau du pointer.
Nous verrons en fin d’article la possibilité de remettre ce pointer à 0 si vous avez un logrotate activé sur votre serveur.

# Vérification du pointer pour ne pas reprendre le fichier a 0
pointer = 0
verif = open(file_pointer,'r')
for num in verif.xreadlines():
	pointer = int(num)
verif.close()
 
# Ouverture du fichier de log et vérification du pointer
logapache = open(log,'r')
if pointer &gt; 0:
	logapache.seek(pointer, 0)

Traitement du fichier ligne par ligne. A chaque ligne de log, le script va analyser l’url et déterminer, en fonction des regexp définies plus haut, si la requête est une tentative d’attaque ou non.
Dans cet exemple du script, les regexp de détection sont assez basiques, dans la partie 3 nous élaborerons des techniques plus poussées.

Si une attaque potentielle est détectée, alors elle sera enregistrée en BDD pour générer par la suite le flux RSS ou tout simplement consulter les différents enregistrements à partir d’une interface web comme nous le verrons dans la partie 2 de l’article.

# Traitement du fichier ligne par ligne
k=1
for line in logapache.xreadlines():
	# Initialisation des Variables
	data_ip 		= 'NULL'
	data_date		= 'NULL'
	data_url		= 'NULL'
	# IP
	field_ip = pattern_ip.search(line)
	if field_ip:
		data_ip = field_ip.group()
	# DATE
	field_date = pattern_date.search(line)
	if field_date:
		tmp_date = time.strptime(field_date.group(), "%d/%b/%Y:%H:%M:%S")
		data_date = time.strftime("%Y-%m-%d %H:%M:%S", tmp_date)
	# URL
	field_url = pattern_url.search(line)
	if field_url:
		data_method,data_url = (field_url.group().strip().split() + ['0','0'])[:2]
		# ATTAQUES POTENTIELLES
		field_faille_xss 		= p_faille_xss.search(data_url)
		field_faille_unicode 	= p_faille_unicode.search(data_url)
		field_faille_inc 		= p_faille_inc.search(data_url)
		field_faille_sql 		= p_faille_sql.search(data_url)
		field_faille_word 		= p_faille_word.search(data_url)
		if field_faille_xss or field_faille_unicode or field_faille_inc or field_faille_sql or field_faille_word:
			if field_faille_xss:
				faille_type = '1'
			elif field_faille_unicode:
				faille_type = '2'
			elif field_faille_inc:
				faille_type = '3'
			elif field_faille_sql:
				faille_type = '4'
			elif field_faille_word:
				faille_type = '5'
			c.execute("INSERT IGNORE veille_vuln (ip, url, log, temps, id_type) VALUES (%s, %s, %s, %s, %s)", (data_ip, data_url, line, data_date, faille_type))
			k = k + 1

Une fois le fichier traité, nous allons successivement :

  • Fermer la connexion MySQL
  • Récupérer la position du pointer
  • Fermer le fichier de log

# Fermeture de la connexion MySQL
cnx.commit()
c.close()
 
# Recuperation position actuelle du pointer puis fermeture du fichier
eof = logapache.tell()
logapache.close()
 
# Sauvegarde du pointer en cours
new_verif = open(file_pointer,'w')
new_verif.write(str(eof))
new_verif.close()

Si vous n’avez qu’un seul fichier de log que vous videz de temps en temps, il suffit de mettre en tâche cron le script veille-secu, mais si vous utilisez un logrotate, il faudra rajouter une étape.

Ci-dessous, on fait tourner le script toutes les 5min.

*/5	*	*	*	*	/usr/sbin/veille-secu -f /path/to/your/apache_log

Dans le cas d’une utilisation de logrotate, dans votre fichier de conf, on va ajouter au niveau de postrotate une commande permettant de remettre le pointer à 0 à chaque rotation de log.

postrotate
	if [ -f /var/run/apache2.pid ]; then
		/etc/init.d/apache2 restart &gt; /dev/null
		echo 0 &gt; /path/to/your/pointer
	fi
endscript

Voilà, j’espère que ce premier article vous plaira sinon Tim risque de pas être content et il ne voudra plus venir me voir ^^

Tags: , , , , ,

A propos de l'auteur

Développeur web & système spécialisé en sécurité. Passionné par tout ce qui touche au web et la sécurité informatique, vous pouvez le retrouvez sur twitter.

Vous avez aimé ce billet? Faites le savoir!

  • Delicious
  • Twitter
  • Technorati Favorites
  • FriendFeed
  • Google Bookmarks
  • Share

3 Réponses

  1. Kévin Dunglas 29 août 2009 à 12 h 36 min #

    Sans quoi il y’a Snort qui est très complet : http://www.snort.org/

  2. mogito 31 août 2009 à 16 h 29 min #

    Il est vrai qu’il existe des IDS très complets et beaucoup plus performants, le but ici est surtout d’avoir un petit script rapide à mettre en place et qui marche sans avoir à configurer un IDS.

    Les IDS sont très bien, snort notamment puisque c’est celui que tu cites, mais sa configuration, quand on ne connait pas, est beaucoup plus longue et prend beaucoup plus de temps si on veut éviter les faux-positifs, très nombreux sur snort.

    De même snort permet de surveiller tout le traffic, ici on s’intéresse seulement aux requêtes web.

    Cela dit, Snort est une très bonne solution ;)

  3. Tim 31 août 2009 à 16 h 36 min #

    Dites, d’ailleurs, c’est quoi un IDS?


Laisser un message