PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Penyediaan Sendiri Akun Pengguna di PostgreSQL melalui Akses Anonim Tanpa Hak

Catatan dari Somenines:Blog ini diterbitkan secara anumerta saat Berend Tober meninggal pada 16 Juli 2018. Kami menghormati kontribusinya kepada komunitas PostgreSQL dan mendoakan kedamaian bagi teman dan penulis tamu kami.

Pada artikel sebelumnya kami memperkenalkan dasar-dasar pemicu PostgreSQL dan fungsi tersimpan dan memberikan enam contoh kasus penggunaan termasuk validasi data, perubahan pencatatan, penurunan nilai dari data yang dimasukkan, penyembunyian data dengan tampilan sederhana yang dapat diperbarui, pemeliharaan data ringkasan dalam tabel terpisah, dan pemanggilan kode yang aman dengan hak istimewa yang lebih tinggi. Artikel ini dibangun lebih lanjut di atas fondasi itu dan menyajikan teknik yang memanfaatkan pemicu dan fungsi tersimpan untuk memfasilitasi pendelegasian penyediaan kredensial login ke peran hak istimewa terbatas (yaitu, non-pengguna super). Fitur ini dapat digunakan untuk mengurangi beban kerja administratif untuk personel administrasi sistem yang bernilai tinggi. Secara ekstrim, kami mendemonstrasikan penyediaan kredensial login pengguna akhir anonim, yaitu, membiarkan calon pengguna database menyediakan kredensial login mereka sendiri dengan menerapkan "SQL dinamis" di dalam fungsi tersimpan yang dijalankan pada tingkat hak istimewa yang tercakup dengan tepat.Pendahuluan

Membaca Latar Belakang Bermanfaat

Artikel terbaru oleh Sebastian Insausti tentang Cara Mengamankan Basis Data PostgreSQL Anda mencakup beberapa tips yang sangat relevan yang harus Anda ketahui, yaitu, Tips #1 - #5 tentang Kontrol Otentikasi Klien, Konfigurasi Server, Manajemen Pengguna dan Peran, Manajemen Pengguna Super, dan Enkripsi data. Kami akan menggunakan bagian dari setiap tip dalam artikel ini.

Artikel terbaru lainnya oleh Joshua Otwell tentang Hak Istimewa &Manajemen Pengguna PostgreSQL juga membahas konfigurasi host dan hak pengguna yang baik yang membahas lebih detail tentang kedua topik tersebut.

Melindungi Lalu Lintas Jaringan

Fitur yang diusulkan melibatkan memungkinkan pengguna untuk menyediakan kredensial login database dan saat melakukannya, mereka akan menentukan nama login dan kata sandi baru mereka melalui jaringan. Perlindungan komunikasi jaringan ini sangat penting dan dapat dicapai dengan mengonfigurasi server PostgreSQL untuk mendukung dan memerlukan koneksi terenkripsi. Keamanan lapisan transport diaktifkan di file postgresql.conf dengan pengaturan “ssl”:

ssl = on

Kontrol Akses Berbasis Host

Untuk kasus ini, kami akan menambahkan baris konfigurasi akses berbasis host di file pg_hba.conf yang memungkinkan anonim, yaitu, tepercaya, login ke database dari beberapa sub-jaringan yang sesuai untuk populasi calon pengguna database secara harfiah menggunakan nama pengguna "anonim", dan baris konfigurasi kedua yang memerlukan kata sandi login untuk nama login lainnya. Ingatlah bahwa konfigurasi host memanggil kecocokan pertama, jadi baris pertama akan berlaku setiap kali nama pengguna "anonim" ditentukan, mengizinkan koneksi tepercaya (yaitu, tidak diperlukan kata sandi), dan kemudian setiap kali nama pengguna lain ditentukan, kata sandi akan diperlukan. Misalnya, jika database sampel "sampledb" akan digunakan, katakanlah, hanya oleh karyawan dan secara internal ke fasilitas perusahaan, maka kami dapat mengonfigurasi akses tepercaya untuk beberapa subnet internal yang tidak dapat dirutekan dengan:

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

Jika database akan tersedia secara umum untuk umum, maka kami dapat mengonfigurasi akses “alamat apa saja”:

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

Catatan di atas berpotensi berbahaya tanpa tindakan pencegahan tambahan, mungkin dalam desain aplikasi atau pada perangkat firewall, untuk membatasi penggunaan fitur ini, karena Anda tahu beberapa skrip kiddie akan mengotomatiskan pembuatan akun tanpa akhir hanya untuk lulz.

Perhatikan juga kami telah menetapkan jenis koneksi sebagai “hostssl” yang berarti koneksi yang dibuat menggunakan TCP/IP hanya berhasil jika koneksi dibuat dengan enkripsi SSL untuk melindungi lalu lintas jaringan dari penyadapan.

Mengunci Skema Publik

Karena kami mengizinkan orang yang mungkin tidak dikenal (yaitu, tidak dipercaya) untuk mengakses database, kami ingin memastikan bahwa akses default memiliki kemampuan terbatas. Salah satu tindakan penting adalah mencabut hak istimewa pembuatan objek skema publik default untuk mengurangi kerentanan PostgreSQL yang baru-baru ini diterbitkan terkait dengan hak istimewa skema default (lih. Mengunci Skema Publik dengan benar-benar milik Anda).

Contoh Basis Data

Kita akan mulai dengan database sampel kosong untuk tujuan ilustrasi:

create database sampledb;
\connect sampledb

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

Kami juga membuat peran login anonim yang sesuai dengan pengaturan pg_hba.conf sebelumnya.

create role anonymous login
    nosuperuser 
    noinherit 
    nocreatedb 
    nocreaterole 
    Noreplication;

Dan kemudian kami melakukan sesuatu yang baru dengan mendefinisikan pandangan yang tidak konvensional:

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

Tampilan ini tidak merujuk pada tabel sehingga kueri pemilihan selalu mengembalikan baris kosong:

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

Satu hal yang kami lakukan adalah memberikan dokumentasi atau petunjuk kepada pengguna akhir tentang data apa yang diperlukan untuk membuat akun. Artinya, dengan mengkueri tabel, meskipun hasilnya berupa baris kosong, hasilnya mengungkapkan nama kedua elemen data tersebut.

Namun lebih baik lagi, keberadaan tampilan ini memungkinkan penentuan tipe data yang diperlukan:

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

Kami akan mengimplementasikan fungsi penyediaan kredensial dengan fungsi dan pemicu yang tersimpan, jadi mari deklarasikan template fungsi kosong dan pemicu terkait:

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

Perhatikan bahwa kami mengikuti konvensi penamaan yang diusulkan dari artikel sebelumnya, menggunakan nama tabel terkait yang diakhiri dengan singkatan singkatan yang menunjukkan atribut dari hubungan pemicu antara tabel dan fungsi yang disimpan untuk pemicu INSTEAD OF INSERT (yaitu, akhiran “ itu"). Kami juga telah menambahkan ke fungsi tersimpan atribut SCHEMA dan SECURITY DEFINER:yang pertama karena ini adalah praktik yang baik untuk mengatur jalur pencarian yang berlaku untuk durasi eksekusi fungsi, dan yang terakhir untuk memfasilitasi pembuatan peran, yang biasanya merupakan otoritas pengguna super basis data hanya tetapi dalam hal ini akan didelegasikan kepada pengguna anonim.

Dan terakhir kami menambahkan izin yang cukup minimal pada tampilan untuk melakukan kueri dan menyisipkan:

grant select, insert on table person to anonymous;
Unduh Whitepaper Hari Ini Pengelolaan &Otomatisasi PostgreSQL dengan ClusterControlPelajari tentang apa yang perlu Anda ketahui untuk menerapkan, memantau, mengelola, dan menskalakan PostgreSQLUnduh Whitepaper

Mari Meninjau

Sebelum menerapkan kode fungsi tersimpan, mari kita tinjau apa yang kita miliki. Pertama ada contoh database yang dimiliki oleh pengguna 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 | {}

Dan ada tampilan yang kami buat dan daftar hak akses buat dan baca yang diberikan kepada pengguna anonim oleh pengguna 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)

Terakhir, detail tabel menunjukkan nama kolom dan tipe data serta pemicu terkait:

\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 Dinamis

Kami akan menggunakan SQL dinamis, yaitu, membangun bentuk akhir dari pernyataan DDL saat run-time sebagian dari data yang dimasukkan pengguna, untuk mengisi badan fungsi pemicu. Secara khusus kami membuat kode keras garis besar pernyataan untuk membuat peran login baru dan mengisi parameter spesifik sebagai variabel.

Bentuk umum dari perintah ini adalah

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

di mana opsi dapat berupa salah satu dari enam belas properti spesifik. Umumnya defaultnya sesuai, tetapi kami akan menjelaskan secara eksplisit tentang beberapa opsi pembatasan dan menggunakan formulir

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

di mana kami akan memasukkan nama peran dan kata sandi yang ditentukan pengguna saat dijalankan.

Pernyataan yang dibangun secara dinamis dipanggil dengan perintah eksekusi:

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

yang untuk kebutuhan spesifik kita akan terlihat seperti

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

di mana fungsi quote_literal mengembalikan argumen string yang dikutip dengan tepat untuk digunakan sebagai string literal untuk memenuhi persyaratan sintaksis bahwa kata sandi sebenarnya dikutip..

Setelah string perintah dibuat, kami menyediakannya sebagai argumen untuk perintah pl/pgsql execute di dalam fungsi trigger.

Menyatukan ini semua terlihat seperti:

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

Ayo Coba!

Semuanya sudah ada, jadi mari kita putar! Pertama kita mengalihkan otorisasi sesi ke pengguna anonim dan kemudian melakukan penyisipan terhadap tampilan orang:

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

Hasilnya adalah alice pengguna baru telah ditambahkan ke tabel sistem:

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

Ia bahkan bekerja langsung dari baris perintah sistem operasi dengan menyalurkan string perintah SQL ke utilitas klien psql untuk menambahkan bob pengguna:

$ 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 | {}

Terapkan Beberapa Armor

Contoh awal dari fungsi pemicu rentan terhadap serangan injeksi SQL, yaitu aktor ancaman jahat dapat membuat input yang menghasilkan akses tidak sah. Misalnya, saat terhubung sebagai peran pengguna anonim, upaya untuk melakukan sesuatu di luar cakupan gagal dengan tepat:

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

Namun masukan berbahaya berikut membuat peran pengguna super bernama 'eve' (serta akun umpan bernama '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 | {}

Kemudian peran superuser sembunyi-sembunyi dapat digunakan untuk membuat kekacauan di database, misalnya menghapus akun pengguna (atau lebih buruk!):

\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 | {}

Untuk mengurangi kerentanan ini, kita harus mengambil langkah-langkah untuk membersihkan input. Misalnya, menerapkan fungsi quote_ident, yang mengembalikan string yang dikutip dengan tepat untuk digunakan sebagai pengenal dalam pernyataan SQL dengan tanda kutip ditambahkan bila perlu, seperti jika string berisi karakter non-identifier atau akan dilipat huruf besar-kecil, dan disematkan dua kali lipat dengan benar kutipan:

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

Sekarang jika eksploitasi injeksi SQL yang sama dicoba untuk membuat pengguna super lain bernama 'frank', itu gagal, dan hasilnya adalah nama pengguna yang sangat tidak lazim:

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 | {}

Kami dapat menerapkan validasi data lebih lanjut yang masuk akal dalam fungsi pemicu seperti hanya membutuhkan nama pengguna alfanumerik dan menolak spasi dan karakter lain:

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

lalu konfirmasikan bahwa berbagai pemeriksaan sanitasi berfungsi:

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.

Mari Tingkatkan Tingkat

Misalkan kita ingin menyimpan metadata atau data aplikasi tambahan yang terkait dengan peran pengguna yang dibuat, mis., mungkin cap waktu dan alamat IP sumber yang terkait dengan pembuatan peran. Tampilan tentu saja tidak dapat memenuhi persyaratan baru ini karena tidak ada penyimpanan yang mendasarinya, sehingga diperlukan tabel yang sebenarnya. Selain itu, mari kita asumsikan lebih lanjut bahwa kita ingin membatasi visibilitas tabel itu dari pengguna yang masuk dengan peran masuk anonim. Kami dapat menyembunyikan tabel di namespace terpisah (yaitu, skema PostgreSQL) yang tetap tidak dapat diakses oleh pengguna anonim. Sebut namespace ini sebagai namespace "pribadi" dan buat tabel di namespace:

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

Perintah penyisipan tambahan sederhana di dalam fungsi pemicu merekam metadata terkait ini:

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

Dan kita bisa memberikannya ujian yang mudah. Pertama, kami mengonfirmasi bahwa saat terhubung sebagai peran anonim, hanya tampilan public.person yang terlihat dan bukan tabel 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

Dan kemudian setelah memasukkan peran baru:

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)

tabel private.person menunjukkan pengambilan metadata untuk alamat IP dan waktu penyisipan baris.

Kesimpulan

Dalam artikel ini, kami telah mendemonstrasikan teknik untuk mendelegasikan penyediaan kredensial peran PostgreSQL ke peran non-pengguna super. Sementara contoh sepenuhnya mendelegasikan fungsionalitas kredensial kepada pengguna anonim, pendekatan serupa dapat digunakan untuk mendelegasikan sebagian fungsionalitas hanya kepada personel tepercaya sambil tetap mempertahankan manfaat dari membongkar pekerjaan ini dari personel administrator sistem atau database bernilai tinggi. Kami juga mendemonstrasikan teknik akses data berlapis menggunakan skema PostgreSQL, secara selektif mengekspos atau menyembunyikan objek database. Pada artikel berikutnya dalam seri ini kami akan memperluas teknik akses data berlapis untuk mengusulkan desain arsitektur database baru untuk implementasi aplikasi.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Rails:FATAL - Otentikasi rekan gagal untuk pengguna (PG::Error)

  2. 'otentikasi kata sandi gagal untuk postgres pengguna'

  3. Fungsi SQL sangat lambat dibandingkan dengan kueri tanpa pembungkus fungsi

  4. Bagaimana Fungsi Radian() Bekerja di PostgreSQL

  5. [Video] Kekuatan Pengindeksan di PostgreSQL