上个周,Postgresql团队对它旗下的著名开源RDBMS发布了一个安全方面的更新,一切看起来都像往常一样……
但是这条更新却引起了人们的极大关注,主要是因为在补丁发布前一周,Postgres开发者在pgsql-hackers邮件列表(
http://www.postgresql.org/message-id/14040.1364490185@sss.pgh.pa.us)中发布了一个令人震惊的消息,说即将发布的新版本中包含了修复某个安全问题的一些补丁,但这些补丁却又引进了极其严重的安全漏洞以至于在更新包出来之前要临时关闭大众对公共Git库的访问权限。
我不知道你是怎么想的,但是就从这个极其机密的安全漏洞的严重程度来看,竟然到了让一个著名的开源项目采取临时闭源的方式来防止细节泄露的地步,就这一点不免让我们感到一点小小的兴奋。
之后到了星期四,补丁出来了,代号
CVE-2013-1899。打眼一看好像是DoS漏洞,因为其至少具备了remote,unauthenticated的特点。并且在某些非常特定(经过认证的用户的用户名与数据库名相同)的情况下还可导致本地提权。
因此我们接下来的目标就是看看能否将这个DoS漏洞提升至RCE(远程代码执行)攻击或者至少是尽可能地接近这个目标……
层层深入
我们来分析一下这个漏洞。首先,这个漏洞的独特之处就是当一个客户端以指定的数据库进行登录时,若在数据库名前加
'-'(连字符),这会导致postgre服务器将数据库名解析为命令行的选项(flag)并传递给处理当前连接的服务器实例(所有这些解析、执行操作都
是在身份验证之前进行的)。换句话说,任何攻击者在未经过身份验证的情况下都可以对处理连接会话的postgre服务器传入任意的命令行选项。
在Posgres的使用者中,大家认为最危险的参数便是“
-r”选项,可以将服务器的所有输出写入到指定的文件中。通过使用这个选项,攻击者可以将运行posgres的用户的那些具有可写权限的文件(像配置文件
或数据库文件)作为参数传入,之后数据库输出的垃圾信息将会破坏这些文件从而导致拒绝服务攻击。
除了"-r"选项其他的命令行参数还是很少用到的。但是还有一个也是比较有用的,那就是"-c"选项。它允许我们设定一个更长的选项列表来作为运行时参数,详情请看
“Chapter 18″ of the PostgreSQL manual(实际上,大多数其他的命令行参数都是运行时参数的简化版)。
首先看一下几个有趣的选项:
- shared_preload_libraries – 指定在服务器启动时预先载入的一个或多个共享库
- archive_command – 指定用于归档完整WAL(Write-Ahead Logging)文件字节码的Shell命令
- log_line_prefix – 将指定的字符串作为每行日志的前缀并以printf风格输出
- dynamic_library_path – 当动态模块未在LOAD或CREAT FUNCTION命令中指定时,用此选项指定
- local_preload_libraries – 指定在连接开始时预载入的一个或多个共享库
进一步分析:
- shared_preload_libraries – 攻击者可以用来指定预载入具有钩子函数的库文件,但是需要对文件系统有写权限
- archive_command – 指定当write-ahead log归档时会调用的shell命令。不幸的是这个命令还需要同时设置其他几个参数才能触发运行,在经过进一步的研究后,我们只能传入一个命令行选项。
- log_line_prefix – 起初我以为可以将此选项与"-r"选项组合使用,让我们在每行日志前加入一些有用的信息,从而将某些信息写入到任意的文件中。不幸的是,这个选项一次也只能传一个参数
- dynamic_library_path 或 local_preload_libraries – 类似于shared_preload_libraries,但限制更多。
嗯,貌似没有一个选项能够让我们利用来进行远程代码执行,因此让我们先回到最初的想法来——使用"-r"选项将垃圾信息写入到任意的文件中。首先看一下用"-r"选项指定一个输出文件时我们会得到什么样的输出:
1 | psql --host=192.168.1.100 --dbname= "-rtest.out" |
test.out:
1 | 2013-04-08 14:24:45 EDT FATAL: no pg_hba.conf entry for host "192.168.1.100" , user "test" , |
2 | database "-rtest.out" , SSL on |
3 | 2013-04-08 14:24:45 EDT FATAL: no pg_hba.conf entry for host "192.168.1.100" , user "test" , |
4 | database "-rtest.out" , SSL off |
好的,我们得到了一个以日期/时间戳开头后跟典型的日志内容的输出,接下来是客户端IP(这个无法做进一步的更改)、连接的用户名以及所谓的dbname。要是我们指定用户名会怎样?
1 | psql --username= "test123" --host=192.168.1.100 --dbname= "-rtest.out" |
test.out:
1 | 2013-04-08 14:25:50 EDT FATAL: no pg_hba.conf entry for host "192.168.1.100" , user "test123" , database "-rtest.out" , SSL on |
2 | 2013-04-08 14:25:50 EDT FATAL: no pg_hba.conf entry for host "192.168.1.100" , user "test123" , database "-rtest.out" , SSL off |
看来至少我们能随意设置写入日志中的用户名……在对输出的日志经过一番分析及测试后发现,
我们可以向任意文件中写入63字节的数据(除去开头和尾部的那些不重要的日志数据)。
那么我们怎样利用这个漏洞呢?首先,想要执行某些代码我们得先找到一个中庸的解析器(lazy
parser),它在遇到不识别的日志数据时或者忽略或者抛出警告但是能继续执行我们所提供的代码。通常web脚本语言是一个比较好的选择,例如php,
我们可以将代码封装到<? ?>标签内传给解释器进行解析。 因此,如果装有像php脚本解释器之类的web服务器与Postgresql服务器位于同一台主机时,我们可以将代码写到文件中并通过web来触发它。但
是有一个问题需要考虑,web目录需要人人可写权限(world
writable)(或者至少对运行posgres进程的用户来说是可写的),此外使用"-r"选项来创建新文件时其默认权限是0600,因此即使能新建
文件也无法执行。那么,我们需要找到一个已经存在、具有执行权限并且我们有权限写入的文件,这样我们就可将代码附加到这个文件中。
由于服务器可以像客户端发回"could not open"和"Permission denied"的消息,因此你可以通过暴力穷举的方式来搜索服务器上的文件或目录。 |