PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Самопредоставяне на потребителски акаунти в PostgreSQL чрез непривилегирован анонимен достъп

Бележка от Severalnines:Този блог се публикува посмъртно, тъй като Беренд Тобер почина на 16 юли 2018 г. Почитаме приноса му към общността на PostgreSQL и желаем мир за нашия приятел и гост писател.

В предишната статия представихме основите на задействанията и съхранените функции на PostgreSQL и предоставихме шест примерни случая на употреба, включително валидиране на данни, регистриране на промените, извличане на стойности от вмъкнати данни, скриване на данни с прости изгледи, които могат да се актуализират, поддържане на обобщени данни в отделни таблици и безопасно извикване на код с повишени привилегии. Тази статия надгражда допълнително тази основа и представя техника, използваща тригер и съхранена функция за улесняване на делегирането на предоставянето на идентификационни данни за влизане на роли с ограничени привилегии (т.е. не-суперпотребител). Тази функция може да се използва за намаляване на административното натоварване за високостойностен системен административен персонал. Доведени до крайност, ние демонстрираме анонимно самопредоставяне на идентификационни данни за вход от краен потребител, т.е. позволявайки на бъдещите потребители на базата данни да предоставят идентификационни данни за вход сами чрез внедряване на „динамичен SQL“ вътре в съхранена функция, изпълнявана на ниво на привилегии с подходящ обхват. Въведение

Полезно четене на заден план

Неотдавнашната статия на Sebastian Insausti за това как да защитите вашата база данни PostgreSQL включва някои изключително подходящи съвети, с които трябва да сте запознати, а именно съвети #1 - #5 за контрол на удостоверяване на клиента, конфигурация на сървъра, управление на потребители и роли, управление на супер потребители и Криптиране на данни. Ще използваме части от всеки съвет в тази статия.

Друга скорошна статия на Джошуа Отуел за PostgreSQL Privileges &User Management също има добро третиране на конфигурацията на хоста и потребителските привилегии, което включва малко повече подробности по тези две теми.

Защита на мрежовия трафик

Предложената функция включва позволяване на потребителите да предоставят идентификационни данни за вход в базата данни и докато правят това, те ще посочат новото си име за вход и парола през мрежата. Защитата на тази мрежова комуникация е от съществено значение и може да бъде постигната чрез конфигуриране на PostgreSQL сървъра да поддържа и изисква криптирани връзки. Защитата на транспортния слой е активирана във файла postgresql.conf чрез настройката “ssl”:

ssl = on

Хост-базиран контрол на достъпа

За настоящия случай ще добавим конфигурационен ред за достъп, базиран на хост във файла pg_hba.conf, който позволява анонимно, т.е. доверено влизане в базата данни от някаква подходяща подмрежа за популацията от потенциални потребители на база данни, буквално използвайки потребителското име „анонимен“ и втори конфигурационен ред, изискващ влизане с парола за всяко друго име за вход. Не забравяйте, че конфигурациите на хост извикват първото съвпадение, така че първият ред ще се прилага всеки път, когато е посочено „анонимно“ потребителско име, което позволява доверена (т.е. не се изисква парола) връзка, а след това всеки път, когато е посочено друго потребителско име, ще се изисква парола. Например, ако примерната база данни „sampledb“ трябва да се използва, да речем, само от служители и вътрешно за корпоративни съоръжения, тогава можем да конфигурираме доверен достъп за някаква немаршрутируема вътрешна подмрежа с:

# TYPE  DATABASE USER      ADDRESS        METHOD
hostssl sampledb anonymous 192.168.1.0/24 trust
hostssl sampledb all       192.168.1.0/24 md5

Ако базата данни трябва да бъде общодостъпна за обществеността, тогава можем да конфигурираме достъп до „всякакъв адрес“:

# TYPE  DATABASE USER       ADDRESS  METHOD
hostssl sampledb anonymous  all      trust
hostssl sampledb all        all      md5

Обърнете внимание, че горното е потенциално опасно без допълнителни предпазни мерки, вероятно в дизайна на приложението или в устройство с защитна стена, за да ограничите скоростта на използване на тази функция, защото знаете, че някои скриптове ще автоматизират безкрайното създаване на акаунт само за lulz.

Забележете също, че сме посочили типа на връзката като „hostssl“, което означава, че връзките, направени чрез TCP/IP, са успешни само когато връзката е осъществена със SSL криптиране, за да се защити мрежовият трафик от подслушване.

Заключване на публичната схема

Тъй като позволяваме на евентуално неизвестни (т.е. ненадеждни) лица да имат достъп до базата данни, ще искаме да сме сигурни, че достъпът по подразбиране е ограничен. Една важна мярка е да се отмени привилегията за създаване на публичен обект на схема по подразбиране, за да се смекчи наскоро публикувана PostgreSQL уязвимост, свързана с привилегиите на схемата по подразбиране (вж. Заключване на публичната схема от ваша страна).

Примерна база данни

Ще започнем с празна примерна база данни за илюстративни цели:

create database sampledb;
\connect sampledb

revoke create on schema public from public;
alter default privileges revoke all privileges on tables from public;

Също така създаваме ролята за анонимно влизане, съответстваща на по-ранната настройка на pg_hba.conf.

create role anonymous login
    nosuperuser 
    noinherit 
    nocreatedb 
    nocreaterole 
    Noreplication;

И тогава правим нещо ново, като дефинираме нетрадиционен възглед:

create or replace view person as 
 select 
    null::name as login_name,
    null::name as login_pass;

Този изглед не препраща към таблица и така заявката за избор винаги връща празен ред:

select * from person;
 login_name | login_pass 
------------+-------------
            | 
(1 row)

Едно нещо, което това прави за нас, е в известен смисъл да предоставим документация или намек на крайните потребители какви данни са необходими за създаване на акаунт. Това означава, че чрез запитване към таблицата, въпреки че резултатът е празен ред, резултатът разкрива имената на двата елемента от данни.

Но още по-добре, съществуването на този изглед позволява определяне на необходимите типове данни:

\d person
      View "public.person"
    Column    | Type | Modifiers 
--------------+------+-----------
 login_name   | name | 
 login_pass   | name | 

Ще внедрим функционалността за предоставяне на идентификационни данни със съхранена функция и тригер, така че нека декларираме празен шаблон на функция и свързаното задействане:

create or replace function person_iit()
  returns trigger
  set schema 'public'
  language plpgsql
  security definer
  as '
  begin
  end;
  ';

create trigger person_iit
  instead of insert
  on person
  for each row execute procedure person_iit();

Имайте предвид, че следваме предложената конвенция за именуване от предишната статия, като използваме свързаното име на таблица със суфикс със съкратено съкращение, обозначаващо атрибути на връзката на задействане между таблицата и съхранената функция за тригер INSTEAD OF INSERT (т.е. суфикс „ iit”). Ние също така добавихме към съхранената функция атрибутите SCHEMA и SECURITY DEFINER:първият, защото е добра практика да се зададе пътя за търсене, който се прилага за продължителността на изпълнението на функцията, а вторият за улесняване на създаването на роля, което обикновено е орган на суперпотребител на база данни само, но в този случай ще бъде делегирано на анонимни потребители.

И накрая, добавяме минимално достатъчни разрешения към изгледа, за да потърсим и вмъкнем:

grant select, insert on table person to anonymous;
Изтеглете Бялата книга днес Управление и автоматизация на PostgreSQL с ClusterControl Научете какво трябва да знаете, за да внедрите, наблюдавате, управлявате и мащабирате PostgreSQLD Изтеглете Бялата книга

Нека прегледаме

Преди да приложим кода на съхранената функция, нека прегледаме какво имаме. Първо има примерната база данни, собственост на потребителя на postgres:

\l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges   
-----------+----------+----------+-------------+-------------+-----------------------
 sampledb  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
And there’s the user roles, including the database superuser and the newly-created anonymous login roles:
\du
                                   List of roles
 Role name |                         Attributes                         | Member of 
-----------+------------------------------------------------------------+-----------
 anonymous | No inheritance                                             | {}
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}

И има изгледа, който създадохме, и списък с привилегиите за създаване и четене, предоставени на анонимния потребител от потребителя на postgres:

\d
         List of relations
 Schema |  Name  | Type |  Owner   
--------+--------+------+----------
 public | person | view | postgres
(1 row)


\dp
                                Access privileges
 Schema |  Name  | Type |     Access privileges     | Column privileges | Policies 
--------+--------+------+---------------------------+-------------------+----------
 public | person | view | postgres=arwdDxt/postgres+|                   | 
        |        |      | anonymous=ar/postgres     |                   | 
(1 row)

И накрая, подробностите в таблицата показват имената на колоните и типовете данни, както и свързания тригер:

\d person
      View "public.person"
    Column    | Type | Modifiers 
--------------+------+-----------
 login_name   | name | 
 login_pass   | name | 
Triggers:
    person_iit INSTEAD OF INSERT ON person FOR EACH ROW EXECUTE PROCEDURE person_iit()

Динамичен SQL

Ще използваме динамичен SQL, т.е. конструиране на окончателната форма на DDL оператор по време на изпълнение частично от въведени от потребителя данни, за да попълним тялото на тригерната функция. По-конкретно, ние твърдо кодираме контура на изявлението, за да създадем нова роля за влизане и да попълним специфичните параметри като променливи.

Общата форма на тази команда е

create role name [ [ with ] option [ ... ] ]

където опция може да бъде всяко от шестнадесет специфични свойства. Като цяло настройките по подразбиране са подходящи, но ще бъдем изрични относно няколко ограничаващи опции и ще използваме формуляра

create role name 
  with 
    login 
    inherit 
    nosuperuser 
    nocreatedb 
    nocreaterole 
    password ‘password’;

където ще вмъкнем посоченото от потребителя име и парола на ролята по време на изпълнение.

Динамично конструираните изрази се извикват с командата execute:

execute command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];

който за нашите специфични нужди би изглеждал така

  execute 'create role '
    || new.login_name
    || ' with login inherit nosuperuser nocreatedb nocreaterole password '
    || quote_literal(new.login_pass);

където функцията quote_literal връща аргумента низ, подходящо цитиран за използване като низов литерал, за да се съобрази със синтактичното изискване паролата всъщност да бъде цитирана..

След като изградим командния низ, ние го предоставяме като аргумент на командата за изпълнение pl/pgsql в рамките на функцията за задействане.

Обединяването на всичко това изглежда така:

Функцията
create or replace function person_iit()
  returns trigger
  set schema 'public'
  language plpgsql
  security definer
  as $$
  begin

  -- note this is for demonstration only. it is vulnerable to sql injection.

  execute 'create role '
    || new.login_name
    || ' with login inherit nosuperuser nocreatedb nocreaterole password '
    || quote_literal(new.login_pass);

  return new;
  end;
  $$;

Нека опитаме!

Всичко си е на мястото, така че нека се завъртим! Първо превключваме упълномощаването на сесията към анонимен потребител и след това правим вмъкване срещу изглед на човек:

set session authorization anonymous;
insert into person values ('alice', '1234');

Резултатът е, че нов потребител alice е добавен към системната таблица:

\du
                                   List of roles
 Role name |                         Attributes                         | Member of 
-----------+------------------------------------------------------------+-----------
 alice     |                                                            | {}
 anonymous | No inheritance                                             | {}
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}

Той дори работи директно от командния ред на операционната система, като подава SQL команден низ към клиентската помощна програма psql, за да добави потребител bob:

$ psql sampledb anonymous <<< "insert into person values ('bob', '4321');"
INSERT 0 1

$ psql sampledb anonymous <<< "\du"
                                   List of roles
 Role name |                         Attributes                         | Member of 
-----------+------------------------------------------------------------+-----------
 alice     |                                                            | {}
 anonymous | No inheritance                                             | {}
 bob       |                                                            | {}
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}

Приложете малко броня

Първоначалният пример за функцията за задействане е уязвим към атака с инжектиране на SQL, т.е. злонамерен заплаха може да създаде вход, който води до неоторизиран достъп. Например, докато е свързан като ролята на анонимен потребител, опитът да се направи нещо извън обхвата се проваля по подходящ начин:

set session authorization anonymous;
drop user alice;
ERROR:  permission denied to drop role

Но следното злонамерено въвеждане създава роля на суперпотребител, наречена „eve“ (както и акаунт за примамка, наречен „cathy“):

insert into person 
  values ('eve with superuser login password ''666''; create role cathy', '777');
\du
                                   List of roles
 Role name |                         Attributes                         | Member of 
-----------+------------------------------------------------------------+-----------
 alice     |                                                            | {}
 anonymous | No inheritance                                             | {}
 cathy     |                                                            | {}
 eve       | Superuser                                                  | {}
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}

Тогава тайната роля на суперпотребител може да се използва за нанасяне на хаос в базата данни, например изтриване на потребителски акаунти (или по-лошо!):

\c - eve
drop user alice;
\du
                                   List of roles
 Role name |                         Attributes                         | Member of 
-----------+------------------------------------------------------------+-----------
 anonymous | No inheritance                                             | {}
 cathy     |                                                            | {}
 eve       | Superuser                                                  | {}
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}

За да смекчим тази уязвимост, трябва да предприемем стъпки за саниране на входа. Например, прилагане на функцията quote_ident, която връща низ, подходящ за използване като идентификатор в SQL израз с добавени кавички, когато е необходимо, като например ако низът съдържа неидентификаторски знаци или ще бъде сгънат в главни и главни букви и правилно вградено удвояване цитати:

create or replace function person_iit()
  returns trigger
  set schema 'public'
  language plpgsql
  security definer
  as $$
  begin

  execute 'create role '
    || quote_ident(new.login_name)
    || ' with login inherit nosuperuser nocreatedb nocreaterole password '
    || quote_literal(new.login_pass);

  return new;
  end;
  $$;

Сега, ако същата инжекция на SQL се опита да създаде друг суперпотребител на име „frank“, той се проваля и резултатът е много неортодоксално потребителско име:

set session authorization anonymous;
insert into person 
  values ('frank with superuser login password ''666''; create role dave', '777');
\du
                                 List of roles
    Role name          |                         Attributes                         | Member of 
-----------------------+------------------------------------------------------------+----------
 anonymous             | No inheritance                                             | {}
 eve                   | Superuser                                                  | {}
 frank with superuser  |                                                            |
  login password '666';|                                                            |
  create role dave     |                                                            |
 postgres              | Superuser, Create role, Create DB, Replication, Bypass RLS | {}

Можем да приложим допълнително разумно валидиране на данни в рамките на функцията за задействане, като например изискване само на буквено-цифрови потребителски имена и отхвърляне на празно пространство и други знаци:

create or replace function person_iit()
  returns trigger
  set schema 'public'
  language plpgsql
  security definer
  as $$
  begin

  -- Basic input sanitization

  if new.login_name is null then
    raise exception 'null login_name disallowed';
  elsif position(' ' in new.login_name) > 0 then
    raise exception 'login_name whitespace disallowed';
  elsif length(new.login_name) = 0 then
    raise exception 'login_name must be non-empty';
  elsif not (select new.login_name similar to '[A-Za-z]%') then
    raise exception 'login_name must begin with a letter.';
  end if;

  if new.login_pass is null then
    raise exception 'null login_pass disallowed';
  elsif position(' ' in new.login_pass) > 0 then
    raise exception 'login_pass whitespace disallowed';
  elsif length(new.login_pass) = 0 then
    raise exception 'login_pass must be non-empty';
  end if;

  -- Provision login credentials

  execute 'create role '
    || quote_ident(new.login_name)
    || ' with login inherit nosuperuser nocreatedb nocreaterole password '
    || quote_literal(new.login_pass);

  return new;
  end;
  $$;

и след това потвърдете, че различните проверки за дезинфекция работят:

set session authorization anonymous;
insert into person values (NULL, NULL);
ERROR:  null login_name disallowed
insert into person values ('gina', NULL);
ERROR:  null login_pass disallowed
insert into person values ('gina', '');
ERROR:  login_pass must be non-empty
insert into person values ('', '1234');
ERROR:  login_name must be non-empty
insert into person values ('gi na', '1234');
ERROR:  login_name whitespace disallowed
insert into person values ('1gina', '1234');
ERROR:  login_name must begin with a letter.

Нека да повишим напредък

Да предположим, че искаме да съхраняваме допълнителни метаданни или данни от приложението, свързани със създадената потребителска роля, например, може би времеви печат и IP адрес на източника, свързан със създаването на роля. Разбира се, изгледът не може да удовлетвори това ново изискване, тъй като няма основно хранилище, така че е необходима действителна таблица. Освен това, нека приемем, че искаме да ограничим видимостта на тази таблица от потребителите, които влизат с ролята за анонимно влизане. Можем да скрием таблицата в отделно пространство от имена (т.е. PostgreSQL схема), което остава недостъпно за анонимни потребители. Нека наречем това пространство от имена „частно“ пространство от имена и да създадем таблицата в пространството от имена:

create schema private;

create table private.person (
  login_name   name not null primary key,
  inet_client_addr inet default inet_client_addr(),
  create_time timestamptz default now()  
);

Една проста допълнителна команда за вмъкване вътре в тригерната функция записва тези свързани метаданни:

create or replace function person_iit()
  returns trigger
  set schema 'public'
  language plpgsql
  security definer
  as $$
  begin

  -- Basic input sanitization
  if new.login_name is null then
    raise exception 'null login_name disallowed';
  elsif position(' ' in new.login_name) > 0 then
    raise exception 'login_name whitespace disallowed';
  elsif length(new.login_name) = 0 then
    raise exception 'login_name must be non-empty';
  elsif not (select new.login_name similar to '[A-Za-z]%') then
    raise exception 'login_name must begin with a letter.';
  end if;

  if new.login_pass is null then
    raise exception 'null login_pass disallowed';
  elsif length(new.login_pass) = 0 then
    raise exception 'login_pass must be non-empty';
  end if;

  -- Record associated metadata
  insert into private.person values (new.login_name);

  -- Provision login credentials

  execute 'create role '
    || quote_ident(new.login_name)
    || ' with login inherit nosuperuser nocreatedb nocreaterole password '
    || quote_literal(new.login_pass);

  return new;
  end;
  $$;

И можем да го направим лесен тест. Първо потвърждаваме, че докато сте свързани като анонимна роля, се вижда само изгледът public.person, а не таблицата private.person:

set session authorization anonymous;

\d
         List of relations
 Schema |  Name  | Type |  Owner   
--------+--------+------+----------
 public | person | view | postgres
(1 row)
                   
select * from private.person;
ERROR:  permission denied for schema private

И след това след нова роля вмъкнете:

insert into person values ('gina', '1234');

reset session authorization;

select * from private.person;
 login_name | inet_client_addr |          create_time          
------------+------------------+-------------------------------
 gina       | 192.168.2.106    | 2018-06-24 07:56:13.838679-07
(1 row)

таблицата private.person показва заснемането на метаданни за IP адрес и времето за вмъкване на ред.

Заключение

В тази статия ние демонстрирахме техника за делегиране на предоставяне на идентификационни данни за ролята на PostgreSQL на роли, които не са суперпотребител. Докато примерът делегира напълно функционалността за идентификация на анонимни потребители, подобен подход може да се използва за частично делегиране на функционалността само на доверен персонал, като същевременно се запазва ползата от разтоварването на тази работа от високостойностен персонал на база данни или системен администратор. Ние също така демонстрирахме техника за многослоен достъп до данни, използвайки PostgreSQL схеми, избирателно излагане или скриване на обекти от базата данни. В следващата статия от тази серия ще разширим техниката за многослоен достъп до данни, за да предложим нов дизайн на архитектура на база данни за внедряване на приложения.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Не може да се свърже с Postgres чрез PHP, но може да се свърже от командния ред и PgAdmin на друга машина

  2. Не може да се вмъкне:ГРЕШКА:стойността на масива трябва да започва с { или информация за измерения

  3. свържете се със сървъра на postgres на google compute engine

  4. Как Extract() работи в PostgreSQL

  5. org.postgresql.util.PSQLException:Големите обекти не може да се използват в режим на автоматично записване