[OpenBSD]

[Section précédente : Tables] [Index] [Section suivante : Traduction des Adresses IP ("NAT")]

PF : Le Filtrage de Paquets


Table des Matières


Introduction

Le filtrage de paquets à l'aide de pf(4) consiste à autoriser ou bloquer le trafic réseau en fonction des propriétés des protocoles des couches 3 (IPv4 et IPv6) et 4 (TCP, UDP, ICMP, et ICMPv6). Les adresses et ports source et destination ainsi que les protocoles utilisés sont des critères fréquemment employés.

Les règles de filtrage énumèrent les critères auxquels doivent se conformer les paquets et spécifient les actions qui y sont associées : bloquer ou laisser passer. Ces règles sont évaluées de façon séquentielle de la première à la dernière (du haut vers le bas dans les fichiers de règles utilisés). Sauf utilisation du mot-clef quick dans l'une d'entre elles, chaque paquet est évalué à l'aide de toutes les règles avant qu'une décision finale ne soit prise. L'action (block ou pass) associée à la dernière règle dont les critères se rapportent au paquet traité est appliquée. La première règle est un tout laisser passer implicite de sorte que si aucune règle n'est applicable à un paquet, celui-ci est accepté (pass).

Syntaxe

L'écriture des règles obéit à la syntaxe très simplifiée suivante :
action [direction] [log] [quick] [on interface] [af] [proto protocol] \
   [from src_addr [port src_port]] [to dst_addr [port dst_port]] \
   [flags tcp_flags] [state]
action
Ce mot-clef indique le type d'action associé à tout paquet correspondant aux critères contenus dans la règle. Les deux valeurs possibles sont pass et block. pass signifie que le paquet sera transféré au noyau pour être traité. Le blocage du paquet (block) sera fonction de la politique définie par les options block-policy. L'action associée par défaut peut être modifiée en spécifiant block drop ou block return.
direction
Ce mot-clef spécifie le sens du trafic vu depuis l'interface réseau : celui-ci peut être entrant (in) ou sortant (out).
log
La présence de ce mot-clef dans une règle déclenche la journalisation des paquets correspondants à la règle grâce à l'utilitaire pflogd(8). Si les options keep state, modulate state, ou synproxy state sont également renseignées, seul le paquet correspondant à l'ouverture de la session est conservé. Pour conserver tous les paquets d'une session, il faut utiliser le mot-clef log (all).
quick
Si le mot-clef quick est utilisé dans une règle, celle-ci est considérée comme étant la dernière à prendre en compte et l'action spécifiée est prise.
interface
Ce mot-clef désigne l'interface sur laquelle le trafic est à analyser. Il est possible de grouper plusieurs interfaces. Dans ce cas, le groupe est désigné par le nom générique de l'interface non suivi d'un numéro. Par exemple : ppp ou fxp. La règle s'appliquera aux paquets traversant toute interface de type ppp ou fxp respectivement.
af
Il est possible de spécifier la famille d'adresses IP à laquelle appliquer une règle à l'aide de ce mot-clef. Les deux valeurs possibles sont inet pour les adresses IPv4 ou inet6 pour IPv6. PF peut généralement déterminer à quelle famille appartient un paquet à partir de ses adresses source et destination.
protocol
Ce mot-clef identifie les protocoles de couche 4 utilisés. Il peut prendre les valeurs suivantes :
src_addr, dst_addr
Ces mots-clefs identifient respectivement les adresses source et destination du paquet telles que contenues dans son en-tête IP. Ces adresses peuvent être spécifiées :
src_port, dst_port
Ces mots-clefs identifient respectivement les ports source et destination qui apparaissent dans les en-têtes des protocoles de couche 4. Ces ports peuvent être spécifiés :
tcp_flags
Spécifie les drapeaux qui doivent être activés dans l'en-tête TCP lors de l'utilisation de proto tcp. Cette valeur est spécifiée ainsi : flags check/mask. Par exemple : flags S/SA. Dans ce cas, PF teste la valeur des drapeaux S et A (SYN et ACK) pour savoir s'ils sont positionnés. Si le drapeau SYN est positionné, et uniquement ce drapeau, alors la règle est applicable.
state
Ce mot-clef indique dans quel état doit être le paquet pour satisfaire à une règle.

Blocage par défaut

Il est recommandé d'adopter une approche de blocage par défaut lors de la configuration d'un pare-feu. Cela signifie que tout est interdit et que l'on autorise le trafic au cas par cas. Cette approche est assimilable à l'application d'un principe de précaution et simplifie l'écriture des règles.

Cette politique de blocage par défaut repose sur deux règles :

block in  all
block out all

Cela suffit à interdire tout trafic dans un sens comme dans l'autre et ce sur toutes les interfaces de la machine.

Laisser passer le trafic

Une fois notre politique restrictive mise en place, il faut spécifier quelles sont les connexions autorisées. C'est là qu'entrent en jeu les critères décrits précédemment : adresse et port source et destination, protocole, etc. Chaque fois qu'un paquet est autorisé à franchir les murs du pare-feu, les règles correspondantes devront être les plus restrictives possible : il s'agit de n'autoriser que le trafic voulu et lui seul.

Quelques exemples:

# Autoriser le trafic entrant sur l'interface dc0
# en provenance du réseau local 192.168.0.0/24,
# à destination de la machine dont l'adresse IP est 192.168.0.1.
# Dans le même temps, autoriser le trafic sortant par l'interface dc0.
pass in  on dc0 from 192.168.0.0/24 to 192.168.0.1
pass out on dc0 from 192.168.0.1 to 192.168.0.0/24


# Autoriser le trafic TCP entrant sur l'interface fxp0
# à destination d'un serveur HTTP.
# Le nom de l'interface - fxp0 - est utilisé comme adresse de destination
# pour les paquets autorisés. pass in on fxp0 proto tcp from any to fxp0 port www

L'option quick

Comme nous l'avons vu, chaque paquet est testé au regard de toutes les règles de filtrage, de la première à la dernière. Par défaut, un paquet est marqué à chaque test. Le résultat final peut ainsi changer d'une règle à l'autre jusqu'à ce que toutes aient été parcourues. Rappelons que c'est la dernière règle à laquelle correspond un paquet qui l'emporte sur les autres. Il y a cependant une exception : si l'option quick est présente dans une règle et qu'un paquet correspond aux critères de cette règle, il n'y a plus de tests : la règle en question est alors considérée par PF comme étant la dernière. Les exemples suivants illustrent ce cas de figure :

Mauvais :

block in on fxp0 proto tcp from any to any port ssh
pass  in all

La ligne block n'aura aucun effet. Les paquets seront bien évalués suivant ses critères, mais la ligne suivante annulera tout effet de cette première règle.

Mieux :

block in quick on fxp0 proto tcp from any to any port ssh
pass  in all

A première vue c'est la même chose. Si la ligne block correspond, l'option quick provoque l'arrêt des tests pour chaque paquet qui correspond aux critères de la première règle. Les paquets qui n'y satisfont pas seront quant à eux testés au regard des critères de la règle suivante.

Conserver l'état

Une des fonctionnalités les plus importantes de PF est sa capacité à conserver l'état des connexions. PF est capable d'évaluer un paquet non plus unitairement mais dans le contexte de la connexion à laquelle il appartient. PF utilise pour cela une table d'état grâce à laquelle PF peut rapidement déterminer si un paquet fait partie d'une connexion déjà établie et autorisée. Si tel est le cas, le paquet est transféré sans test complémentaire.

Conserver l'état des connexions a pour avantage de simplifier les règles de filtrage et d'améliorer les performances. PF évalue les paquets quel que soit leur sens : il n'est alors plus nécessaire d'écrire les règles pour les paquets des flux retour. PF consacre ainsi beaucoup moins de temps à inspecter les paquets.

Quand l'option keep state est utilisée dans une règle, le premier paquet qui déclenche l'activation de celle-ci provoque la création d'un enregistrement dans la table d'état des connexions en cours. Par la suite, non seulement les paquets allant de l'expéditeur au destinataire sont rattachés à cette table et donc autorisés à passer, mais également les paquets qui appartiennent aux réponses du destinataire. Par exemple :

pass out on fxp0 proto tcp from any to any keep state

Cette règle autorise les connexions TCP sortantes sur l'interface fxp0 mais aussi les paquets retour. L'option keep state permet donc d'augmenter les performances du pare-feu car la recherche dans la table d'état est beaucoup plus rapide que l'opération consistant à évaluer un paquet au regard de toutes les règles de filtrage.

L'option modulate state fonctionne de la même façon que l'option keep state à ceci près qu'elle ne s'applique qu'aux paquets TCP sortants. L'option modulate state renforce le caractère aléatoire de leurs numéros de séquence initiaux (ISN). Cette option permet de renforcer la sécurité de certains systèmes d'exploitation ne sachant pas générer des ISN suffisamment aléatoires. A partir de la version 3.5 d'OpenBSD, l'option modulate state peut être utilisée pour les autres protocoles que TCP.

Pour conserver l'état des connexions TCP, UDP et ICMP tout en renforçant la sécurité des ISN :

pass out on fxp0 proto { tcp, udp, icmp } from any \
    to any modulate state

Un des avantages de conserver l'état des connexions tient à ce que les messages ICMP relatifs à celles-ci seront traités comme faisant partie de la connexion. Si des messages ICMP sont émis par une machine pour signaler une congestion par exemple, et que l'option keep state est activée, les messages seront pris en compte et acceptés par le pare-feu. Sans cela, ils auraient été bloqués ou ignorés.

La portée d'une entrée dans la table d'état dépend des options state-policy d'une manière globale ou bien des options if-bound, group-bound et floating-state. Les valeurs appliquées règle par règle on la même signification que lorsqu'elles sont utilisées avec l'option state-policy. Par exemple :

pass out on fxp0 proto { tcp, udp, icmp } from any \
    to any modulate state (if-bound)

Selon cette règle, les paquets ne trouveront une correspondance dans la table d'état que s'ils transitent par l'interface fxp0.

Il faut noter que les règles nat, binat et rdr créent implicitement des entrées dans la table d'état.

Conserver l'état des connexions UDP

On entend souvent dire qu'il est impossible d'utiliser la table d'état avec UDP car c'est un protocole sans état. S'il est vrai qu'une connexion UDP n'utilise pas stricto sensu le concept d'état tel que le fait une connexion TCP (à savoir une ouverture et une terminaison explicites de la connexion), cela n'a aucune conséquence sur la capacité qu'a PF de créer et gérer des états pour les connexions UDP. Pour ce type de protocole sans début ou fin de connexion explicites, PF conserve simplement une trace de la durée d'une session. S'il s'écoule un certain laps de temps sans échange de paquets entre les deux parties, l'entrée associée à la session dans la table d'état est supprimée. Ce laps de temps peut être configuré dans la section options du fichier pf.conf.

Options de Suivi Stateful

Quand une règle de filtrage crée une entrée dans la table d'états suite à l'utilisation des mots clé keep state, modulate state ou synproxy state, certaines options peuvent être spécifiées afin de contrôler le comportement de ces créations d'états. Les options suivantes sont disponibles :
max number
Limite le nombre maximum d'entrées d'états que la règle peut créer à number. Si le maximum est atteint, les paquets qui devraient normalement créer un état sont rejetés jusqu'à ce que le nombre d'états existants diminue.
source-track
Cette option active le suivi du nombre d'états créés par adresse IP source. Cette option a deux formats : Le nombre total d'adresses IP source suivies globalement peut être contrôlé via l'option src-nodes runtime.
max-src-nodes nombre
Lorsque l'option source-track est utilisée, max-src-nodes limitera le nombre d'adresses IP source pouvant créer simultanément une entrée. Cette option peut seulement être utilisée avec une règle source-track.
max-src-states nombre
Lorsque l'option source-track est utilisée, max-src-states limitera le nombre d'entrées d'états simultanées pouvant être crées par adresse IP source. La portée de cette limite (les états créés par cette règle uniquement ou les états créés par toutes les règles utilisant source-track) est dépendante de l'option source-track spécifiée.

Une règle d'exemple :

pass in on $ext_if proto tcp to $web_server \
    port www flags S/SA keep state \
    (max 200, source-track rule, max-src-nodes 100, max-src-states 3)

La règle ci-dessus définit le comportement suivant :

Un jeu séparé de restrictions peut être placé sur les connexions TCP stateful qui ont une poignée de main "3-way handshake" complète.

max-src-conn nombre
Limiter le nombre maximum de connexions TCP simultanées ayant réalisé la poignée de main qu'un hôte peut initier.
max-src-conn-rate nombre / intervalle
Limiter le taux de nouvelles connexions à un certaine fréquence.

Ces deux options font appel à l'option source-track rule et sont incompatibles avec source-track global.

Ces limites étant placées sur les connexions TCP ayant réalisé la poignée de main TCP, des connexions plus agressives pourront toujours avoir lieu depuis les adresses IP concernées.

overload <table>
Mettre l'adresse d'un hôte concerné dans la table désignée.
flush [global]
Tue toutes les autres connexions qui correspondent à cette règle et qui ont été créées par cette adresse IP source. Quand global est spécifié, cela tue tous les états correspondant à cette adresse IP source, sans discernement de la règle qui a créé cet état.

Un exemple :

table <abusive_hosts> persist
block in quick from <abusive_hosts>

pass in on $ext_if proto tcp to $web_server \
    port www flags S/SA keep state \
    (max-src-conn 100, max-src-conn-rate 15/5, overload <abusive_hosts> flush)

Ceci permet de :

Les drapeaux TCP

On utilise souvent les drapeaux TCP dans des règles pour traiter les ouvertures de sessions. Ces drapeaux et leur signification sont présentés dans la liste suivante :

Le mot-clef flags doit apparaître dans une règle si l'on souhaite que PF prenne en compte la valeur des drapeaux TCP d'un paquet. La syntaxe est la suivante :

flags check/mask

La partie mask de la règle indique la liste des drapeaux que PF doit inspecter. La partie check quant à elle spécifie les drapeaux qui doivent être positionnés pour que la règle s'applique au paquet traité.

pass in on fxp0 proto tcp from any to any port ssh flags S/SA

Cette règle s'applique aux paquets TCP dont le drapeau SYN est positionné. PF limite son inspection aux drapeaux SYN et ACK. La règle s'applique donc à un paquet dont les drapeaux SYN et ECE sont positionnés mais pas à un paquet dont les drapeaux SYN et ACK ou dont seul le drapeau ACK sont positionnés.

Notez que les précédentes versions d'OpenBSD acceptaient cette syntaxe :

. . . flags S

Ce qui n'est plus vrai : le masque doit maintenant toujours être renseigné.

Les drapeaux sont souvent utilisés avec l'option keep state pour mieux contrôler la création des entrées dans la table d'état :

pass out on fxp0 proto tcp all flags S/SA keep state

Une entrée est créée pour tous les paquets TCP sortants dont le drapeau SYN est positionné.

Il faut manipuler les drapeaux avec prudence et se méfier des mauvais conseils. Certaines personnes suggèrent de ne créer des entrées que pour les paquets dont le drapeau SYN est positionné. Ce qui peut aboutir à cette règle :

     . . . flags S/FSRPAUEW  mauvaise pioche !!

En théorie, une session TCP commence par un paquet dont le drapeau SYN est positionné. Toujours en théorie, il ne faut créer une entrée dans la table d'état que pour ce genre de paquets. Mais certains systèmes utilisent le drapeau ECN en début de session. Ces paquets sont rejetés par la règle précédente. Une meilleure solution est :

. . . flags S/SAFR

Si le trafic est normalisé, il peut être pratique et sûr de ne pas tester la valeur des drapeaux FIN et RST. Dans ce cas, PF rejette tout paquet entrant dont les drapeaux TCP sont positionnés de manière illicite (par exemple SYN et RST) et les combinaisons potentiellement ambiguës (telles que SYN et FIN) seront normalisées. Il est fortement recommandé de toujours normaliser (scrub) le trafic entrant :

scrub in on fxp0
.
.
.
pass in on fxp0 proto tcp from any to any port ssh flags S/SA \
   keep state

Mandataire TCP SYN

Normalement, quand un client ouvre une connexion TCP vers un serveur, PF relaie les paquets d'ouverture (handshake) au fur et à mesure qu'ils arrivent. PF peut agir en tant que mandataire (proxy). Dans ce cas, PF va traiter la demande en lieu et place du serveur et ne transfèrera qu'ensuite les paquets à ce dernier. Aucun paquet n'est transmis au serveur avant que le client n'ait terminé l'échange initial (handshake). L'avantage de cette méthode est de protéger le serveur des attaques par inondation de paquets SYN lancées à l'aide de paquets falsifiés.

Le mandataire TCP SYN est activé à l'aide de l'option synproxy state :

pass in on $ext_if proto tcp from any to $web_server port www \
   flags S/SA synproxy state

Toutes les connexions à destination du serveur HTTP seront mandatées par PF.

L'option synproxy state apporte les mêmes avantages que les options keep state et modulate state.

Par contre, l'option synproxy ne fonctionne pas quand PF est installé en passerelle transparente (bridge(4)).

Bloquer les paquets usurpés

On parle d'usurpation quand un utilisateur mal intentionné maquille son adresse IP dans le but d'anonymiser ou de cacher son identité afin de lancer des attaques sans que leur origine soit détectable. Il peut également essayer et parfois réussir à avoir accès à des services réservés à certaines adresses.

PF permet de se prémunir de ce type d'attaques grâce à l'option antispoof :

antispoof [log] [quick] for interface [af]
log
Journalise les paquets via pflogd(8).
quick
Si un paquet correspond à la règle, celle-ci est appliquée immédiatement.
interface
Désigne l'interface sur laquelle s'applique la protection. Il est possible de passer une liste d'interfaces en paramètre.
af
Spécifie le type d'adresse : inet pour IPv4 ou inet6 pour IPv6.

Exemple:

antispoof for fxp0 inet

Quand les règles sont chargées, toutes les occurrences du mot antispoof sont décodées dans deux filtres. Si l'interface fxp0 dont l'adresse IP est 10.0.0.1 pour un masque de sous- réseau de 255.255.255.0 (soit /24) est protégée, l'option antispoof sera décodée ainsi :

block in on ! fxp0 inet from 10.0.0.0/24 to any
block in inet from 10.0.0.1 to any

Cette règle déclenche deux actions :

REMARQUE : le filtrage activé par l'option antispoof d'une règle s'applique également aux paquets envoyés sur l'adresse de bouclage interne (loopback). Le filtrage est communément désactivé sur ces interfaces, et cela devient primordial lors de l'utilisation de règles antispoof :

set skip on lo0

antispoof for fxp0 inet

L'utilisation de l'option antispoof est réservée aux interfaces qui possèdent une adresse IP. Utiliser antispoof sur une interface sans adresse IP aboutit au filtrage suivant :

block drop in on ! fxp0 inet all
block drop in inet all

Avec ce genre de règles, le risque est réel de bloquer tout le trafic entrant sur toutes les interfaces.

Reconnaissance passive d'OS par leurs empreintes

La reconnaissance passive d'OS par leurs empreintes ("OS Fingerprinting" ou OSFP) est une méthode qui permet de reconnaître à distance quel système d'exploitation tourne sur une machine. Cette reconnaissance se base sur les caractéristiques des paquets TCP SYN renvoyés par une machine. Ces informations peuvent être utilisées comme critères dans des règles de filtrage.

PF utilise le fichier d'empreintes /etc/pf.os pour reconnaître les systèmes d'exploitation auxquels il a affaire. Lorsque PF s'exécute, la liste des empreintes reconnues peut être consultée grâce à la commande suivante :

# pfctl -s osfp

Dans une règle, une empreinte peut être désignée sous la forme d'une classe, d'une version ou d'un sous-type d'OS. La liste de ces éléments est affichée à l'aide de la commande pfctl. Pour désigner une empreinte dans une règle, il faut utiliser le mot-clef os :

pass  in on $ext_if from any os OpenBSD keep state
block in on $ext_if from any os "Windows 2000"
block in on $ext_if from any os "Linux 2.4 ts"
block in on $ext_if from any os unknown

unknown est une classe spéciale désignant les systèmes d'exploitation dont l'empreinte n'est pas connue.

Notez bien que :

Les options IP

PF bloque par défaut tous les paquets qui utilisent les options IP. Cela rend moins aisé le travail des outils de reconnaissance d'empreintes tels que nmap. Si une application utilise ces options (par exemple IGMP ou les diffusions multicast) il est possible d'utiliser l'option allow-opts :
pass in quick on fxp0 all allow-opts

Exemple de règles de filtrage

Vous trouverez ci-dessous un exemple de règles de filtrage pour un pare-feu PF destiné à protéger un petit réseau connecté à Internet. Seules les règles de filtrage sont mentionnées ; queueing, nat, rdr, etc. ont été volontairement laissées de côté.

ext_if  = "fxp0"
int_if  = "dc0"
lan_net = "192.168.0.0/24"

# Déclaration du tableau référençant toutes les adresses IP affectées au
# pare-feu.
table <firewall> const { self }

# Ne pas filtrer sur l'interface de bouclage
set skip on lo0

# Normalisation de tous les paquets entrants.
scrub in all

# Mise en place d'une politique d'interdiction par défaut.
block all

# Activation de la protection contre l'usurpation sur l'interface
# externe.
antispoof quick for $int_if inet

# Les connexions ssh ne sont autorisées qu'en provenance du réseau local
# et de la machine 192.168.0.15. "block return" provoque l'émission d'un
# paquet TCP RST pour mettre fin aux connexions illicites. "quick"
# assure que cette règle n'est pas contredite par les règles "pass".
block return in quick on $int_if proto tcp from ! 192.168.0.15 \
   to $int_if port ssh flags S/SA

# Autoriser le trafic sortant et entrant sur le réseau local.
pass in  on $int_if from $lan_net to any
pass out on $int_if from any to $lan_net

# Autoriser les connexions sortantes tcp, udp et icmp sur l'interface
# externe.
# Activer le suivi des états pour les protocoles udp et icmp.
# Activer l'option modulate state sur les paquets tcp.

pass out on $ext_if proto tcp all modulate state flags S/SA
pass out on $ext_if proto { udp, icmp } all keep state

# Autoriser les connexions ssh sur l'interface externe du moment
# qu'elles ne sont pas destinées au pare-feu lui-même. Journaliser le
# paquet qui initie la session afin de pouvoir déterminer qui s'est
# connecté. Activer un service mandataire SYN.
pass in log on $ext_if proto tcp from any to ! <firewall> \
   port ssh flags S/SA synproxy state

[Section précédente : Tables] [Index] [Section suivante : Traduction des Adresses IP ("NAT")]


[back] www@openbsd.org
$OpenBSD: filter.html,v 1.23 2006/05/14 09:54:41 saad Exp $