内容


PostgreSQL 数据库的整体安全性

建立第一道防线

Comments

简介

报纸上经常出现黑客袭击企业数据库的报道。大多数攻击由叛逆的未成年人发起的日子已经一去不复返。如今,数据收集成为了一项重要的事业,并且由在企业基础设施中工作的专家专门负责。问题已经不再是您如何 阻止未授权的访问企图 — 您无法阻止 — 而在于您在发生这种情况时如何降低 影响。

本文讨论了在保护您的 PostgreSQL(也称为 Postgres)数据库服务器时遇到的挑战。PostgreSQL 是一款强大的开源对象-关系数据库系统。它拥有一个可靠的架构并以可靠性、数据完整性和准确性著称。它运行在所有主流操作系统之上,包括 Linux®、UNIX® 和 Windows®。它与 ACID 完全兼容,并且充分支持外键、连接、视图、触发器和存储过程(支持多种语言)。

请务必 下载 本文使用的样例代码清单。

理想的管理员

在传统的 UNIX 中,PostgreSQL 被进行了重新设计来补充它所依附的操作系统。要最大限度地发掘 PostgreSQL 的价值,所需要的知识超过了一般数据库管理员(DBA)所要求具备的技能。

简单来说,一名合格的 PostgreSQL DBA 需要具备以下背景:

  • 了解关系理论并熟悉 SQL'92, '99, 和 2003。
  • 知道如何阅读源代码,特别是 C 代码,并且能够在 Linux 上编译源代码。
  • 能够管理系统并熟悉 system-V UNIX 或 Linux。
  • 能够维护(如果需要的话)IT 组织中出现的各种典型硬件项目。理解 TCP OS 层,能够将网络分为子网,调优防火墙,等等。

许多 DBA 只具备管理、监控和调优数据库本身的技能。然而,PostgreSQL 在构建时也考虑了 OS 工具。当然,很少有 DBA 精通所有学科的知识,但是拥有这些知识使 PostgreSQL DBA 能够用更少的时间完成更多的工作,而通过其他方式是无法办到的。

访问权限回顾

如果您要了解可能的攻击媒介(attack vector),那么了解数据库角色的作用是非常重要的。首先,您需要通过授予和撤销权限来控制对数据的访问。

角色、授予权限和特权

一个具有默认权限和特权的普通角色的安全性究竟如何?用户帐户可以通过以下命令的其中之一创建:

  • SQL 语句 CREATE USER
  • SQL 语句 CREATE ROLE
  • Postgres 命令行实用程序 createuser

这三种创建用户帐户的方法表现出不同的行为,并导致产生截然不同的默认权限和特权。

对于一个普通角色,典型的用户可以执行下面的操作:

  • 如果数据集群使用如 pg_hba.conf 中描述的默认身份验证策略,那么用户可以访问任何数据库。
  • 在用户可以访问的任何数据库的 PUBLIC 模式中创建对象。
  • 在临时会话中创建会话(临时)对象,比如模式 pg_temp_?
  • 修改运行时参数。
  • 创建用户定义函数
  • 执行在 PUBLIC 模式中由其他用户创建的用户定义函数(只要处理的是用户有权访问的对象 。

一定要清楚用户所具有的权限,但是,了解普通用户在默认情况下不可以做什么也同样重要。普通用户无权执行以下操作:

  • 创建数据库或模式。
  • 创建其他用户。
  • 访问由其他用户创建的对象。
  • 登录(只适合语句 CREATE ROLE)。

超级用户权限和特权

尽管普通用户无权执行被定义为超级用户功能的权限和特权,但是普通用户仍然会引起一些与默认权限和特权有关的问题。

本文将讨论一些可由普通用户操作的攻击媒介。

访问对象

一项极其常见且不太安全的实践发生在将 PostgreSQL 用作 Web 服务器的后端时。开发人员创建普通用户的目的只是让其使用 INSERTUPDATEDELETE 命令来执行数据操作命令。然而,未经授权的操作也可能会被执行,因为 PUBLIC 模式是公开给所有人的。例如,用户可以对这些表进行数据挖掘。甚至还可以对表进行修改:添加规则和触发器、将数据保存到 PUBLIC 模式中的表,随后这些数据就会被收集。

记住,一个被盗用的用户帐户可以对它所拥有的对象做任何事情。

对这类威胁的反击很简单:不要让普通用户访问或创建任何内容。清单 1 展示了如何对一个表提供保护。

清单 1. 对表提供保护
postgres=# SET SESSION AUTHORIZATION postgres;
SET
postgres=# CREATE ROLE user1 WITH LOGIN UNENCRYPTED PASSWORD '123';
CREATE ROLE
postgres=# CREATE SCHEMA user1 CREATE TABLE t1(i int);
CREATE SCHEMA
postgres=# INSERT INTO user1.t1 VALUES(1);
INSERT 0 1
postgres=# GRANT USAGE ON SCHEMA user1 TO user1;
GRANT
postgres=# SELECT I FROM user1.t1;
 i
---
 2
(1 row)

postgres=# SET SESSION AUTHORIZATION user1;
SET
postgres=> SELECT I FROM user1.t1;
ERROR:  permission denied for relation t1
postgres=> SET SESSION AUTHORIZATION postgres;
SET
postgres=# GRANT SELECT ON user1.t1 TO user1;
GRANT
postgres=# SET SESSION AUTHORIZATION user1;
SET
postgres=> SELECT I FROM user1.t1;
 i
---
 2
(1 row)

清单 2 演示了对 PUBLIC 模式的访问被禁止。

清单 2. 禁止角色 user1 创建任何实体
postgres=> SET SESSION AUTHORIZATION postgres;
SET
postgres=# REVOKE ALL PRIVILEGES ON SCHEMA PUBLIC FROM user1;
REVOKE
postgres=# SET SESSION AUTHORIZATION user1;
SET

The error message of "ERROR:  permission denied for schema user1" means that this 
   defensive measure works:

postgres=> CREATE TABLE X();
ERROR:  permission denied for schema user1

访问由其他用户控制的对象

如下面清单 3 所示的攻击媒介假设用户可以访问 PUBLIC 模式;例如,GRANT USAGE ON SCHEMA PUBLIC TO user1。它以下面的假设作为前提:

  • 所有用户在默认情况下都有权访问集群中的任何数据库。
  • 假设集群允许用户创建并操作 PUBLIC 模式中的所有实体。
  • 一个普通用户帐户有权访问系统目录。否则,用户帐户不能正常工作(固有的 PostgreSQL 服务器行为)。
清单 3. 收集有关表的信息
postgres=> SELECT * FROM user1.t2;
ERROR:  permission denied for relation t2
postgres=> insert into user1.t2 values(10);
ERROR:  permission denied for relation t2
postgres=>
postgres=> \d
        List of relations
 Schema | Name | Type  |  Owner
--------+------+-------+----------
 user1  | t1   | table | postgres
 user1  | t2   | table | postgres
(2 rows)

postgres=> \d t?
        Table "user1.t1"
 Column |  Type   | Modifiers
--------+---------+-----------
 i      | integer |

        Table "user1.t2"
 Column |  Type   | Modifiers
--------+---------+-----------
 i      | integer |

尽管可能无法访问表,但是用户仍然可以收集有关表的信息。

清单 4 展示了用户帐户 user1 获取了一组用户帐户及其各自的属性。普通用户自己无法访问密码。

清单 4. 获取用户帐户的属性
postgres=> select * from pg_user;
 usename | usesysid | usecreatedb | usesuper | usecatupd | passwd | valuntil | useconfig
----------+----------+-------------+----------+-----------+----------+----------
postgres | 10 | t | t | t | ******** | |
 user1 | 18770 | f | f | f | ******** | |
(2 rows)

所有用户默认情况下都能够获得集群的定义和模式。

清单 5 展示了一个可以获得有关集群完整定义模式的信息的脚本,方法就是查询系统目录。系统目录可以被超级用户修改或解密,从而减轻了这一威胁。

清单 5. 提取集群范围内的定义
#!/bin/bash
psql mydatabase << _eof_
set search_path=public,information_schema,pg_catalog,pg_toast;
\t
\o list.txt
SELECT n.nspname||'.'||c.relname as "Table Name"
FROM pg_catalog.pg_class c
 JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','')
ORDER BY 1;
\q
_eof_

for i in $( cat list.txt ); do
 psql -c "\d $i"
done

创建和访问由用户定义的函数

函数也分为可信的或不可信的。可信的 过程语言在数据库上下文内执行指令,比如创建表、索引,添加或移除数据,等等。不可信的 过程语言(除了可信的特性外)也能够影响真实的世界,比如列出目录的内容,创建或删除文件,调用系统进程,甚至是创建与其他主机的套接字连接。

清单 6. 向数据库添加过程语言并恢复对 user1 的访问
postgres=# create language plpgsql;
CREATE LANGUAGE
postgres=# create language plperlu;
CREATE LANGUAGE
postgres=# create language plperl;
CREATE LANGUAGE
postgres=> SET SESSION AUTHORIZATION postgres;
SET
postgres=# GRANT USAGE ON SCHEMA PUBLIC TO user1;
GRANT
清单 7. 可信 vs. 不可信过程语言
postgres=# select lanname as language, lanpltrusted as trusted from pg_language;
 language | trusted
----------+---------
 internal | f
 c        | f
 sql      | t
 plperlu  | f
 plperl   | t
(5 rows)

与表不同的是,普通用户帐户在调用某些用户的函数时不需要特殊权限,即使是由超级用户创建的函数也是如此。

清单 8. 调用超级用户的函数
postgres=# SET SESSION AUTHORIZATION postgres;
SET
postgres=# CREATE OR REPLACE FUNCTION public.f1 (
postgres(# OUT x text
postgres(# ) AS
postgres-# $body$
postgres$# select 'hello from f1()'::text;
postgres$# $body$
postgres-# LANGUAGE SQL;
CREATE FUNCTION
postgres=# SET SESSION AUTHORIZATION user1;
SET
postgres=>
postgres=> SELECT * FROM f1();
 x
-----------------
 hello from f1()
(1 row)

下面清单 9 中的函数由超级用户通过 plperlu 创建。它返回目录的内容;user1 可以调用这个函数。一个普通用户可以同时调用可信函数和不可信函数。要应对这种威胁,最好的方法是通过撤销权限来拒绝对函数的访问。

清单 9. 函数被未授权的用户利用
postgres=> SET SESSION AUTHORIZATION postgres;
SET
postgres=# CREATE OR REPLACE FUNCTION public.f2 (
postgres(# OUT x text
postgres(# ) AS
postgres-# $body$
postgres$# # output the root directory contents into standard output
postgres$# # notice the use of the single back ticks
postgres$# $a = `ls -l / 2>/dev/null`;
postgres$# $message = "\nHere is the directory listing\n".$a;
postgres$# return $message;
postgres$# $body$
postgres-# LANGUAGE PLPERLU;
CREATE FUNCTION
postgres=# SET SESSION AUTHORIZATION user1;
SET
postgres=> SELECT * FROM f2();
 x
----------------------------------------------------------------------------

 Here is the directory listing
 total 120
 drwxr-xr-x 2 root root 4096 Aug 29 07:03 bin
 drwxr-xr-x 3 root root 4096 Oct 11 05:17 boot
 drwxr-xr-x 3 root root 4096 Nov 26 2006 build
 lrwxrwxrwx 1 root root 11 Aug 22 2006 cdrom -> media/cdrom
 drwxr-xr-x 15 root root 14960 Oct 12 07:35 dev
 drwxr-xr-x 118 root root 8192 Oct 12 07:36 etc
(1 row)
清单 10. 针对 user1 和组 PUBLIC 提供保护
postgres=# SET SESSION AUTHORIZATION postgres;
SET
postgres=# REVOKE ALL ON FUNCTION f2() FROM user1, GROUP PUBLIC;
REVOKE
postgres=# SET SESSION AUTHORIZATION user1;
SET
postgres=> SELECT * FROM f2();
ERROR: permission denied for function f2
postgres=>

清单 11 展示了信息收集。

清单 11. 获取函数的源代码
postgres=> SET SESSION AUTHORIZATION user1;
SET
postgres=> select prosrc as "function f3()" from pg_proc where proname='f3';

 function f3()
---------------
# output the root directory contents into standard output
# notice the use of the single back ticks
 $a = `ls -l / 2>/dev/null`;
 $message = "\nHere is the directory listing\n".$a;
 return $message;
(1 row)

要隐藏这些源代码:

  • 使用原生语言环境(C、Perl、Python 等)将函数编写为一个模块并存储到主机的硬盘中。随后在 PostgreSQL 中创建一个抽象的用户定义函数来调用该模块。
  • 考虑在一个表中编写源代码并在需要时动态创建函数。
  • 在集群的另一个数据库中编写您的用户定义函数,该函数将由另一个已授权用户帐户通过 dblink 模块调用。

使用 security definer

security definer 使用创建函数的用户所拥有的特权执行函数。因此,用户可以访问在平常状态下不可用的表。

例如,如清单 12 所示,一个包含两个列的表在模式 Postgres 中由超级用户 postgres 创建。普通用户 user1 将使用 security definer 参数调用该函数并根据输入值获得一个值。

清单 12. 创建一个表和函数
postgres=# SET SESSION AUTHORIZATION postgres;
SET
postgres=# CREATE TABLE postgres.t4(x serial,y numeric);
NOTICE: CREATE TABLE will create implicit sequence "t4_x_seq" for serial column "t4.x"
CREATE TABLE
postgres=# INSERT INTO postgres.t4(y) VALUES (random()::numeric(4,3));
INSERT 0 1
postgres=# INSERT INTO postgres.t4(y) VALUES (random()::numeric(4,3));
INSERT 0 1
postgres=# INSERT INTO postgres.t4(y) VALUES (random()::numeric(4,3));
INSERT 0 1
postgres=# INSERT INTO postgres.t4(y) VALUES (random()::numeric(4,3));
INSERT 0 1
postgres=# INSERT INTO postgres.t4(y) VALUES (random()::numeric(4,3));
INSERT 0 1
postgres=# CREATE OR REPLACE FUNCTION public.f4 (
postgres(# IN a int,
postgres(# OUT b numeric
postgres(# ) RETURNS SETOF numeric AS
postgres-# $body$
postgres$# select y from postgres.t4 where x=$1 limit 1;
postgres$# $body$
postgres-# LANGUAGE SQL SECURITY DEFINER;
CREATE FUNCTION

清单 13 表明用户帐户 user1 现在可以访问所需的信息。

清单 13. 未授权角色通过一个函数调用访问表
postgres=# SET SESSION AUTHORIZATION user1;
SET
postgres=> SELECT b as "my first record" FROM f4(1);
 my first record
-----------------
 0.379
(1 row)

postgres=> SELECT b as "my second record" FROM f4(2);
 my second record
------------------
 0.200
(1 row)

破解 PostgreSQL 密码

有效的密码管理是保证 DBMS 安全性的关键。DBA 的职责就是实施一项获得认可的密码策略。密码应当由随机选择的字符组成,这些字符不具备可识别的模式。常见实践表明密码至少应有 6 个字符并且需要经常更换。

PostgreSQL 用户帐户和密码

PostgreSQL 用户帐户安全策略主要与创建和管理用户帐户的 SQL 命令有关:

  • CREATE ROLE
  • ALTER ROLE
  • DROP ROLE

下面的 SQL 语句属于比较旧的用户帐户管理(尽管有效,您仍然应当使用较新的技术管理用户角色):

  • CREATE GROUP
  • ALTER GROUP
  • DROP GROUP
  • CREATE USER
  • ALTER USER
  • DROP USER

密码可以以加密或非加密形式保存。非加密 密码以明文形式存储,并且可以被超级用户读取。密码的加密 包括生成并存储其 MD5 散列,MD5 散列是无法读取的。要在登录时对密码进行验证,需要对密码进行散列化并将其与已经存储在数据集群中的密码进行比较。

下面是一些创建和管理密码的示例方法:

  • 未使用密码创建帐户:

    CREATE ROLE user1 WITH LOGIN;

  • 使用未加密密码创建帐户:

    CREATE ROLE roger WITH LOGIN UNENCRYPTED PASSWORD '123'

  • 修改帐户并分配一个加密过的密码:

    ALTER ROLE user1 WITH ENCRYPTED PASSWORD '123'

由超级用户对目录表 pg_shadow 执行一个 SQL 查询,结果返回用户帐户名及其密码。清单 4 展示了代码。

清单 14. 从目录获得用户的密码
postgres=# select usename as useraccount,passwd as "password" from pg_shadow where
length(passwd)>1 order by usename;

 useraccount | password
-------------+-------------------------------------
 user1 | md5173ca5050c91b538b6bf1f685b262b35
 roger | 123
(2 rows)

清单 15 展示了如何为使用密码 123 的 user1 生成 MD5 散列。

清单 15. 生成 MD5 密码
postgres=# select 'md5'||md5('123user1') as "my own generated hash", 
                  passwd as "stored hash for user1"  
           from pg_shadow where usename='user1';

 my own generated hash | stored hash for user1
-------------------------------------+-------------------------------------
 md5173ca5050c91b538b6bf1f685b262b35 | md5173ca5050c91b538b6bf1f685b262b35
(1 row)

准备好再受一次惊吓了吗?PostgreSQL 几乎没有提供任何机制以实施可靠的密码策略。

可能的安全局限性包括:

  • 超级用户无法对将用于密码的字符限制最小字符数。
  • 尽管针对如何保存密码(未加密的或已加密的 MD5 散列)的配置设置提供了一个默认参数,超级用户无法强制用户使用特定的存储方法。
  • 不存在对用户帐户施加生命周期的机制。
  • 在集群的客户机身份验证配置文件 pg_hba.conf 中,当连接方法不是 PASSWORD 或 MD5 时,控制用户帐户的有效生命周期的机制将变得无关紧要。
  • ALTER ROLE 语句修改的用户运行时参数,以及由超级用户或文件 postgresql.conf 中的默认配置设置的参数,都可以由用户帐户的所有者随意修改。
  • 重命名一个用户帐户将清除该帐户的密码,如果已被加密的话。
  • 无法跟踪谁对用户帐户做了修改或何时做了修改。

一个具有精心系统目录设计的强有力的架构可以为随时保持警惕的 DBA 带来回报。

由于存在各种各样的危害,因此有关用户帐户和密码的安全局限性需要用另一篇文章单独详细介绍。

破解密码

实施一个强类型的密码是值得的,但是没有办法去判断它的强度,除非有人破解它。破解实用程序主要基于两种方法,如下所示。

蛮力攻击(Brute force)
针对散列的系统化测试。它以一些字母为起点,随着攻击的继续不断增加字母的长度。这种方法被建议用于测试较短的密码。
字典攻击(Dictionary attack)
使用一种社会工程(social-engineering)方法。破解程序使用一个单词字典作为起点。此后,不断组合这些单词并针对捕捉到的散列进行测试。这种攻击利用了一种错误的观念,即由有助于记忆的字符串和字符组合组成的长字符串要比由随机字符组成的稍微短一些的字符串更加安全。

根据密码的强度以及使用的硬件,用于解密的时间可能从几秒到几个月不等。

DBA 对识别长度小于 6 个字符的密码比较感兴趣。

命令行实用工具 MDCrack 使用蛮力攻击法测试密码。这个 Windows 二进制工具在 Linux under Wine 上也可以很好地工作。

输入 wine MDCrack-sse.exe --help 将返回配置参数(switch)。其中一些如下面所示:

Usage: MDCrack [options...] --test-hash|hash
       MDCrack [options...] --bench[=PASS]
       MDCrack [options...] --resume[=FILENAME]|--delete[=FILENAME]
       MDCrack [options...] --help|--about

最简单的命令行调用是 wine MDCrack-sse.exe --algorithm=MD5 --append=$USERNAME $MD5_HASH,其中 $USERNAME 为用户名,而 $MD5_HASH 是 pg_shadow 目录文件中的 MD5 散列。

如下所示,MDCrack 可以在会话模式下运行,因此您可以停止一个解密操作并在稍后继续执行。

清单 16. 在会话模式下运行的 MDCrack
# start session
wine MDCrack-sse.exe --algorithm=MD5 --append=$USERNAME $MD5_HASH\
 --session=mysessionfile.txt

# resume using the last session mode
wine MDCrack-sse.exe --algorithm=MD5 --append=$USERNAME $MD5_HASH\
 --resume=mysessionfile.txt

默认字符集为 abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ。如果要测试的密码包含了不属于默认字符集的字符,那么您可以中止进程。您可以将其修改为由字母和数字字符组成的任意组合。例如,您可能希望包含控制字符和标点符号。

调整字符集是在命令行中完成的。变量 $CHARSET 表示将要使用的实际字符集:

wine MDCrack-sse.exe --algorithm=MD5 --append=$USERNAME $MD5_HASH --charset=$CHARSET

下面的示例将修改 Postgres 密码 123。如果忽略前三个字符,那么将得到 MD5 散列值 md5173ca5050c91b538b6bf1f685b262b35。您可以通过以下调用确定密码(提示:对字符串 Collision found 执行 grep 命令)。这次解密用了大约 0.32 秒:

wine MDCrack-sse.exe --algorithm=MD5 --append=user1 173ca5050c91b538b6bf1f685b262b35\
| grep "Collision found"

清单 17 演示了在系统目录 pg_shadow 中解密密码。

清单 17. 解密密码
wine MDCrack-sse.exe --algorithm=MD5 --append=user1 \
`psql -t -c "select substring(passwd,4) from pg_shadow where usename='user1';"` \
| grep "Collision found"

身份验证模型

您已经知道了哪些部分会出现问题,现在让我们看看采取哪些措施来纠正错误。身份验证是一个庞大的主题,因此这里只涉及一些基本的知识。

在 “身份验证” 这一庞大主题下,可以用许多种方法控制对 Postgres 集群的访问:

  • UNIX 域套接字
  • Ident 服务器身份验证
  • LDAP 服务器身份验证
  • PAM
  • Kerberos
  • SSL

UNIX 域套接字

一个 UNIX 域套接字就是一个双向通信管道,它在许多方面与文件相似。服务器创建域套接字,域套接字等待客户机通过文件系统打开文件。一个典型的 PostgreSQL 域套接字如下所示。

清单 18. 典型的域套接字
robert@wolf:~$ ls -la /tmp|grep PGSQL
srwxrwxrwx 1 robert robert 0 2007-10-15 12:47 .s.PGSQL.5432
-rw-------  1 robert robert   33 2007-10-15 12:47 .s.PGSQL.5432.lock

注意,端口号被附加到文件名的末尾。将服务器重新配置为使用一个不同的 TCP/IP 端口也将改变域套接字的名称。

postgresql.conf 配置文件中的三个参数将控制对域套接字的权限:

  • unix_socket_directory(文件 PATH)
  • unix_socket_group(用户组)
  • unix_socket_permissions(默认为 0777)

域套接字的位置因 Linux 发行版而异:

  • PostgreSQL 源代码安装并将套接字放到 /tmp 目录。
  • BSD 将套接字放到 /tmp 目录。
  • RedHat 衍生系统将套接字放到 /tmp 目录。
  • Debian 衍生系统将套接字放到 /var/run/postgresql 且只具有对 postgresqlaccount 的权限。

域套接字有一些比较特别的方面。考虑下面这个例子。

在 robert(超级用户)的主目录中创建一个集群,并对可信性进行了验证。但是,在服务器启动时,域套接字的权限只允许除 robert 以外的用户登录。用户 robert 使用 TCP 登录,但是被域套接字拒绝。然而,robert 在对 nobody 执行 sudo 后,他就可以通过域套接字登录。

这个例子展示了文件权限的多样性,从而减轻由于骇客成为超级用户而引起的破坏。

清单 19. 权限
robert@wolf:~$ initdb -A trust -U postgres ~/data

robert@wolf:~$ pg_ctl -D ~/data/ -l ~/logfile.txt \
-o "-c unix_socket_permissions=007 -c unix_socket_directory=/tmp" start
server starting

robert@wolf:~$ psql -h localhost -U postgres -c "select 'superuser:this works' as msg"
         msg
----------------------
 superuser:this works
(1 row)

robert@wolf:~$ psql -h /tmp -U postgres -c "select 'superuser:this fails' as msg"
psql: could not connect to server: Permission denied
        Is the server running locally and accepting
        connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

robert@wolf:~$ sudo su nobody
[sudo] password for robert:

$ psql -h localhost -U postgres -c "select 'nobody:this works' as msg"
        msg
-------------------
 nobody:this works
(1 row)


$ psql -h /tmp -U postgres -c "select 'nobody:this still works' as msg"
           msg
-------------------------
 nobody:this still works
(1 row)

Ident

Ident 服务器回答了一个简单的问题:哪些用户从您的端口 X 发起连接并连接到我的端口 Y? 在 PostgreSQL 服务器的环境下,它将通知正在尝试登录的用户帐户的 Identity 所在的 DBMS。PostgreSQL 随后获得这个问题的答案,并根据由 DBA 在相应配置文件中配置的规则集允许或拒绝登录。

PostgreSQL Ident 服务器身份验证机制的工作原理是使用主机自己的 Ident 服务器,将 PostgreSQL 用户帐户映射到 UNIX 用户帐户。

下面的例子假设所有 UNIX 用户帐户都已经被映射到 PostgreSQL 中,能够登录到任何数据库,前提是它们在 PostgreSQL 中使用相同的帐户名。如果 UNIX 用户名在 PostgreSQL 服务器中不存在对应的用户名,或者如果尝试使用另一个 用户帐户名登录,那么登录将失败。

假设您已经通过 SSH 连接到主机:ssh -l robert wolf

清单 20. 失败的和成功的登录
robert@wolf:~$ psql -U robert robert
Welcome to psql 8.2.4, the PostgreSQL interactive terminal.

Type:  \copyright for distribution terms
       \h for help with SQL commands
       \? for help with psql commands
       \g or terminate with semicolon to execute query
       \q to quit

robert@wolf:~$ psql -U postgres robert
psql: FATAL:  Ident authentication failed for user "postgres"

-- This works, su to become the UNIX user account postgres

PostgreSQL 使用两个文件管理并控制已通过 Ident 服务器身份验证的用户的所有登录会话:

pg_hba.conf
通过一个文件中定义的记录控制访问。
pg_Ident.conf
当 Ident 服务被用作用户帐户的认证者(authenticator)时发挥作用。比如,METHOD 在 pg_hba.conf 文件中被识别为 Ident
清单 21. 简单配置示例
Example 1: A LOCALHOST connection enforces unix account robert to access database robert 
exclusively.  There is no authentication on UNIX domain sockets.

(pg_hba.conf)
# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD    OPTION
  host    all         all         127.0.0.1/32        Ident     mymap
  local   all         all                             trust
(pg_Ident.conf)
# MAPNAME     Ident-USERNAME    PG-USERNAME
  mymap       robert            robert


Example 2: A domain socket connection enforces unix account robert to access any database 
as pg account robert; unix account postgres can access any database as user robert.

(pg_hba.conf)
# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD    OPTION
  local   all         all                             Ident     mymap
  host    all         all         127.0.0.1/32        trust

(pg_Ident.conf)
# MAPNAME     Ident-USERNAME    PG-USERNAME
  mymap       robert            robert
  mymap       postgres          robert


Example 3: A domain socket connection enforces that unix account can connect to any 
database with its postgres database namesake using the
keyword "sameuser".  pg_Ident.conf is not necessary here. Local host 
connections via TCP-IP are rejected.

(pg_hba.conf)
# TYPE  DATABASE                USER        CIDR-ADDRESS          METHOD    OPTION
  local   template0,template1    all                             Ident     sameuser
  host    all                    all         127.0.0.1/32        reject

Ex4: (all users can connect with their own user names only to the databases postgres 
and robert)

(pg_hba.conf)
# TYPE  DATABASE                USER        CIDR-ADDRESS          METHOD    OPTION
  local   template0,template1    all                             Ident     sameuser

牢记下面的注意事项:

  • 配置修改在您重新加载文件后立即生效,比如重新加载 pg_ctl -D mycluster。
  • 不同的配置设置会引起奇怪的行为。在日志中查看失败的配置消息。
  • 当机器本身被认为是安全的时,将设计并实现 Ident。任何试图进行身份验证的远程服务器都应当被认为是可疑对象。
  • Ident 服务器只用于对本地主机连接进行身份验证。

数据加密

有许多种方式会使您不经意地将自己暴露给 intranet 内部的骇客。

让我们来执行一次嗅探(sniff)。假设您在自己的本地主机 192.168.2.64 上执行以下命令:tcpdump -i eth0 -X -s 3000 host 192.168.2.100 and port 5432

在一个远程主机 192.168.2.100 上,您连接到您的本地主机的 PostgreSQL 服务器,后者已经在侦听端口 5432:psql -h 192.168.2.64 -p 5432 -U postgres postgres。注意修改您的超级用户帐户 postgres 的密码:ALTER USER postgres WITH ENCRYPTED PASSWORD 'my_new_password';

清单 22. 在被嗅探的数据转储中识别密码
16:39:17.323806 IP wolf.56336 > laptop.postgresql: P 598:666(68) ack 470 win 3068
<nop,nop,timestamp 9740679 9589666>
 0x0000: 4500 0078 4703 4000 4006 6d88 c0a8 0264 E..xG.@.@.m....d
 0x0010: c0a8 0240 dc10 1538 6a4f 7ada 6a71 e77c ...@...8jOz.jq.|
 0x0020: 8018 0bfc 1a9d 0000 0101 080a 0094 a187 ................
 0x0030: 0092 53a2 5100 0000 4341 4c54 4552 2055 ..S.Q...CALTER.U
 0x0040: 5345 5220 706f 7374 6772 6573 2057 4954 SER.postgres.WIT
 0x0050: 4820 454e 4352 5950 5445 4420 5041 5353 H.ENCRYPTED.PASS
 0x0060: 574f 5244 2027 6d79 5f6e 6577 5f70 6173 WORD.'my_new_pas
        0x0070:  7377 6f72 6427 3b00                      sword';.

SSH 隧道使用端口转发

IP 转发是一项隧道(tunneling)技术,它可以将 Internet 包从一台主机转发到另一台主机。它允许您的 PostgreSQL 客户机,比如 psqlpgadmin 甚至 openoffice,通过一个 SSH 连接与远程 Postgres 服务器建立联系。

考虑下面的问题:

  • 如果远程 PostgreSQL 服务器上不存在 psql 客户机,会发生什么?
  • 如果需要在您的工作站和远程主机之间上传或下载数据,会发生什么?
  • 如果需要使用数据库客户机来执行 psql 客户机无法很好地执行或根本无法执行的任务,该怎么做?
  • 如何对网络使用隧道,从而使您的团队能够远程连接到防火墙后面的数据库?

这个例子将一个客户机(本地主机)连接到一台远程主机(192.168.2.100)。在工作站的端口 10000 创建了一个代理连接。客户机连接到端口 10000 后,被转发到远程主机的 PostgreSQL 服务器,后者正在侦听端口 5432:ssh -L 10000:localhost:5432 192.168.2.100

添加 -g 参数(switch)允许其他主机利用您的转发连接,这使得连接成为了专门用于 Postgres 连接的即时虚拟专有网(VPN):ssh -g -L 10000:localhost:5432 192.168.2.100.

有关隧道的一些注意事项:

  • 数据库客户机和服务器都被认为它们正在与各自的本地主机通信。
  • 注意要配置文件 pg_hba.conf 以使用 TCP/IP 为本地主机连接设置正确的身份验证。
  • 1024 以内的端口全部由根用户控制。
  • SSH 会话需要在 PostgreSQL/SSH 服务器上有一个已有的用户帐户。

由 SSL 加密的会话

PostgreSQL 中的加密会话要求服务器通过 --with-openssl 参数进行编译。Linux 发行版二进制文件提供了这个功能。诸如 psqlpgadmin 之类的客户机也具有这类必需的功能。

可以使用 pg_config 命令行工具对服务器进行检验,如下所示。

pg_config --configure

针对加密会话配置 PostgreSQL 服务器:

  1. 使用 OpenSSL 命令行工具 openssl 创建一个自签名的服务器密匙(server.key)和证书(server.crt)。
    1. 创建服务器密匙:openssl genrsa -des3 -out server.key 1024
    2. 删除密码 openssl rsa -in server.key -out server.key
    3. 为服务器创建一个自签名的证书:openssl req -new -key server.key -x509 -out server.crt
  2. 将 server.key 和 server.crt 这两个文件安装到数据集群的目录中。
  3. 编辑 postgresql.conf 文件并设置指定的对:ssl = on
  4. 重启服务器。
    清单 23. 成功的 SSL 加密会话连接
    robert@wolf:~$ psql -h 192.168.2.100 -U robert
    Welcome to psql 8.2.4, the PostgreSQL interactive terminal.
    
    Type: \copyright for distribution terms
     \h for help with SQL commands
     \? for help with psql commands
     \g or terminate with semicolon to execute query
     \q to quit
    
    SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)
    
    robert=#

服务器将始终针对已加密的会话请求测试连接。然而,通过编辑身份验证文件 pg_hba.conf,您可以控制服务器的行为。在客户机端,可以通过定义环境变量 PGSSLMODE 来控制客户机(psql)的默认行为:是否使用加密的会话。

共有 6 种模式(其中两种新模式特别针对 V8.4)。

模式描述
disable将只尝试未加密的 SSL 连接。
allow首先尝试未加密的连接,如果不成功,则尝试 SSL 连接。
prefer与 allow 模式相反;首先尝试 SSL 连接,然后尝试未加密连接。
require客户机只尝试已加密的 SSL 连接。
verify-caSSL 连接,并且具有由可信 CA 签发的有效客户机证书。
Verify-fullSSL 连接,具有由可信 CA 签发的有效客户机证书,并且服务器主机名匹配证书的主机名。

例如:导出 PGSSLMODE=prefer.

SSL 证书

SSL 身份验证是指客户机和服务器交换由具有可靠凭证的第三方签发的证书。这个第三方被称为证书权威(CA)。如果客户机或服务器不能从另一方接收到合法的证书,那么连接将被拒绝。

尽管涉及许多细节,但是在 PostgreSQL 上使用 SSL 证书设置身份验证非常简单:

  1. 编辑 postgresql.conf,ssl=on

    服务器端身份验证要求下面的文件必须位于其数据集群中:

    • server.key
    • server.crt(必须由 CA 签发)
    • root.crt(检验客户机身份验证)
    • root.crl(证书撤销列表,可选)

    文件 root.crt 包含一个经过检验的证书列表。其中应该包含可用于您的特定发行版的所有证书,您也可以向该文件中添加证书。

    文件 root.crl 类似于 root.crt,因为它包含一组经过 CA 签名的证书。然而,这些证书属于已被撤销了连接权的客户机。一个空的 root.crl 文件不会影响身份验证过程。

    客户端身份验证要求下面的文件必须位于客户机的主目录 ~/.postgresql 中:

    • postgresql.key
    • postgresql.crt
    • root.crt(检验服务器身份验证)
    • root.crl(证书撤销列表,可选)

    和服务器的 root.crt 一样,客户机的 root.crt 文件包含了由一个可信的第三方 CA 签名的服务器证书的列表。最后一个文件 root.crl 是可选的,用于撤销服务器证书。

    要获取证书,需要客户机和服务器都向 CA 提交了证书请求 client.csr 和 server.csr。证书只有在生成了它们的密匙后才能被创建,如下所示。

    openssl req -new -newkey rsa:1024 -nodes -keyout client.key -out client.csr
    openssl req -new -newkey rsa:1024 -nodes -keyout server.key -out server.csr

    可以使用多种方法执行 openssl 实用工具来获得证书。例如,您可以对它们施加一个生命周期,或者使用自签名证书来生成它们,这样就不需要涉及到 CA。

  2. 您现在可以使用三种方法生成客户机和服务器证书。您可以:
    • 获得由可信 CA 签名的 client.csr 和 server.csr。
    • 通过使用 openssl perl 实用工具 CA.pl 成为 CA。
    • 创建自签名证书并分别将它们添加到服务器和客户机的 root.crt 文件中。
  3. 下面是一组用于 CA.pl 的命令,它们被进行了缩减。查看 CA.pl 手册页获得有关证书请求的更多信息。
    • CA.pl -newca(创建新 CA)
    • CA.pl -newreq(使用私匙创建一个证书请求)
    • CA.pl -signreq (使用您创建的 CA 对证书请求签名)

    对于坚持使用纯开源技术的人,可以在 http://www.cacert.org 找到 “免费” 的证书。

    清单 24. 一个示例证书
    -----BEGIN CERTIFICATE-----
    MIIC9TCCAl6gAwIBAgIJAMuhpY+o4QR+MA0GCSqGSIb3DQEBBQUAMFsxCzAJBgNV
    BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
    aWRnaXRzIFB0eSBMdGQxFDASBgNVBAMTC0NvbW1vbiBOYW1lMB4XDTA3MDIxMjEy
    MjExNVoXDTA3MDMxNDEyMjExNVowWzELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNv
    bWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIG
    A1UEAxMLQ29tbW9uIE5hbWUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKA4
    nX/eBKsPJI1DmtH2wdJE9uZf+IRMUWYrAEDL4F6NEuo2+BsIoOBKS/rrV77Itet9
    kduJCQ6k/z2ouAVb4muXpJALDjJpYBXt9wqZf+2p1n9dqDw1rCWBjXIdhOcA3DDv
    u0Ig1FUfm8GS97evxM5IJBECRnK/5JZroXCRSHcpAgMBAAGjgcAwgb0wHQYDVR0O
    BBYEFElEWNUCV+61itXp86czrDe35vjrMIGNBgNVHSMEgYUwgYKAFElEWNUCV+61
    itXp86czrDe35vjroV+kXTBbMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1T
    dGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQD
    EwtDb21tb24gTmFtZYIJAMuhpY+o4QR+MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
    AQEFBQADgYEAaFzbUmXcWVzqaVeEpZkNwF/eVh110qIUUxXGdeKZGNXIyK67GCUY
    SG/IFkZ/hrGLeqElLrdmU0mHd2Enq2IuvhxnsOVTTickjKospJvlHPYSumkXx0Xp
    zey9PhjLh1chpxNGTATKb8ET8YZvBRrDHl/EMPIjLd62iSR/ugFe8go=
    -----END CERTIFICATE-----
  4. 假设您已经生成了自签名证书,将它们复制到正确的位置并编辑 root.crt。

    客户机证书被保存在服务器的 root.crt 中,而服务器的证书被保存在客户机的 root.crt 中。

  5. 重启服务器后监视日志消息,确定一切配置正常。

服务器的默认行为将仍然使用加密。这可以通过编辑 postgresql.conf 中的名称对 ssl_ciphers='NULL' 并重启服务器禁用。慎重考虑您的决定:将 ssl_ciphers 设置为 NULL 就可以禁用加密。

结束语

在本文中,您了解了有关 PostgresSQL 数据库服务器保护的基本知识。有关这个话题还涉及到许多内容,但是一篇文章所能介绍的内容是有限的。目前有关 PostgreSQL 的介绍还不够丰富。也许,借助您的一点帮助,我们可以更深入地了解 PostgreSQL 安全性。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=458517
ArticleTitle=PostgreSQL 数据库的整体安全性
publish-date=12232009