V-73009

Severity: Medium

Generated

2019-05-20 15:48:11.984914

Status

Passed

Access to external executables must be disabled or restricted.

NIST 800-53

STIG # Description Result
CM-7 CM-7: Least Functionality passed

Guidance

Information systems are capable of providing a wide variety of functions and services. Some of the functions and services, provided by default, may not be necessary to support essential organizational operations (e.g., key missions, functions).

It is detrimental for applications to provide, or install by default, functionality exceeding requirements or mission objectives.

Applications must adhere to the principles of least functionality by providing only essential capabilities.

PostgreSQLs may spawn additional external processes to execute procedures that are defined in PostgreSQL but stored in external host files (external procedures). The spawned process used to execute the external procedure may operate within a different OS security context than PostgreSQL and provide unauthorized access to the host system.

Check

PostgreSQL’s Copy command can interact with the underlying OS. Only superuser has access to this command.

First, as the database administrator (shown here as “postgres”), run the following SQL to list all roles and their privileges:

$ sudo su - postgres $ psql -x -c “\du”

If any role has “superuser” that should not, this is a finding.

It is possible for an extension to contain code that could access external executables via SQL. To list all installed extensions, as the database administrator (shown here as “postgres”), run the following SQL:

$ sudo su - postgres $ psql -x -c “SELECT * FROM pg_available_extensions WHERE installed_version IS NOT NULL”

If any extensions are installed that are not approved, this is a finding.

Fix

To remove superuser from a role, as the database administrator (shown here as “postgres”), run the following SQL:

$ sudo su - postgres $ psql -c “ALTER ROLE WITH NOSUPERUSER”

To remove extensions from PostgreSQL, as the database administrator (shown here as “postgres”), run the following SQL:

$ sudo su - postgres $ psql -c “DROP EXTENSION extension_name”

Test Results

  Result
PostgreSQL query: SELECT r.rolsuper FROM pg_catalog.pg_roles r WHERE r.rolname = 'pg_monitor'; output should not eq "t" passed
PostgreSQL query: SELECT r.rolsuper FROM pg_catalog.pg_roles r WHERE r.rolname = 'pg_read_all_settings'; output should not eq "t" passed
PostgreSQL query: SELECT r.rolsuper FROM pg_catalog.pg_roles r WHERE r.rolname = 'pg_read_all_stats'; output should not eq "t" passed
PostgreSQL query: SELECT r.rolsuper FROM pg_catalog.pg_roles r WHERE r.rolname = 'pg_stat_scan_tables'; output should not eq "t" passed
PostgreSQL query: SELECT r.rolsuper FROM pg_catalog.pg_roles r WHERE r.rolname = 'pg_signal_backend'; output should not eq "t" passed
PostgreSQL query: SELECT r.rolsuper FROM pg_catalog.pg_roles r WHERE r.rolname = 'dashboard'; output should not eq "t" passed
PostgreSQL query: SELECT r.rolsuper FROM pg_catalog.pg_roles r WHERE r.rolname = 'replication'; output should not eq "t" passed
PostgreSQL query: SELECT r.rolsuper FROM pg_catalog.pg_roles r WHERE r.rolname = 'haproxy'; output should not eq "t" passed
PostgreSQL query: SELECT r.rolsuper FROM pg_catalog.pg_roles r WHERE r.rolname = 'testuser'; output should not eq "t" passed
PostgreSQL query: SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name NOT IN ('address_standardizer','address_standardizer_data_us','adminpack','amcheck','autoinc','bloom','btree_gin','btree_gist','chkpass','citext','cube','dblink','dict_int','dict_xsyn','earthdistance','file_fdw','fuzzystrmatch','hstore','hstore_plperl','insert_username','intagg','intarray','isn','lo','ltree','moddatetime','pageinspect','pgaudit','pg_buffercache','pgcrypto','pg_freespacemap','pg_prewarm','pgrowlocks','pg_stat_statements','pgstattuple','pg_trgm','pg_visibility','postgis','postgis_tiger_geocoder','postgis_topology','postgres_fdw','redis_fdw','refint','seg','set_user','sslinfo','tablefunc','tcn','timetravel','tsm_system_rows','tsm_system_time','unaccent','uuid-ossp','xml2','plpgsql','plperl','plr'); output should eq "" passed
PostgreSQL query with errors: SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name NOT IN ('address_standardizer','address_standardizer_data_us','adminpack','amcheck','autoinc','bloom','btree_gin','btree_gist','chkpass','citext','cube','dblink','dict_int','dict_xsyn','earthdistance','file_fdw','fuzzystrmatch','hstore','hstore_plperl','insert_username','intagg','intarray','isn','lo','ltree','moddatetime','pageinspect','pgaudit','pg_buffercache','pgcrypto','pg_freespacemap','pg_prewarm','pgrowlocks','pg_stat_statements','pgstattuple','pg_trgm','pg_visibility','postgis','postgis_tiger_geocoder','postgis_topology','postgres_fdw','redis_fdw','refint','seg','set_user','sslinfo','tablefunc','tcn','timetravel','tsm_system_rows','tsm_system_time','unaccent','uuid-ossp','xml2','plpgsql','plperl','plr'); output should match /FATAL:\s+database "template0" is not currently accepting connections/ passed
PostgreSQL query: SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name NOT IN ('address_standardizer','address_standardizer_data_us','adminpack','amcheck','autoinc','bloom','btree_gin','btree_gist','chkpass','citext','cube','dblink','dict_int','dict_xsyn','earthdistance','file_fdw','fuzzystrmatch','hstore','hstore_plperl','insert_username','intagg','intarray','isn','lo','ltree','moddatetime','pageinspect','pgaudit','pg_buffercache','pgcrypto','pg_freespacemap','pg_prewarm','pgrowlocks','pg_stat_statements','pgstattuple','pg_trgm','pg_visibility','postgis','postgis_tiger_geocoder','postgis_topology','postgres_fdw','redis_fdw','refint','seg','set_user','sslinfo','tablefunc','tcn','timetravel','tsm_system_rows','tsm_system_time','unaccent','uuid-ossp','xml2','plpgsql','plperl','plr'); output should eq "" passed
PostgreSQL query: SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name NOT IN ('address_standardizer','address_standardizer_data_us','adminpack','amcheck','autoinc','bloom','btree_gin','btree_gist','chkpass','citext','cube','dblink','dict_int','dict_xsyn','earthdistance','file_fdw','fuzzystrmatch','hstore','hstore_plperl','insert_username','intagg','intarray','isn','lo','ltree','moddatetime','pageinspect','pgaudit','pg_buffercache','pgcrypto','pg_freespacemap','pg_prewarm','pgrowlocks','pg_stat_statements','pgstattuple','pg_trgm','pg_visibility','postgis','postgis_tiger_geocoder','postgis_topology','postgres_fdw','redis_fdw','refint','seg','set_user','sslinfo','tablefunc','tcn','timetravel','tsm_system_rows','tsm_system_time','unaccent','uuid-ossp','xml2','plpgsql','plperl','plr'); output should eq "" passed
PostgreSQL query: SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name NOT IN ('address_standardizer','address_standardizer_data_us','adminpack','amcheck','autoinc','bloom','btree_gin','btree_gist','chkpass','citext','cube','dblink','dict_int','dict_xsyn','earthdistance','file_fdw','fuzzystrmatch','hstore','hstore_plperl','insert_username','intagg','intarray','isn','lo','ltree','moddatetime','pageinspect','pgaudit','pg_buffercache','pgcrypto','pg_freespacemap','pg_prewarm','pgrowlocks','pg_stat_statements','pgstattuple','pg_trgm','pg_visibility','postgis','postgis_tiger_geocoder','postgis_topology','postgres_fdw','redis_fdw','refint','seg','set_user','sslinfo','tablefunc','tcn','timetravel','tsm_system_rows','tsm_system_time','unaccent','uuid-ossp','xml2','plpgsql','plperl','plr'); output should eq "" passed
PostgreSQL query: SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name NOT IN ('address_standardizer','address_standardizer_data_us','adminpack','amcheck','autoinc','bloom','btree_gin','btree_gist','chkpass','citext','cube','dblink','dict_int','dict_xsyn','earthdistance','file_fdw','fuzzystrmatch','hstore','hstore_plperl','insert_username','intagg','intarray','isn','lo','ltree','moddatetime','pageinspect','pgaudit','pg_buffercache','pgcrypto','pg_freespacemap','pg_prewarm','pgrowlocks','pg_stat_statements','pgstattuple','pg_trgm','pg_visibility','postgis','postgis_tiger_geocoder','postgis_topology','postgres_fdw','redis_fdw','refint','seg','set_user','sslinfo','tablefunc','tcn','timetravel','tsm_system_rows','tsm_system_time','unaccent','uuid-ossp','xml2','plpgsql','plperl','plr'); output should eq "" passed
PostgreSQL query: SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name NOT IN ('address_standardizer','address_standardizer_data_us','adminpack','amcheck','autoinc','bloom','btree_gin','btree_gist','chkpass','citext','cube','dblink','dict_int','dict_xsyn','earthdistance','file_fdw','fuzzystrmatch','hstore','hstore_plperl','insert_username','intagg','intarray','isn','lo','ltree','moddatetime','pageinspect','pgaudit','pg_buffercache','pgcrypto','pg_freespacemap','pg_prewarm','pgrowlocks','pg_stat_statements','pgstattuple','pg_trgm','pg_visibility','postgis','postgis_tiger_geocoder','postgis_topology','postgres_fdw','redis_fdw','refint','seg','set_user','sslinfo','tablefunc','tcn','timetravel','tsm_system_rows','tsm_system_time','unaccent','uuid-ossp','xml2','plpgsql','plperl','plr'); output should eq "" passed
PostgreSQL query: SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name NOT IN ('address_standardizer','address_standardizer_data_us','adminpack','amcheck','autoinc','bloom','btree_gin','btree_gist','chkpass','citext','cube','dblink','dict_int','dict_xsyn','earthdistance','file_fdw','fuzzystrmatch','hstore','hstore_plperl','insert_username','intagg','intarray','isn','lo','ltree','moddatetime','pageinspect','pgaudit','pg_buffercache','pgcrypto','pg_freespacemap','pg_prewarm','pgrowlocks','pg_stat_statements','pgstattuple','pg_trgm','pg_visibility','postgis','postgis_tiger_geocoder','postgis_topology','postgres_fdw','redis_fdw','refint','seg','set_user','sslinfo','tablefunc','tcn','timetravel','tsm_system_rows','tsm_system_time','unaccent','uuid-ossp','xml2','plpgsql','plperl','plr'); output should eq "" passed

Code

control "V-73009" do
  title "Access to external executables must be disabled or restricted."
  desc  "Information systems are capable of providing a wide variety of functions
and services. Some of the functions and services, provided by default, may not be
necessary to support essential organizational operations (e.g., key missions,
functions).

It is detrimental for applications to provide, or install by default, functionality
exceeding requirements or mission objectives.

Applications must adhere to the principles of least functionality by providing only
essential capabilities.

PostgreSQLs may spawn additional external processes to execute procedures that are
defined in PostgreSQL but stored in external host files (external procedures). The
spawned process used to execute the external procedure may operate within a
different OS security context than PostgreSQL and provide unauthorized access to the
host system."
  impact 0.5
  tag "severity": "medium"
  tag "gtitle": "SRG-APP-000141-DB-000093"
  tag "gid": "V-73009"
  tag "rid": "SV-87661r1_rule"
  tag "stig_id": "PGS9-00-009100"
  tag "cci": "CCI-000381"
  tag "nist": ["CM-7 a", "Rev_4"]
  tag "check": "PostgreSQL’s Copy command can interact with the underlying OS. Only
superuser has access to this command.

First, as the database administrator (shown here as \"postgres\"), run the following
SQL to list all roles and their privileges:

$ sudo su - postgres
$ psql -x -c \"\\du\"

If any role has \"superuser\" that should not, this is a finding.

It is possible for an extension to contain code that could access external
executables via SQL. To list all installed extensions, as the database administrator
(shown here as \"postgres\"), run the following SQL:

$ sudo su - postgres
$ psql -x -c \"SELECT * FROM pg_available_extensions WHERE installed_version IS NOT
NULL\"

If any extensions are installed that are not approved, this is a finding."
  tag "fix": "To remove superuser from a role, as the database administrator (shown
here as \"postgres\"), run the following SQL:

$ sudo su - postgres
$ psql -c \"ALTER ROLE <role-name> WITH NOSUPERUSER\"

To remove extensions from PostgreSQL, as the database administrator (shown here as
\"postgres\"), run the following SQL:

$ sudo su - postgres
$ psql -c \"DROP EXTENSION extension_name\""

  sql = postgres_session(PG_DBA, PG_DBA_PASSWORD, PG_HOST)

  roles_sql = 'SELECT r.rolname FROM pg_catalog.pg_roles r;'
  roles_query = sql.query(roles_sql, [PG_DB])
  roles = roles_query.lines

  roles.each do |role|
    unless PG_SUPERUSERS.include?(role)
      superuser_sql = "SELECT r.rolsuper FROM pg_catalog.pg_roles r "\
        "WHERE r.rolname = '#{role}';"

      describe sql.query(superuser_sql, [PG_DB]) do
        its('output') { should_not eq 't' }
      end
    end
  end

  extensions = PG_APPROVED_EXTENSIONS + PG_TRUSTED_LANGUAGES
  extensions_sql = "SELECT name FROM pg_available_extensions "\
    "WHERE installed_version IS NOT NULL "\
    "AND name NOT IN (#{extensions.map { |e| "'#{e}'" }.join(',')});"

  databases_sql = 'SELECT datname FROM pg_catalog.pg_database;'
  databases_query = sql.query(databases_sql, [PG_DB])
  databases = databases_query.lines

  databases.each do |database|
    connection_error = "FATAL:\\s+database \"#{database}\" is not currently "\
      "accepting connections"
    connection_error_regex = Regexp.new(connection_error)

    describe.one do
      describe sql.query(extensions_sql, [database]) do
        its('output') { should eq '' }
      end

      describe sql.query(extensions_sql, [database]) do
        it { should match connection_error_regex }
      end
    end
  end
end