-
-
Notifications
You must be signed in to change notification settings - Fork 257
fix: make pg_net background worker run as postgres #2261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+158
−1
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
6558016
fix: make pg_net background worker run as postgres
imor ae43f7d
chore: suffix to test
samrose d014284
tests: add pg_net privesc test
imor 0248c2e
chore: bump versions for tests
imor c60a461
Merge branch 'develop' into fix/configure-pg-net-username
imor 3a374f9
chore: update version to generate a test AMI
imor e303cdf
Merge branch 'develop' into fix/configure-pg-net-username
imor f1c6cd5
chore: revert versions
imor c1a641f
fix: use postgres role in test
imor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| pg_net.username = 'postgres' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| -- Regression test for a privilege-escalation vulnerability in pg_net's background | ||
| -- worker. See ansible/files/postgresql_config/conf.d/pg_net.conf, which sets | ||
| -- pg_net.username = 'postgres' to fix it. | ||
| -- | ||
| -- pg_net.username controls which role the background worker uses to drain | ||
| -- net.http_request_queue and write to net._http_response. When unset, the worker | ||
| -- connects as the bootstrap superuser -- supabase_admin on this platform, a true | ||
| -- superuser. | ||
| -- | ||
| -- pg_net's own install script grants ALL privileges (which includes TRIGGER) on | ||
| -- both of those tables to PUBLIC: | ||
| -- grant all on all tables in schema net to PUBLIC; | ||
| -- so literally any role -- including a `postgres` app user -- can attach | ||
| -- a trigger to them. Since a normal (non SECURITY DEFINER) trigger runs with the | ||
| -- privileges of whoever performs the triggering statement, a `postgres` user | ||
| -- could make such a trigger run arbitrary SQL with the pg_net worker's privileges | ||
| -- the next time it processed a queued request. | ||
| -- | ||
| -- This test plants such a trigger as `postgres` and has it record which role | ||
| -- and privilege level it actually ran with, confirming the worker's DML now runs | ||
| -- as the unprivileged `postgres` role instead of the superuser `supabase_admin`. | ||
| create table public.pg_net_privesc_log ( | ||
| recorded_role name, | ||
| recorded_is_superuser bool | ||
| ); | ||
| grant insert on public.pg_net_privesc_log to public; | ||
| create function public.pg_net_privesc_trigger() returns trigger | ||
| language plpgsql | ||
| security invoker | ||
| as $$ | ||
| begin | ||
| -- records the privileges of whoever actually performed the triggering | ||
| -- statement -- i.e. the pg_net worker, not whoever created this trigger | ||
| insert into public.pg_net_privesc_log (recorded_role, recorded_is_superuser) | ||
| values (current_user, (select rolsuper from pg_roles where rolname = current_user)); | ||
| return null; | ||
| end; | ||
| $$; | ||
| grant execute on function public.pg_net_privesc_trigger() to public; | ||
| set session authorization postgres; | ||
| create trigger pg_net_privesc | ||
| after insert on net._http_response | ||
| for each row execute function public.pg_net_privesc_trigger(); | ||
| select net.http_get( | ||
| 'http://localhost:' || (select value from test_config where key = 'http_mock_port') || '/get' | ||
| ) is not null as request_queued; | ||
| request_queued | ||
| ---------------- | ||
| t | ||
| (1 row) | ||
|
|
||
| reset session authorization; | ||
| -- give the background worker time to drain the queue and process the response | ||
| do $$ | ||
| declare | ||
| i int := 0; | ||
| begin | ||
| while (select count(*) from public.pg_net_privesc_log) = 0 and i < 40 loop | ||
| perform pg_sleep(0.25); | ||
| i := i + 1; | ||
| end loop; | ||
| end; | ||
| $$; | ||
| -- the pg_net worker must have run the trigger as the unprivileged `postgres` | ||
| -- role, not the superuser `supabase_admin` | ||
| select recorded_role, recorded_is_superuser from public.pg_net_privesc_log; | ||
| recorded_role | recorded_is_superuser | ||
| ---------------+----------------------- | ||
| postgres | f | ||
| (1 row) | ||
|
|
||
| -- cleanup | ||
| drop trigger if exists pg_net_privesc on net._http_response; | ||
| drop function if exists public.pg_net_privesc_trigger(); | ||
| drop table public.pg_net_privesc_log; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| -- Regression test for a privilege-escalation vulnerability in pg_net's background | ||
| -- worker. See ansible/files/postgresql_config/conf.d/pg_net.conf, which sets | ||
| -- pg_net.username = 'postgres' to fix it. | ||
| -- | ||
| -- pg_net.username controls which role the background worker uses to drain | ||
| -- net.http_request_queue and write to net._http_response. When unset, the worker | ||
| -- connects as the bootstrap superuser -- supabase_admin on this platform, a true | ||
| -- superuser. | ||
| -- | ||
| -- pg_net's own install script grants ALL privileges (which includes TRIGGER) on | ||
| -- both of those tables to PUBLIC: | ||
| -- grant all on all tables in schema net to PUBLIC; | ||
| -- so literally any role -- including a `postgres` app user -- can attach | ||
| -- a trigger to them. Since a normal (non SECURITY DEFINER) trigger runs with the | ||
| -- privileges of whoever performs the triggering statement, a `postgres` user | ||
| -- could make such a trigger run arbitrary SQL with the pg_net worker's privileges | ||
| -- the next time it processed a queued request. | ||
| -- | ||
| -- This test plants such a trigger as `postgres` and has it record which role | ||
| -- and privilege level it actually ran with, confirming the worker's DML now runs | ||
| -- as the unprivileged `postgres` role instead of the superuser `supabase_admin`. | ||
|
|
||
| create table public.pg_net_privesc_log ( | ||
| recorded_role name, | ||
| recorded_is_superuser bool | ||
| ); | ||
|
|
||
| grant insert on public.pg_net_privesc_log to public; | ||
|
|
||
| create function public.pg_net_privesc_trigger() returns trigger | ||
| language plpgsql | ||
| security invoker | ||
| as $$ | ||
| begin | ||
| -- records the privileges of whoever actually performed the triggering | ||
| -- statement -- i.e. the pg_net worker, not whoever created this trigger | ||
| insert into public.pg_net_privesc_log (recorded_role, recorded_is_superuser) | ||
| values (current_user, (select rolsuper from pg_roles where rolname = current_user)); | ||
| return null; | ||
| end; | ||
| $$; | ||
|
|
||
| grant execute on function public.pg_net_privesc_trigger() to public; | ||
|
|
||
| set session authorization postgres; | ||
|
|
||
| create trigger pg_net_privesc | ||
| after insert on net._http_response | ||
| for each row execute function public.pg_net_privesc_trigger(); | ||
|
|
||
| select net.http_get( | ||
| 'http://localhost:' || (select value from test_config where key = 'http_mock_port') || '/get' | ||
| ) is not null as request_queued; | ||
|
|
||
| reset session authorization; | ||
|
|
||
| -- give the background worker time to drain the queue and process the response | ||
| do $$ | ||
| declare | ||
| i int := 0; | ||
| begin | ||
| while (select count(*) from public.pg_net_privesc_log) = 0 and i < 40 loop | ||
| perform pg_sleep(0.25); | ||
| i := i + 1; | ||
| end loop; | ||
| end; | ||
| $$; | ||
|
|
||
| -- the pg_net worker must have run the trigger as the unprivileged `postgres` | ||
| -- role, not the superuser `supabase_admin` | ||
| select recorded_role, recorded_is_superuser from public.pg_net_privesc_log; | ||
|
|
||
| -- cleanup | ||
| drop trigger if exists pg_net_privesc on net._http_response; | ||
| drop function if exists public.pg_net_privesc_trigger(); | ||
| drop table public.pg_net_privesc_log; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.