← THE INDEX  ·  WRITEUP

Privilege Boundary Violation via Subscription Ownership Escalation

The aiven_extras SECURITY DEFINER subscription creation function assigns ownership to the postgres superuser rather than the calling user, causing apply worker triggers to run with session_user=postgres.

Summary

aiven_extras.pg_create_subscription() is a SECURITY DEFINER function owned by postgres. When it executes CREATE SUBSCRIPTION, the DDL runs as postgres, and PostgreSQL assigns the subscription owner as the effective user at creation time. The subscription is therefore owned by postgres (oid 10), not by the calling avnadmin user. PostgreSQL's logical replication apply worker runs with session_user equal to the subscription owner, so any trigger that fires on an apply worker row operates with session_user=postgres. This breaks the intended isolation between customer code and the platform superuser context on all Aiven PostgreSQL plans, including the free tier.

Impact

Customer trigger code that runs in the apply worker context passes PostgreSQL's superuser permission checks for GRANT, COPY TO FILE, and CREATE EXTENSION before being blocked only by the secondary SECURITY_RESTRICTED_OPERATION enforcement. The error messages are distinct: a normal avnadmin session receives permission denied to grant role, but the apply worker receives ROLE modification to SUPERUSER/privileged role not allowed in SECURITY_RESTRICTED_OPERATION. The second message confirms that the permission check itself passed and only the secondary restriction stopped the operation.

This reduces the effective defense to a single mechanism: SECURITY_RESTRICTED_OPERATION. A bypass of that flag, whether via a future PostgreSQL CVE, an extension vulnerability, or a logic error in aiven_extras, would immediately grant unrestricted superuser code execution. The issue also enables the SECDEF dblink chain (see companion report), because the apply worker context can call the same SECDEF subscription management functions.

Root cause

When aiven_extras.pg_create_subscription() runs EXECUTE format('CREATE SUBSCRIPTION ...'), PostgreSQL sets the subscription owner to current_user at the time of the CREATE statement. Inside a SECURITY DEFINER function owned by postgres, current_user is postgres, so the subscription is recorded in pg_subscription.subowner as oid 10. The calling avnadmin user's identity is never used.

-- Inside the SECURITY DEFINER function:
EXECUTE pg_catalog.format(
    'CREATE SUBSCRIPTION %I CONNECTION %L PUBLICATION %I ...',
    arg_subscription_name, ...);
-- subowner is set to postgres (oid 10), not to avnadmin

The fix is one line: add ALTER SUBSCRIPTION %I OWNER TO %I after the CREATE, using session_user (the original caller's identity) as the new owner.

Proof of concept

The following demonstrates that the subscription is owned by postgres and that triggers in the apply worker context see session_user=postgres. All credentials and instance identifiers have been replaced with placeholders.

Disclosure and fix

Reported to Aiven through their bug bounty program. Aiven triaged this as P3 (Medium). The recommended fix is to add one statement to pg_create_subscription() after the CREATE SUBSCRIPTION call:

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

This reassigns ownership to the calling user, causing the apply worker to run with session_user=avnadmin rather than session_user=postgres. Alternatively, the function can use SET ROLE before the CREATE SUBSCRIPTION so the DDL runs as the calling user from the start.

-- 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;