À cause de la réécriture des requêtes par le système de règles de PostgreSQL, d'autres tables/vues que celles utilisées dans la requête originale pourraient être accédées. Lorsque des règles de mise à jour sont utilisées, ceci peut inclure des droits d'écriture sur les tables.
Les règles de réécriture n'ont pas de propriétaire séparé. Le propriétaire d'une relation (table ou vue) est automatiquement le propriétaire des règles de réécriture qui lui sont définies. Le système de règles de PostgreSQL modifie le comportement du système de contrôle d'accès par défaut. Les relations qui sont utilisées à cause des règles se voient vérifier avec les droits du propriétaire de la règle, et non avec ceux de l'utilisateur appelant cette règle. Ceci signifie qu'un utilisateur a seulement besoin des droits requis pour les tables/vues qu'il nomme explicitement dans ses requêtes.
Par exemple : un utilisateur a une liste de numéros de téléphone dont certains sont privés, les autres étant d'intérêt pour l'assistant du bureau. Il peut construire de cette façon :
CREATE TABLE phone_data (person text, phone text, private boolean); CREATE VIEW phone_number AS SELECT person, CASE WHEN NOT private THEN phone END AS phone FROM phone_data; GRANT SELECT ON phone_number TO assistant;
Personne en dehors de cet utilisateur (et les super-utilisateurs de la base de
données) ne peut
accéder à la table phone_data
. mais, à cause du
grant
, l'assistant peut lancer un select
sur la vue phone_number
. le système de règles réécrira le
select
sur phone_number
en un
select
sur phone_data
. Comme l'utilisateur est le
propriétaire de phone_number
et du coup le propriétaire de la
règle, le droit de lecture de phone_data
est maintenant vérifié
avec ses propres privilèges et la requête est autorisée. La vérification de
l'accès à phone_number
est aussi réalisée mais ceci est fait
avec l'utilisateur appelant, donc personne sauf l'utilisateur et l'assistant
ne peut l'utiliser.
Les droits sont vérifiés règle par règle. Donc, l'assistant est
actuellement le seul à pouvoir voir les numéros de téléphone publiques.
Mais l'assistant peut configurer une autre vue et autoriser l'accès au
public. Du coup, tout le monde peut voir les données de
phone_number
via la vue de l'assistant. Ce que l'assistant
ne peut pas faire est de créer une vue qui accède directement à
phone_data
(en fait, il le peut mais cela ne fonctionnera pas car
tous les accès seront refusés lors de la vérification des droits). Dès que
l'utilisateur s'en rendra compte, du fait que l'assistant a ouvert la vue
phone_number
à tout le monde, il peut révoquer son accès.
Immédiatement, tous les accès de la vue de l'assistant échoueront.
Il pourrait être dit que cette vérification règle par règle est une brèche
de sécurité mais ce n'est pas le cas. Si cela ne fonctionne pas de cette
façon, l'assistant pourrait copier une table avec les mêmes colonnes que
phone_number
et y copier les données une fois par jour. Du coup,
ce sont ces propres données et il peut accorder l'accès à tout le monde si
il le souhaite. Une commande grant
signifie « j'ai
confiance en vous ». Si quelqu'un en qui vous avez confiance se
comporte ainsi, il est temps d'y réfléchir et d'utiliser
revoke
.
Notez que, bien que les vues puissent être utilisées pour cacher le
contenu de certaines colonnes en utilisant la technique montrée ci-dessus,
elles ne peuvent pas être utilisées de manière fiable pour cacher des
données dans des lignes invisibles sauf si le drapeau
security_barrier
a été initialisé. Par exemple, la vue
suivante n'est pas sécurisée :
CREATE VIEW phone_number AS SELECT person, phone FROM phone_data WHERE phone NOT LIKE '412%';
Cette vue peut sembler sécurisée car le système de règles va réécrire tout
SELECT
à partir de phone_number
dans
un SELECT
à partir de phone_data
et
ajouter la qualification permettant de filter les enregistrements dont la
colonne phone
ne commence pas par 412. Mais si
l'utilisateur peut créer ses propres fonctions, il n'est pas difficile de
convaincre le planificateur d'exécuter la fonction définie par
l'utilisateur avant l'expression NOT LIKE
.
CREATE FUNCTION tricky(text, text) RETURNS bool AS $$ BEGIN RAISE NOTICE '% => %', $1, $2; RETURN true; END $$ LANGUAGE plpgsql COST 0.0000000000000000000001; SELECT * FROM phone_number WHERE tricky(person, phone);
Chaque personne et chaque numéro de téléphone de la table
phone_data
sera affiché dans un
NOTICE
car le planificateur choisira d'exécuter la
procédure tricky
avant le NOT
LIKE
car elle est moins coûteuse. Même si l'utilisateur ne peut
pas définir des nouvelles fonctions, les fonctions internes peuvent être
utilisées pour des attaques similaires. (Par exemple, la plupart des fonctions de
conversions affichent les valeurs en entrée dans le message d'erreur qu'elles
fournissent.)
Des considérations similaires s'appliquent aussi aux règles de mise à jour. Dans les
exemples de la section précédente, le propriétaire des tables de la base de
données d'exemple pourrait accorder les droits select
,
insert
, update
et delete
sur la vue
lacet
à quelqu'un d'autre mais seulement select
sur lacet_log
. l'action de la règle pourrait écrire des
entrées de trace qui seraient toujours exécutées avec succès et que l'autre
utilisateur pourrait voir. Mais il ne peut pas créer d'entrées fausses, pas
plus qu'il ne peut manipuler ou supprimer celles qui existent. Dans ce cas,
il n'existe pas de possibilité de subvertir les règles en convaincant le
planificateur de modifier l'ordre des opérations car la seule règle qui fait
référence à shoelace_log
est un INSERT
non qualifié. Ceci pourrait ne plus être vrai dans les scénarios complexes.
Lorsqu'il est nécessaire qu'une vue fournisse une sécurité au niveau des
lignes, l'attribut security_barrier
doit être appliqué
à la vue. Ceci empêche des fonctions et des opérateurs choisis spécialement
de voir des valeurs de lignes jusqu'à ce que la vue ait fait son travail.
Par exemple, si la vue montrée ci-dessus a été créée ainsi, elle serait
sécurisée :
CREATE VIEW phone_number WITH (security_barrier) AS SELECT person, phone FROM phone_data WHERE phone NOT LIKE '412%';
Les vues créées avec l'attribut security_barrier
peuvent
avoir de bien pires performances que les vues créées sans cette option.
En général, il n'y a pas de moyen de l'éviter : le plan le plus rapide
doit être éviter si cela compromet la sécurité. Pour cette raison, cette
option n'est pas activée par défaut.
Le planificateur de requêtes a plus de flexibilité lorsqu'il s'occupe de
fonctions qui n'ont pas d'effets de bord. Ces fonctions sont qualifiées
de LEAKPROOF
et incluent de nombreux opérateurs simples fréquemment
utilisés, comme les opérateurs d'égalité. Le planificateur de requêtes
peut en tout sécurité permettre à de telles fonctions d'être évaluées
à tout moment dans l'exécution de la requête car les appeler sur des lignes
invisibles à l'utilisateur ne pourra pas faire transpirer ces informations
sur les lignes invisibles. De plus, les fonctions qui ne prennent pas
d'arguments ou à qui aucun argument n'est passé à partir de la vue
disposant de l'option security_barrier n'ont pas besoin d'être marquées
LEAKPROOF
pour être exécutées avant car elles ne
reçoivent jamais des données de la vue. En contraste complet, une fonction
qui peut envoyer des erreurs dépendant des valeurs reçues en argument
(comme les fonctions qui renvoient une erreur dans le cas d'un dépassement
de capacité ou de division par zéro) ne sont pas
LEAKPROOF
, et risquent de fournir des informations sur
les lignes invisibles si elles sont exécutées avant que la vue ne les
filtre.
Il est important de comprendre que, même une vue créée avec l'option
security_barrier
est supposée être sécurisée dans le
sens où le contenu de lignes invisibles ne sera pas passé à des fonctions
supposées non sécurisées. L'utilisateur pourrait bien avoir d'autres moyens
pour accéder aux données non vues ; par exemple, ils peuvent voir le
plan d'exécution en utilisant EXPLAIN
ou mesurer la durée
d'exécution de requêtes sur la vue. Un attaquant pourrait être capable
de deviner certaines informations comme la quantité de données invisibles,
voire obtenir des informations sur la distribution des données ou les valeurs
les plus communes (ces informations affectent la durée d'exécution de la
requête ; ou même, comme elles font partie des statistiques de
l'optimiseur, du choix du plan). Si ces types d'attaques vous posent
problème, il est alors déconseillé de donner l'accès aux données.