SQL注入漏洞
1.SQL语句基础知识
一个数据库由多个表空间组成,sql注入关系到关系型数据库,常见的关系型数据库有MySQL,Postgres,SQLServer,Oracle等
以Mysql为例,输入 mysql-u用户名-p密码 即可登录到MySQL交互式命令行界面。
既然是数据的集合,我们要想操作这些数据就需要有一定的媒介和手段。比如,可以选择用图形化的工具(如Datagrip、Navicat等),也可以直接使用数据库管理工具自带的命令行(如MySQL自带了一个命令行的界面,Postgres亦然)。我们要与这些界面进行交互,去管理数据的话,就需要一种语言或者说一种规范去定义,这就是sql语句
sql语句的基本操作:
#创建表users,其中包含两个字段id、name
create table users (id int (11),name varchar(256));
#查看当前数据库下的所有表名
show tables;
#向 users表中插入数据
insert into users values (1,'admin'), (2,'test');
#查询users 表中的所有数据
select * from users;
#查询id为1的数据
select * from users where id=1;
#修改数据(将name为admin的数据改为name为abc)
update users set name='abc' where name='admin';详细解释看我另一篇文章SQL 语句的详细解释-CSDN博客【代码】SQL 语句的详细解释。
https://blog.csdn.net/2401_88743143/article/details/145594691?sharetype=blogdetail&sharerId=145594691&sharerefer=PC&sharesource=2401_88743143&sharefrom=mp_from_link
2.SQL注入漏洞的基础概念
代码解释
include "db.php";
:使用include
语句引入名为db.php
的文件。通常db.php
会包含数据库连接、操作相关的函数或配置信息,这里引入它是为了后续能使用其中定义的数据库操作功能(如代码中的select
函数)。$id = $_GET['id'];
:通过$_GET
超全局变量获取 URL 中传递的名为id
的参数值,并将其赋值给变量$id
。$_GET
用于获取通过 GET 方法提交的数据,例如在 URL 中形如example.php?id=1
的形式,1
就会被获取到赋值给$id
。$result = select("select title, content from contents where id=$id;");
:调用名为select
的函数(此函数应在db.php
中定义),传入一个 SQL 查询语句select title, content from contents where id=$id;
。该 SQL 语句的作用是从名为contents
的数据库表中,查询id
字段值等于$id
的记录的title
和content
字段内容。查询结果会被存储在$result
变量中。echo(json_encode(isset($result[0])? $result[0] : []));
:首先使用isset
函数判断$result
数组的第一个元素(索引为0
)是否存在。如果存在,就将其作为参数传递给json_encode
函数,将其转换为 JSON 格式的字符串;如果不存在,就将一个空数组[]
转换为 JSON 格式字符串。最后使用echo
函数输出转换后的 JSON 字符串。这段代码存在 SQL 注入风险,因为
$id
直接拼接进 SQL 语句中,如果用户可控的$id
传入恶意内容(如1; DROP TABLE contents;
),可能导致数据库被恶意操作。
注意,如果输入“id =- 1 or1=1;#”,则查询语句为“select title,content from contents where id =- 1 or 1=1;#;”,语句中的where判断子句部分可以分成两个条件“或”来看,满足其一即可。首先来看id =- 1,id字段显然不会存在为负数的记录,则前半部分不成立;后半部分为1=1,永远成立,此条件可以把所有记录匹配查询出来。“;#”是为了将语句后面可能出现的多余查询条件给注释掉,避免造成利用失败。
3.手工注入方法
数据库
先用union(因为SQL语句union要求联合起来的前后请求列数必须相等,这样才能正确查询出结果。)探测sql查询语句有几行,利用“-1 union select 1;#"“-1 union select 1,2;#”等进行探测(
只要有形如title为1,content为2等返回不为空的结果,就说明成功探测到了列数。),
控制联合查询中1,2任意一个位置为回显位,即可回带数据。例如:
获取数据库当前的版本,传入“id =- 1 unionselect 1,version();#",
获取当前连接所在的数据库的名称,传入“id =- 1 unionselect 1,database();#”,
获取当前连接的用户,传入“id =- 1 union select1,user();#。
如果想获取数据库系统中所有数据库的名称列表,显然此处不能使用“show databases;”,需要想办法从MySQL之前自带的数据库中获取信息,如information_schema,这个数据库储存了数据库的信息,我们来尝试获取其中的schema_name字段的数据。
为什莫不用show databases;
在正常的MySQL客户端环境中,“show databases”能直接展示所有数据库名称,这是客户端工具和MySQL服务端交互的正常流程。但在SQL注入时,我们注入的语句通常是要嵌在其他SQL语句里执行的,而且可能受到各种限制和上下文环境的影响。很多时候,单纯使用“show databases”并不能按预期那样获取到所有数据库名称列表,并且它的输出格式等也不一定适合注入场景下我们对数据提取和处理的需求。
information_schema
MySQL自带的数据库,其中有一个很重要的数据库叫“information_schema”。这个数据库里存储了很多关于MySQL系统的元数据信息,包括所有数据库的结构、表结构、字段信息等等。如果我们想在SQL注入时获取所有数据库名称,就可以从“information_schema”数据库里的特定表(比如“schemata_names”表)中去查询。这个表中记录了所有数据库的名称,我们通过合适的查询语句,比如
“select schema_name from information_schema.schemata_names;”,就能获取到所有数据库的名称列表。
如果我们想要获取其他数据库的名字,一种办法是用limit子句。传入“id =- 1 union select 1,schema_name from
information_schema.schemata limit 1, 1;#",格式为limit<起始位置>,<查询条数>从0开始,则1就是第二条数据。
(id=-1 UNION SELECT 1, schema_name FROM information_schema.schemata_names LIMIT 1, 1;#)
但这样就意味着我们需要依次修改起始位置来获取数据,非常麻烦。这时我们就需要使用到另外一种语法,传入“id=-1 union select 1,group_concat (schema_name) from information_schema. schemata;#”,使用group_concat把这个字段所有的查询结果用逗号拼接到一起
数据表
例如查看ctf-training 这个库里的表有什么内容,需要从information_schema这个库里的表tables去查找相应的信息
(1)先找表名。传入“id =- 1 union select1,group_concat(table_name) from information_schema.tables where table_schema='ctftraining';#",查询ctftraining这个库中所有表的名称并拼接返回,
(2)接着union select需要知道表里字段的名称才能做单字段的查询,如果直接输入 ,很大概率会因为union select前后列数不一致导致查询出错。所以我们还得获取表里的字段名,这里我们就从information_schema的columns表进行查询。比如想查询FLAG_TABLE这个表的字段名,就传入“id =- 1 union select 1 , group_concat( column_name ) from information_schema.columns where table_schema='ctftraining'and table_name='flag';#"
(3)拿到了字段名flag,我们就可以来尝试拉取数据了,传入“id =- 1 union select 1, flag from ctftraining.flag;#",
4.注入利用方式分类——布尔盲注利用
数据库有记录,就返回res=1,否则就返回res=0,所以需要利用好仅存的这一点1和0的结果,获取数据库里的数据,尝试利用username参数,传入username=1' or 1=1;#,后端查询的语句就相当于变成了:select password from admin where username =' 1 ' or 1=1; #
or 1=1永远为真,则永远会查询到记录,所以请求会返回1,对此进行利用,如果我们能在此处进行一个判断,比如判断某个数据字段第几位上的字符是否为'1',如果成立则为真,不成立为假。写成语句如下:select password from admin where username='1'or if((substring(version (),1,1)='1'),1,0);#
代码解释
if
函数:if
是 SQL 中的条件判断函数,其语法为if(condition, value_if_true, value_if_false)
。意思是如果condition
条件为真,则返回value_if_true
;如果为假,则返回value_if_false
。在这条语句里,if((substring(version (),1,1)='1'),1,0)
就是判断substring(version (),1,1)='1'
这个条件是否成立,如果成立就返回1
,不成立则返回0
。substring(version (),1,1)
:
version()
是 SQL 中的一个函数,用于返回当前数据库的版本信息,例如5.7.33
等。substring(str, start, length)
是字符串截取函数,用于从字符串str
中截取从start
位置开始、长度为length
的子字符串。所以substring(version (),1,1)
就是截取数据库版本信息的第一个字符。- 总的说就是:就是判断version()从第一位开始的一位字符是否为'1',是则返回1,否则返回0,得出version()的值
5.SQL注入利用方式分类——时间盲注利用
没有回显就利用返回时间快慢确定字符,用MySQL中sleep函数。例如:
传入“username=1' or if((substring (version(),1,1)='1'),sleep (10),0);#”,也就是当version()第一位为1时,就会进入到第二个参数里,会触发sleep(10),如果不符合就会返回0。sleep(10)则会让语句等待10s后再返回
补充资料
我在之前的文章zyNo.18-CSDN博客 有一些关于布尔和时间盲注的相关知识
经典例题:[CISCN2019]Hack World-CSDN博客