=========== 
SQL/Protect
===========

SQL/Protect is an extension for Postgres Plus(R) that allows a database
administrator to protect a database from SQL injection attacks. A "SQL
injection attack" is an attempt to compromise a database by running
SQL statements whose results provide clues to the attacker as to the
content, structure, or security of that database.

Preventing a SQL injection attack is normally the responsibility of the
application developer. The database administrator typically has little
or no control over the potential threat.

The difficulty for database administrators is that the application must
have access to the data to function properly. SQL/Protect provides a
layer of security in addition to the normal database security policies
by examining incoming queries for common SQL injection profiles.

SQL/Protect gives the control back to the database administrator by
alerting the administrator to potentially dangerous queries and by
blocking these queries.

========
OVERVIEW
========

The following is a brief description of how SQL/Protect guards against
SQL injection attacks.

Types of SQL Injection Attacks
==============================

There are a number of different techniques used to perpetrate SQL
injection attacks. Each technique is characterized by a certain
"signature".

SQL/Protect examines queries for the following signatures:

Unauthorized Relations
----------------------

While Postgres Plus allows administrators to restrict access to
relations (tables, views, etc.), many administrators do not perform
this tedious task. SQL/Protect provides a "learn" mode that tracks
the relations a user accesses.

This allows administrators to examine the workload of an application,
and for SQL/Protect to learn which relations an application should be
allowed to access for a given user.

When SQL/Protect is switched to either "passive" or "active" mode, the
incoming queries are checked against the list of learned relations.

Utility Commands
----------------

A common technique used in SQL injection attacks is to run utility
commands, which are typically SQL Data Definition Language (DDL)
statements. An example is creating a user-defined function that has
the ability to access other system resources.

SQL/Protect can block the running of all utility commands, which are
not normally needed during standard application processing.

SQL Tautology
-------------

The most frequent technique used in SQL injection attacks is issuing
a tautological WHERE clause condition (that is, using a condition that
is always true).

The following is an example:

  WHERE password = 'x' OR 'x'='x'

Attackers will usually start identifying security weaknesses using
this technique. SQL/Protect can block queries that use a tautological
conditional clause.

Unbounded DML Statements
------------------------

A dangerous action taken during SQL injection attacks is the running
of unbounded DML statements. These are UPDATE and DELETE statements
with no WHERE clause. For example, an attacker may update all users'
passwords to a known value or initiate a denial of service attack by
deleting all of the data in a key table.

Protected Roles
===============

Monitoring for SQL injection attacks involves analyzing SQL statements
originating in database sessions where the current user of the session
is a protected role. A "protected role" is a Postgres Plus user or
group that the database administrator has chosen to monitor using
SQL/Protect. (In Postgres Plus, users and groups are collectively
referred to as "roles".)

Each protected role can be customized for the types of SQL injection
attacks for which it is to be monitored, thus providing different
levels of protection by role.

Note: A role with the superuser privilege cannot be made a protected
      role. If however, a protected non-superuser role is subsequently
      altered to become a superuser, certain behaviors are exhibited
      whenever an attempt is made by that superuser to issue any
      command:
 
      - A warning message is issued by SQL/Protect on every command
        issued by the protected superuser

      - When SQL/Protect is in active mode, all commands issued by the
        superuser are prevented from running

      A protected role that has the superuser privilege should either
      be altered so that it is no longer a superuser, or it should be
      reverted back to an unprotected role.

Attack Attempt Statistics
=========================

Each usage of a command by a protected role that is considered an
attack by SQL/Protect is recorded in a statistics view that can be
easily monitored to identify the start of a potential attack.

Statistics are collected by type of SQL injection attack.

This gives database administrators the opportunity to react proactively
in preventing theft of valuable data or other malicious actions.

Attack Attempt Queries
======================

Each usage of a command by a protected role that is considered an
attack by SQL/Protect is also recorded in a queries view which
contains the following fields:
  - username
    The name the attacker used to login to the databse server.
  - ip_address
    The IP address of the machine that was used to fire the query.
  - port
    The source port number used to fire the query.
  - machine_name
    The name of the machine (if available) that was used to
    fire the query.
  - date_time
    The date and time at which the query was received at the
    server. The time is stored up to the precision of a minute.
  - query
    The query string sent by the attacker.

============
INSTALLATION
============

The library file ('sqlprotect.so' on Linux, 'sqlprotect.dll' on Windows)
necessary to run SQL/Protect should already be installed in the 'lib'
subdirectory of your Postgres Plus home directory.

You will also need the SQL script file 'sqlprotect.sql' located in the
'share/contrib' subdirectory of your Postgres Plus home directory.

Starting SQL/Protect
====================

1. Edit the postgresql.conf configuration file, located in the 'data' 
   subdirectory of your Postgres Plus home directory.  Adjust the following
   parameters:

   a) shared_preload_libraries - Edit the parameter, adding 
      '$libdir/sqlprotect' to the current list:

        shared_preload_libraries = '...,$libdir/sqlprotect'

   Note: If the configuration parameter already contains a list of
         entries, add the new entry to the list delimited by a comma
         (,) from the previous entry.

   Then, add the following configuration parameters to the postgresql.conf file:

   b) edb_sql_protect.enabled - Controls whether or not SQL/Protect is
      in effect. Set this parameter to 'on' so SQL/Protect will be
      running. Default is 'off'.

        edb_sql_protect.enabled = on

   c) edb_sql_protect.level - Sets the action taken by SQL/Protect
      when a SQL statement is issued by a protected role. Default is
      'passive'.

      Initially, set this parameter to 'learn'. (A full explanation of
      this parameter is given in the CONFIGURATION section.)

        edb_sql_protect.level = learn

   Optional - The following configuration parameters can be omitted and
              allowed to default, or added to the configuration file to 
              enforce user-specified values:

   d) edb_sql_protect.max_protected_roles - Sets the maximum number of
      roles that can be protected. Default is 64.

   e) edb_sql_protect.max_protected_relations - Sets the maximum number
      of relations that can be protected per role. Default is 1024.

2. For each database that you want to protect from SQL injection attacks:

  a) Connect to the database as a superuser (either 'enterprisedb' or
     'postgres' depending upon your installation options).

     You can use the PEM Client if you prefer a graphical user
     interface, or you can use psql if you prefer a command line
     utility.

     The following example uses psql:

       $ /opt/PostgresPlus/9.3AS/bin/psql -d edb -U enterprisedb
       Password for user enterprisedb:
       Type "help" for help.

       edb=#

  b) Run the script 'sqlprotect.sql' located in the 'share/contrib'
     subdirectory of the Postgres Plus home directory.

       edb=# \i /opt/PostgresPlus/9.3AS/share/contrib/sqlprotect.sql
       CREATE SCHEMA
       GRANT
       SET
       CREATE TABLE
       GRANT
       CREATE TABLE
       GRANT
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE FUNCTION
       CREATE VIEW
       GRANT
       CREATE VIEW
       GRANT
       CREATE VIEW
       GRANT
       CREATE VIEW
       GRANT
       CREATE FUNCTION
       CREATE FUNCTION
       SET

    The SQL/Protect database objects are built in a schema named
    'sqlprotect'.

3. Restart the Postgres Plus database server.  

   On Linux: 
   
     Assume the identity of the 'root' user, and run '/etc/init.d/ppas-9.3' 
     as follows:

     $ su root
     Password:
     $ /etc/init.d/ppas-9.3 restart
     Stopping Postgres Plus Advanced Server 9.3:
     waiting for server to shut down.... done
     server stopped
     Starting Postgres Plus Advanced Server 9.3:
     waiting for server to start.... done
     server started
     Postgres Plus Advanced Server 9.3 started successfully   

   On Windows:  
     Open the Windows Control Panel; navigate to Administrative Tools, then Services. 
     Restart the 'ppas-9.3 - Postgres Plus Advanced Server 9.3' service.

   On Solaris SPARC:

     Assume superuser privileges, and use the following command to restart the server:
       
       svcadm restart ppas-9_3


=============
CONFIGURATION
=============

After SQL/Protect has been installed in a database, you select the roles
for which SQL queries are to be monitored for protection, and the
current level of protection.

Selecting Roles to Protect
==========================

Each role that you wish to protect must be added to the protected roles
list. This list is maintained in the table 'edb_sql_protect'.

To add a role use the function protect_role('rolename').

For example, connect to the PEM Client or psql as a superuser and
issue the following command to protect a role named 'appuser'.

  edb=# SELECT sqlprotect.protect_role('appuser');
   protect_role
  --------------

  (1 row)

You can list the roles that have been added to the protected roles list
by issuing the following query:

  edb=# SELECT * FROM sqlprotect.edb_sql_protect;
   dbid  | roleid | protect_relations | allow_utility_cmds | allow_tautology | allow_empty_dml
  -------+--------+-------------------+--------------------+-----------------+-----------------
   14414 |  16480 | t                 | f                  | f               | f
  (1 row)

A view is also provided that gives the same information using the object
names instead of the Object Identification numbers (OIDs).

  edb=# SELECT * FROM sqlprotect.list_protected_users;
   dbname | username | protect_relations | allow_utility_cmds | allow_tautology | allow_empty_dml
  --------+----------+-------------------+--------------------+-----------------+-----------------
   edb    | appuser  | t                 | f                  | f               | f
  (1 row)

Setting the Protection Level
============================

Configuration parameter 'edb_sql_protect.level' defines the behavior
of SQL/Protect when an event is found. This behavior applies to all
roles in the 'edb_sql_protect' table.

The 'edb_sql_protect.level' configuration parameter can be set to one
of the following values:

  learn   - Tracks the activities of protected roles and records the
            relations used by the roles. This is used when initially
            configuring SQL/Protect so the expected behaviors of the
            protected applications are learned.

  passive - Issues warnings if protected roles are breaking the
            defined rules, but does not stop any SQL statements from
            executing. This is the next step after SQL/Protect has
            learned the expected behavior of the protected roles.
            This essentially behaves in intrusion detection mode and
            can be run in production when properly monitored.

  active  - Stops all invalid statements for a protected role. This
            behaves as a SQL firewall preventing dangerous queries from
            running. This is particularly effective against early
            penetration testing when the attacker is trying to determine
            the vulnerability point and the type of database behind the
            application. Not only does SQL/Protect close those
            vulnerability points, but it tracks the blocked queries
            allowing administrators to be alerted before the attacker
            finds an alternate method of penetrating the system.

If the 'edb_sql_protect.level' parameter is omitted from the
configuration file, the default behavior of SQL/Protect is passive.

Setting the maximum number of queries to store
==============================================

Configuration parameter 'edb_sql_protect.max_queries_to_save' defines
the maximum number of offending queries to be saved by edb_sql_protect.
Its default value is 5000. If the number of saved queries reaches this
value, further queries are not saved, only a message is logged in
server log.

=====
USAGE
=====

Once a database is configured with SQL/Protect, it is used as follows.

Basic Steps
===========

1. SQL/Protect must first learn which relations are used by a given,
   protected role.

   As applications run by a protected role access relations, SQL/Protect
   makes entries into the 'edb_sql_protect_rel' table. You can verify
   that this information is being collected by using the following
   query:

     edb=# SELECT * FROM sqlprotect.edb_sql_protect_rel;
      dbid  | roleid | relid
     -------+--------+-------
      14414 |  16480 | 16384
      14414 |  16480 | 16391
      14414 |  16480 | 16481
     (3 rows)


   The view 'list_protected_rels' is provided that gives more
   comprehensive information using the object names instead of the
   Object Identification numbers (OIDs).

     edb=# SELECT * FROM sqlprotect.list_protected_rels;
      Database | Protected User | Schema |    Name     | Type  |    Owner
     ----------+----------------+--------+-------------+-------+--------------
      edb      | appuser        | public | dept        | Table | enterprisedb
      edb      | appuser        | public | emp         | Table | enterprisedb
      edb      | appuser        | public | appuser_tab | Table | appuser
     (3 rows)

2. Once you have determined that a role's applications have accessed
   all relations they will need, you can now change the protection
   level so that SQL/Protect can actively monitor the incoming SQL
   queries.

   Modify the 'edb_sql_protect.level' parameter to either passive or
   active, depending upon the type of monitoring behavior desired.

   For example:

     edb_sql_protect.level = passive

3. Load the new value of 'edb_sql_protect.level' by executing the
   function 'pg_reload_conf()' while connected as a superuser.

     edb=# SELECT pg_reload_conf();
      pg_reload_conf
     ----------------
      t
     (1 row)

4. SQL/Protect updates the statistics in the 'edb_sql_protect_stats'
   view to show the number of times a SQL statement of the type listed
   in the OVERVIEW section is attempted by each protected role:

     edb=# SELECT * FROM sqlprotect.edb_sql_protect_stats;
      username | superusers | relations | commands | tautology | dml
     ----------+------------+-----------+----------+-----------+-----
      appuser  |          0 |         3 |        1 |         1 |   0
     (1 row)

5. SQL/Protect stores the stopped queries in the 'edb_sql_protect_queries'
   view:

     edb=# select * FROM sqlprotect.edb_sql_protect_queries;
      username |   ip_address   | port  | machine_name |     date_time      |                            query
     ----------+----------------+-------+--------------+--------------------+--------------------------------------------
      appuser  |                |       |              | 07-MAY-14 11:20:00 | update protected set b = c;
      appuser  | 172.16.188.212 | 35154 |              | 07-MAY-14 11:22:00 | update protected set c = d;
      appuser  | 172.16.188.184 | 34946 |              | 19-APR-14 17:28:00 | delete from protected where b = b;
      appuser  | 172.16.188.184 | 34946 |              | 19-APR-14 17:28:00 | create view mv as select * from protected;
      appuser  |                |       |              | 01-MAY-14 17:10:00 | update protected                          +
               |                |       |              |                    | set b = b + 22                            +
               |                |       |              |                    | where d = d;
     (5 rows)

Miscellaneous Operations
========================

The following describes how to perform other common operations.

For most functions described in the following sections, either
an object's text name or its OID may be specified in the parameter
list. The alternate formats are shown if allowed.

Adding a Role to the Protected Roles List
-----------------------------------------

To add a role to the protected roles list connect as a superuser and
run protect_role('rolename').

  edb=# SELECT sqlprotect.protect_role('newuser');
   protect_role
  --------------

  (1 row)

Removing a Role From the Protected Roles List
---------------------------------------------

To remove a role from the protected roles list connect as a superuser
and run either of the following functions:

  - unprotect_role('rolename')

  - unprotect_role(roleoid)

Removing a role using these functions also removes the role's list
of protected relations.

The statistics for a role that has been removed are not deleted until
you use the drop_stats function.

  edb=# SELECT sqlprotect.unprotect_role('newuser');
   unprotect_role
  ----------------

  (1 row)

  edb=# SELECT sqlprotect.unprotect_role(16481);
   unprotect_role
  ----------------

  (1 row)


Setting the Types of Protection for a Role
-------------------------------------------

You can change whether or not a role is protected from a certain type
of SQL injection attack.

Change the Boolean value for the column in 'edb_sql_protect'
corresponding to the type of SQL injection attack for which
protection of a role is to be disabled or enabled.

For example, to allow utility commands issued by a given role, change
the 'allow_utility_cmds' column as follows:

  UPDATE sqlprotect.edb_sql_protect SET allow_utility_cmds = TRUE
    WHERE dbid = 14414 AND roleid = 16480;
  
  - dbid = OID of the database for which you are making the change

  - roleid = OID of the role for which you are changing the Boolean
    settings.

The updated rules take effect on new sessions started by the role since
the change was made.

Removing a Relation From the List of Relations Protected for a Role
-------------------------------------------------------------------

If SQL/Protect has learned that a given relation is accessible for a
given role, you can subsequently remove that relation from the role's
list of protected relations.

Delete its entry from the 'edb_sql_protect_rel' table using any of
the following functions:

  - unprotect_rel('rolename', 'relname')

  - unprotect_rel('rolename', 'schema', 'relname')

  - unprotect_rel(roleoid, reloid);

The following is an example:

  edb=# SELECT sqlprotect.unprotect_rel('appuser', 'emp');
   unprotect_rel
  ---------------

  (1 row)

SQL/Protect will then issue a warning or completely block access
(depending upon the setting of 'edb_sql_protect.level') whenever
the role attempts to utilize that relation.

Deleting Statistics
-------------------

To delete statistics connect as a superuser and run either of the
following functions:

  - drop_stats('rolename')

  - drop_stats(roleoid)

For example:

  edb=# SELECT sqlprotect.drop_stats('appuser');
   drop_stats
  ------------

  (1 row)


Deleting Queries
-------------------

To delete stored queries connect as a superuser and run either of the
following functions:

  - drop_queries('rolename')

  - drop_queries(roleoid)

For example:

  edb=# SELECT sqlprotect.drop_queries('appuser');
   drop_queries
  --------------
              5
  (1 row)

Stopping and Starting SQL/Protect
---------------------------------

If you wish to turn off SQL/Protect once you have installed and
configured it, perform the following steps:

1. Set the configuration parameter 'edb_sql_protect.enabled' to 'off' in
   the 'postgresql.conf' file.

2. Connect as a superuser and run 'pg_reload_conf()' as shown by the
   following:

     edb=# SELECT pg_reload_conf();
      pg_reload_conf
     ----------------
      t
     (1 row)

To re-enable SQL/Protect perform the following steps:

1. Set the configuration parameter 'edb_sql_protect.enabled' to 'on' in
   the 'postgresql.conf' file.

2. Connect as a superuser and run 'pg_reload_conf()' as shown by the
   following:

     edb=# SELECT pg_reload_conf();
      pg_reload_conf
     ----------------
      t
     (1 row)

Dumping and Restoring a Database Configured With SQL/Protect
------------------------------------------------------------

If a database contains SQL/Protect database objects and you wish to
dump this database and later restore the dump file to another database,
you need to follow the steps listed below in order to properly move the
SQL/Protect objects from one database to the other.

The data in SQL/Protect tables contain OIDs instead of the text names
of protected roles, databases, and relations.

When a dump is restored to a new database, the OIDs assigned to the
restored database objects are typically different than the OIDs that
were assigned to the objects in the original database.

The OIDs in the SQL/Protect tables need to be set to the OIDs
assigned to the roles, databases, and relations in the new database.

1. Dump the database using the pg_dump utility program:

     $ cd /opt/PostgresPlus/9.3AS/bin
     $ ./pg_dump -U enterprisedb -Fp -f /tmp/edb.dmp edb
     Password:

2. Connect as a superuser and export the SQL/Protect data using the
   export_sqlprotect('sqlprotect_file') function where
   'sqlprotect_file' is the fully qualified path to a file where
   the SQL/Protect data is to be dumped.

   Be sure the 'enterprisedb' operating system account ('postgres' if
   you installed with PostgreSQL compatibility mode) has read and
   write access to the directory specified in the 'export_sqlprotect'
   function.

     edb=# SELECT sqlprotect.export_sqlprotect('/tmp/sqlprotect.dmp');
      export_sqlprotect
     -------------------

     (1 row)

3. If it does not already exist, create the database into which you
   want to restore your dump file.

     edb=# CREATE DATABASE newdb;
     CREATE DATABASE

4. Restore the dump file to the database.

     $ cd /opt/PostgresPlus/9.3AS/bin
     $ ./psql -d newdb -U enterprisedb -f /tmp/edb.dmp
     Password for user enterprisedb:
     SET
     SET
     SET
     SET
     SET
     SET
     CREATE SCHEMA
         .
         .
         .

5. Connect to the new database as a superuser. Delete all rows from
   table 'edb_sql_protect_rel'.

   This step is necessary because the OIDs of the relations in the new
   database do not match the OIDs of the corresponding relations in
   the 'edb_sql_protect_rel' table that was restored from the pg_dump
   file.

     newdb=# DELETE FROM sqlprotect.edb_sql_protect_rel;
     DELETE 2

6. In the new database, delete all rows from table 'edb_sql_protect'.

   This step is necessary because the OID of the new database does
   not match the database OID in the 'edb_sql_protect' table that was
   restored from the pg_dump file.

     newdb=# DELETE FROM sqlprotect.edb_sql_protect;
     DELETE 1

7. In the new database, delete all rows from sql protect statistics hash, 
   using drop_stats() function as shown below.

     newdb=# SELECT sqlprotect.drop_stats('username');

8. Make sure the role names that were protected in the original
   database exist in the database server where the new database
   resides.

   If the original and new databases reside in the same database
   server, then nothing needs to be done assuming you have not deleted
   any of these roles from the database server.

9. Connect to the new database as a superuser and run the function
   import_sqlprotect('sqlprotect_file') where 'sqlprotect_file' is the
   fully qualified path to the file you created in Step 2.

     newdb=# SELECT sqlprotect.import_sqlprotect('/tmp/sqlprotect.dmp');
      import_sqlprotect
     -------------------

     (1 row)

   The tables 'edb_sql_protect', 'edb_sql_protect_rel' are now populated with entries 
   containing the OIDs of the database objects as assigned in the new database. The
   "edb_sql_protect_stats" view will now return the sql protect statistics data imported
   from the dump.


-------------------------------------------
Copyright (c) 2011-2013 EnterpriseDB Corporation

