Debido a la reescritura de consultas por parte del sistema de reglas de PostgreSQL, se accede a otras tablas/vistas distintas de las utilizadas en la consulta original. Cuando se utilizan reglas de actualización, esto puede incluir el acceso de escritura a las tablas.
Las reglas de reescritura no tienen un propietario independiente. El propietario de una relación (tabla o vista) es automáticamente el propietario de las reglas de reescritura que se definen para ella. El sistema de reglas de PostgreSQL cambia el comportamiento del sistema de control de acceso predeterminado. Con la excepción de las reglas SELECT asociadas con vistas del tipo security invoker (consulta CREATE VIEW), todas las relaciones que se utilizan debido a las reglas se comprueban contra los privilegios del propietario de la regla, no del usuario que la invoca. Esto significa que, a excepción de las vistas security invoker, los usuarios solo necesitan los privilegios requeridos para las tablas/vistas que se nombran explícitamente en sus consultas.
Por ejemplo: un usuario tiene una lista de números de teléfono donde algunos de ellos son privados, y los otros son de interés para el asistente de la oficina. El usuario puede construir lo siguiente:
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;
Nadie excepto ese usuario (y los superusuarios de la base de datos) puede acceder a la tabla phone_data. Pero debido al GRANT, el asistente puede ejecutar un SELECT en la vista phone_number. El sistema de reglas reescribirá el SELECT de phone_number en un SELECT de phone_data. Dado que el usuario es el propietario de phone_number y, por lo tanto, el propietario de la regla, el acceso de lectura a phone_data se comprueba ahora contra los privilegios del usuario, y la consulta se permite. La comprobación para acceder a phone_number también se realiza, pero se hace contra el usuario que la invoca, por lo que nadie más que el usuario y el asistente pueden utilizarla.
Los privilegios se comprueban regla por regla. Así que el asistente es por ahora el único que puede ver los números de teléfono públicos. Pero el asistente puede configurar otra vista y conceder acceso a ella al público. Entonces, cualquiera puede ver los datos de phone_number a través de la vista del asistente. Lo que el asistente no puede hacer es crear una vista que acceda directamente a phone_data. (En realidad el asistente sí puede, pero no funcionará ya que cada acceso será denegado durante las comprobaciones de permisos). Y tan pronto como el usuario note que el asistente abrió su vista phone_number, el usuario puede revocar el acceso del asistente. Inmediatamente, cualquier acceso a la vista del asistente fallará.
Se podría pensar que esta comprobación regla por regla es una brecha de seguridad, pero en realidad no lo es. Porque si no funcionara de esta manera, el asistente podría configurar una tabla con las mismas columnas que phone_number y copiar los datos allí una vez al día. Entonces serían los propios datos del asistente y este podría conceder acceso a quien quisiera. Un comando GRANT significa: «confío en ti». Si alguien en quien confías hace lo anterior, es hora de pensarlo bien y luego usar REVOKE.
Ten en cuenta que aunque las vistas se pueden utilizar para ocultar el contenido de ciertas columnas mediante la técnica mostrada anteriormente, no se pueden utilizar para ocultar de forma fiable los datos en las filas no vistas a menos que se haya establecido la opción security_barrier. Por ejemplo, la siguiente vista no es segura:
CREATE VIEW phone_number AS
SELECT person, phone FROM phone_data WHERE phone NOT LIKE '412%';
Esta vista podría parecer segura, ya que el sistema de reglas reescribirá cualquier SELECT de phone_number en un SELECT de phone_data y añadirá la condición de que solo se quieren las entradas donde phone no comience con 412. Pero si el usuario puede crear sus propias funciones, no es difícil convencer al planificador para que ejecute la función definida por el usuario antes de la expresión NOT LIKE. Por ejemplo:
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);
Cada persona y número de teléfono en la tabla phone_data se imprimirá como un aviso (NOTICE), porque el planificador elegirá ejecutar la función económica tricky antes de la función más costosa NOT LIKE. Incluso si se impide al usuario definir nuevas funciones, se pueden utilizar funciones integradas en ataques similares. (Por ejemplo, la mayoría de las funciones de conversión de tipos incluyen sus valores de entrada en los mensajes de error que producen).
Se aplican consideraciones similares a las reglas de actualización. En los ejemplos de la sección anterior, el propietario de las tablas en la base de datos de ejemplo podría conceder los privilegios SELECT, INSERT, UPDATE y DELETE sobre la vista shoelace a otra persona, pero solo SELECT sobre shoelace_log. La acción de la regla para escribir entradas de registro se seguirá ejecutando con éxito, y ese otro usuario podría ver las entradas de registro. Pero no podría crear entradas falsas, ni manipular o eliminar las existentes. En este caso, no hay posibilidad de subvertir las reglas convenciendo al planificador para que altere el orden de las operaciones, porque la única regla que hace referencia a shoelace_log es un INSERT sin condiciones. Esto podría no ser cierto en escenarios más complejos.
Cuando es necesario que una vista proporcione seguridad a nivel de fila, se debe aplicar el atributo security_barrier a la vista. Esto evita que funciones y operadores elegidos maliciosamente reciban valores de las filas hasta después de que la vista haya realizado su trabajo. Por ejemplo, si la vista mostrada anteriormente se hubiera creado así, sería segura:
CREATE VIEW phone_number WITH (security_barrier) AS
SELECT person, phone FROM phone_data WHERE phone NOT LIKE '412%';
Las vistas creadas con security_barrier pueden tener un rendimiento muy inferior al de las vistas creadas sin esta opción. En general, no hay forma de evitar esto: el plan más rápido posible debe ser rechazado si puede comprometer la seguridad. Por esta razón, esta opción no está habilitada de forma predeterminada.
El planificador de consultas tiene más flexibilidad cuando trata con funciones que no tienen efectos secundarios. Dichas funciones se denominan LEAKPROOF (a prueba de filtraciones) e incluyen muchos operadores simples y de uso común, como muchos operadores de igualdad. El planificador de consultas puede permitir de forma segura que tales funciones se evalúen en cualquier punto del proceso de ejecución de la consulta, ya que invocarlas en filas invisibles para el usuario no filtrará ninguna información sobre las filas no vistas. Además, las funciones que no toman argumentos o a las que no se les pasa ningún argumento desde la vista con barrera de seguridad no tienen que marcarse como LEAKPROOF para integrarse, ya que nunca reciben datos de la vista. Por el contrario, una función que podría lanzar un error dependiendo de los valores recibidos como argumentos (como una que lanza un error en caso de desbordamiento o división por cero) no es a prueba de filtraciones, y podría proporcionar información significativa sobre las filas no vistas si se aplica antes de los filtros de fila de la vista de seguridad.
Por ejemplo, no se puede seleccionar un escaneo de índice para consultas sobre vistas con barrera de seguridad (o tablas con políticas de seguridad a nivel de fila) si un operador utilizado en la cláusula WHERE está asociado con la familia de operadores del índice, pero su función subyacente no está marcada como LEAKPROOF. El metacomando \dAo+ del programa psql es útil para listar las familias de operadores y determinar cuáles de sus operadores están marcados como a prueba de filtraciones (leakproof).
Es importante entender que incluso una vista creada con la opción security_barrier está pensada para ser segura solo en el sentido limitado de que el contenido de las tuplas invisibles no se pasará a funciones posiblemente inseguras. El usuario puede tener otros medios para inferir información sobre los datos no vistos; por ejemplo, puede ver el plan de consulta utilizando EXPLAIN, o medir el tiempo de ejecución de las consultas contra la vista. Un atacante malintencionado podría inferir algo sobre la cantidad de datos no vistos, o incluso obtener alguna información sobre la distribución de los datos o los valores más comunes (ya que estas cosas pueden afectar el tiempo de ejecución del plan; o incluso, dado que también se reflejan en las estadísticas del optimizador, la elección del plan). Si este tipo de ataques de "canal encubierto" son una preocupación, probablemente no sea prudente conceder ningún acceso a los datos en absoluto.