sql注入学习分享
优采云 发布时间: 2022-09-16 22:18sql注入学习分享
本文为看雪论坛精华文章
看雪论坛作者ID:xi@0ji233
<p mpa-none-contnet="t" style="outline: 0px;font-size: 15px;color: rgb(255, 255, 255);text-align: center;visibility: visible;">一<br style="outline: 0px;visibility: visible;" /><br mpa-from-tpl="t" style="outline: 0px;visibility: visible;" />
WEB框架</p>
web应用一改我们平时常见的 p2p 和 C/S 模式,采用 B/S 模式。随着网络技术的发展,特别随着Web技术的不断成熟,B/S 这种软件体系结构出现了。浏览器-服务器(Browser/Server)结构,简称 B/S 结构,与 C/S不同,其客户端不需要安装专门的软件,只需要浏览器即可,浏览器与Web服务器交互,Web服务器与后端数据库进行交互,可以方便地在不同平台下工作。比如我们玩的英雄联盟就是典型的 C/S 结构的服务,因为有大量图片资源和 3D 模型存储在本地,因此提前安装好客户端就可以方便地与服务器进行交互,如果采用 B/S 结构的话,在我们游戏开始的时候就要与服务器建立连接,下载好各种资源到本地,然后再与服务器进行交互,各种页游均是 B/S 结构。B/S 的优势就是对需要服务一方的电脑要求较低,很容易可以兼容系统上的差异,客户往往只需要安装浏览器便可以享受全部的 web 服务。web 应用会先向我们的浏览器发送前端语言 javascript 或者 html 给浏览器解析执行,我们经过一定的操作之后会向服务器发送请求,然后服务器根据我们的请求做出不同的答复,这个答复还是前端语言形成的网页。服务器会根据什么规则去响应请求,这个就要用到后端语言了,如 php,aspx 等都是常见的后端语言,现在以 php 为主。比如我们实现一个登录页面,那么这个登录肯定是会用到数据库查询操作的,我们将请求提交给服务器之后,后端语言得到我们发送的数据,然后后端语言就会相应地构造 sql 语句去执行数据库查询,并根据查询结果来响应我们那么我们很清晰了,我们负责发送数据,php 构造 sql 语句去查询。首先明白一点,sql 语句肯定我们能控制,因为我输入什么它就要去查什么。我们的输入一定会被嵌入 sql 语句。如果我们在 sql 中能输入任意内容,那我就相当于直接控制了整个数据库。sql 注入的就这么产生了,带来的本质危害也就是数据库信息泄露,如果数据库配置权限过高甚至能让攻击者拿到 shell。
<p mpa-none-contnet="t" style="outline: 0px;font-size: 15px;color: rgb(255, 255, 255);text-align: center;visibility: visible;">二<br style="outline: 0px;visibility: visible;" /><br mpa-from-tpl="t" style="outline: 0px;visibility: visible;" />
sql语言</p>
SQL(Structured Query Language,结构化查询语言)是一种特定目的程式语言,用于管理关系数据库管理系统(RDBMS),或在关系流数据管理系统(RDSMS)中进行流处理。
SQL基于关系代数和元组关系演算,包括一个数据定义语言和数据操纵语言。SQL的范围包括数据插入、查询、更新和删除,数据库模式创建和修改,以及数据访问控制。尽管SQL经常被描述为,而且很大程度上是一种声明式编程(4GL),但是其也含有过程式编程的元素。(from wiki)我们最常用的数据库系统是mysql。
<p mpa-none-contnet="t" style="outline: 0px;font-size: 15px;color: rgb(255, 255, 255);text-align: center;visibility: visible;">三<br style="outline: 0px;visibility: visible;" /><br mpa-from-tpl="t" style="outline: 0px;visibility: visible;" />
Mysql常用函数</p>
数据库基本信息函数
注意,这些函数都无参数且在使用时必须使用 select 关键字输出。
字符串处理函数
在sql中,字符串通常使用一对单引号表示。
sql注入常用函数
<p mpa-none-contnet="t" style="outline: 0px;font-size: 15px;color: rgb(255, 255, 255);text-align: center;visibility: visible;">四<br style="outline: 0px;visibility: visible;" /><br mpa-from-tpl="t" style="outline: 0px;visibility: visible;" />
Mysql内置数据库</p>
Mysql:保存账户信息,权限信息,存储过程,event,时区等信息。
sys:包含了一系列的存储过程、自定义函数以及视图来帮助我们快速的了解系统的元数据信息。(元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等)
performance_schema:用于收集数据库服务器性能参数。
information_schema:它提供了访问数据库元数据的方式。其中保存着关于MySQL服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表的数据类型与访问权限等。
这里看似很复杂,实际上你只需要知道这个 performance_schema 数据库就可以了。对于一个未知的数据库,我们首先需要知道它的数据库名,数据表名,知道表名之后还得知道字段名,这样我们才能使用类似这样的 sql 语句 select 字段名 from 数据库.表名;去泄露数据库的具体信息。我们 navicat 打开这个数据库观察一下有什么表。
看着很多,其实我们只需要关心三个表:schemata,tables,columns,它们分别能爆出数据库名,表名和字段名。我们先看看第一个表 schemata 的具体信息:
可以看到里面的schema_name 字段的值就是我们当前这个数据库系统中所有的数据库的名字,从左边也可以一一对应看到对应的数据库。然后看看第二个表 tables 的信息。因为有点多我们看主要的:
可以看到里面有一个 table_name 字段就是整个数据库系统的所有表名,然后前面的 table_schema 就是这个表对应的数据库名。这里也可以看到我们这个数据库能从中找到 tables 和 schemata 这两个表名,以及其它乱七八糟的在上一张图也都有显示。
<p mpa-none-contnet="t" style="outline: 0px;font-size: 15px;color: rgb(255, 255, 255);text-align: center;visibility: visible;">五<br style="outline: 0px;visibility: visible;" /><br mpa-from-tpl="t" style="outline: 0px;visibility: visible;" />
获得数据库信息的其它方式</p>
在我们有一个 mysql 连接的情况下,我们想查看所有的数据库很简单,一句 show databases;即可解决,但是通常情况下我们这样子输入并不能很好的回显,如果把数据库名作为一条记录输出出来那处理起来会好很多。我们想查看数据库还可以用这种方式:select schema_name from information_schema.schemata;我们对比一下两个指令的结果。
可以看到结果基本就是一样的。然后我们想查看比如说 world 数据库的表名,我们一般先 use world 再 show tables 或者一句话 show tables from world;直接输出表名,但是有 information_schema 这个数据库,我们就能通过这里把信息显示出来。select table_name from information_schema.tables where table_schema='world';
可以看到结果也是一模一样的。剩下的爆字段就不演示了,同理的。select column_name from information_schema.columns where table_name='city';以上的 payload 可以直接在注入的地方加进去,只需要改一下表名和数据库名即可。
<p mpa-none-contnet="t" style="outline: 0px;font-size: 15px;color: rgb(255, 255, 255);text-align: center;visibility: visible;">六<br style="outline: 0px;visibility: visible;" /><br mpa-from-tpl="t" style="outline: 0px;visibility: visible;" />
sqli-labs环境搭建</p>
主要学习的环境还是用的sqli-labs,我是直接在主机上搭建,因为修改代码起来十分方便,一改就能见到效果。但是这么做确保切断了对外界的网络连接,或者心大一点就算了,想着没人会对自己的主机发起进攻的。然后自己再搭建一个 web 服务,能访问就算成功了。
在使用之前在 sqli-labs\sql-connections\目录下的 db-creds.inc 中配置一下自己的用户名和密码,再点击 setup 把数据库先配置好,如果一切OK,那么进入第一关的效果应该是这样的:
<p mpa-none-contnet="t" style="outline: 0px;font-size: 15px;color: rgb(255, 255, 255);text-align: center;visibility: visible;">七<br style="outline: 0px;visibility: visible;" /><br mpa-from-tpl="t" style="outline: 0px;visibility: visible;" />
sql注入详解</p>
在对一个 ctf 打 sql 注入的时候,我们第一步就是要寻找注入点。怎么寻找注入点呢,因为后端源码我们都是不知道的,所以我们只能通过抓包的方式观察所有能提交的参数进行 sql 注入的测试。找到注入点之后我们还需要判断注入的类型。大体的注入分两类,一类是有回显的注入,另一类是没有回显的注入。一般情况下我们优先考虑有回显的注入,因为时间成本比较低,那么我们先来看看有回显的注入吧。
有回显的注入
什么叫有回显?查询到的数据库信息会直接显示出来,你能看到的就叫有回显,反之则是没有回显。有回显的注入有以下类型:
1、联合查询的注入:通过union关键字泄露数据库信息。
2、堆叠注入:通过重新执行一个 sql 语句的方式泄露数据库信息,或者直接增删改查数据库。
3、报错注入:通过一些特殊的函数报错把信息显示出来。
4、二次注入:咕咕咕。
联合查询的注入
利用要求:有回显假如你是 admin 登录之后,它页面可能会显示 hello,admin。那么这个 hello 后面就是一个回显的点,这里就可以用来泄露其它信息。这里需要怎么理解呢,假如它在登录的逻辑是这样写的:select username,passowrd from data.user where username='$input_username' and password='$input_password';
然后我们判断你的账号密码是否正确就主要看它是否能查找到记录,如果找到,那么我选取这条记录的第一个记录的 username 字段,然后输出这个,就达到了它成功登录了什么账号,我输出那个账号的目的了。至于上面为什么说是第一条记录呢,这里你需要这么看:select 的返回结果可能有很多,而不管它返回了一条还是多条它都是一个数据集,是个二维的表。因此选择第一条记录是开发人员默认会加上的,此时我只需使得前面的语句查询失败(返回空数据集)并选取其它内容用 union 合并这个数据集,并把这里的其它内容替换成我想知道的内容,比如它的数据库名,表名,然后它这里就会原样输出这些信息了,我们就知道了。这里需要知道 union 是合并两个数据集的,因此两个数据集的宽度(字段数)必须一样,数据类型可以不一样,返回 php 处理之后都会变成字符串类型其实。这里我们拿刚刚搭建的环境的第一关来做测试:
这里我们不需要寻找测试点了,它这里已经贴心地提醒我们用 get 传一个 id 参数进去了,因此我们先试 1。
可以看到我输入一个 1 它直接贴心的告诉了我们账号和密码是什么,这里显示的账号和密码就是回显的点。我们再测试这个参数是否能注入,最简单最直接的方法就是打个单引号或者双引号进去。
可以发现数据库报错,那就说明这个参数是可以注入的。因此我们用刚刚提到的方法,先另前一个语句查询失败(空数据集),然后再 union 上一个数据集,这个数据集是我们任何我们想泄露的信息,首先我们假装对数据库一无所知,我们第一步就是要知道这里有多少数据库,分别什么名字。根据报错信息可以略微猜测一下它的写法select username,password from xxx.yyy where limit 0,1我们先用引号闭合前面的参数,然后后面加上一个 and 1=0让前面的数据集必为空,然后再 union select 1,2--+,这里需要测试参数的个数,因为你不知道前面有几个字段,不过这里可以姑且先猜个 2,因为目前看来就找了账号和密码嘛,最后用--+去注释后面的单引号。结果发现数据库报了这个错误:The used SELECT statements have a different number of columns,这个也不难看出来是因为 union 前后的数据集含有不同的列数,也就是字段数不一样,所以这里不是两个,那我们换成 3 个参数再看看,如果不行就接着换,知道不报这个错误为止。
这里可以看到结果出来了,那么前面是有三列的,并且账号在第二列,密码在第三列,第一列大概率是这个 id 了。那么我们就朝着这几个回显的地方去改参数,比如我想知道数据库名,就用前面的方法。但是这里需要知道一点,那就是回显的地方这里只能存在一条记录,如果存在多条记录将报错。也就是说我可以把 2 替换成 select xxx from zzzx.yyy 但是必须保证结果集只能含有一条记录一个字段,否则会报错。
一个字段没有问题,但是一条记录的话,你会想到 limit,可以,但是太慢了,如果数据记录很多一条一条打要累死人,这里我们用到之前讲过的聚合函数 group_concat,聚合函数会把所有记录整合成一条记录,并且我们还能一次输出多条记录的信息,那简直一举多得了。我们开始报数据库名吧select schema_name from information_schema.schemata
可以看到我们爆出了当前数据库名和所有数据库名,这里需要注意,我们在替换为语句的时候,语句一定要加上括号,不然它的 sql 会分析失败。然后我们爆一下 security 数据库的信息,先爆表名,其实只需要替换一下就可以了:select group_concat(table_name) from information_schema.tables where table_schema='security'
我们主要收集一下用户信息吧,所以看看 users 数据表的内容,我们先获取字段名,一样一样地往上套就完事了:select group_concat(column_name) from information_schema.columns where table_name='users'
然后我们这里我们就看到了所有的字段名,我们这里点到为止,把所有用户名和密码爆出来就结束吧。select group_concat(username) from security.users和select group_concat(password) from security.users
好,到这里我们就把数据库的信息成功获取到了。
总结
我们可以看到联合查询注入十分方便,几步到位可以把数据库全部泄露出来,但是利用条件一般比较苛刻,需要有回显点才能实现。
堆叠注入
堆叠注入的原理就是使用引号隔开前一个查询语句,再自己书写另外的 sql 语句以此达到任意执行 sql 语句的目的。由于结果很难回显,我们一般这个用的不多,因为我们主要还是获取信息为主,而不是要去修改它的数据库。这个演示我们用 buuctf 里面的一道题吧,是来自2019强网杯的一道题目。
先不管它怎么说,有提交窗口先正常提交看看它原本的业务逻辑。
看这个输出格式,应该也是从数据库里按照一个应该是 id 字段查询,查询结果为两个字段,然后用 var_dump 输出第一条记录的信息,然后按照国际惯例加个分号看它是否报错。
报错了说明有注入点。我们当然还是先试试联合查询注入,用1' union select 1,2--+,然后我们看到它回显了。
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
它过滤了很多关键字导致我们没办法直接使用联合查询注入,并且正则后面的/i 表示大小写全匹配,那看来它是不想让你用联合查询注入,我们不妨先试试堆叠注入。我们可以先去 mysql 连接里面自己试试堆叠注入,比如我先实现一个逻辑,这个逻辑仅仅是查询每个数据库的表,那么数据库参数可控,我们就是这么写 sql 语句的:select table_name from information_schema.tables where table_schema='$input_database';。
可以看到随便输入一个数据库可以实现功能,那么我们让$input_database=1';show databases;--,经过拼接之后形成了:select table_name from information_schema.tables where table_schema='1';show databases;--';。可以看到我们在参数中输入了其它的 sql 语句。那我们看看结果如何呢?不出意外地执行了我们输入的 show databases 指令。
所以你也就清楚了堆叠注入是怎么一回事,我们试试看,一般题目里面堆叠注入都没有很好的回显,但是这题它有,至于为什么能有我们等会可以分析一下它题目的源码。
再通过 show tables 我们可以发现有两张表 14514 和 words。然后我们下一步可以用 show columns from table_name 的方式去显示表中所有的字段名。先看看 words 表,发现有 id 和 data 字段,这里大胆点猜测,我们应该是根据 id 去查询 data。它的 sql 语句大概是 select data from supersqli.words where。这里一个烫芝士注意一下啊,就是当数据库名或表名或列名可能引起歧义的时候,需要使用反引号将其包裹。比如你 select 1,2,3 我并不知道你想找的是 1,2,3 三个数值还是这 1,2,3 是列名。那么为了消除这个歧义我们在这个时候使用反引号。
select `1`,`2`,`3`
上述写法就是表示 1,2,3 代表列名,反引号在键盘上数字 1 的左边。这里因为是全数字,所以我们用反引号才能显示出它所有的列,我们可以看到只有一个 flag 列。那 flag 应该是在里面,我们需要查询出它,这里就可以用到堆叠注入的另一种姿势:预编译。我们也先来看看预编译的一般用法:
set @sql='show databases';prepare ext from @sql;execute ext;
可以发现它成功执行了 show databases,你可能会觉得一举两得了,但是这对于我们绕过 WAF 还是很有帮助的,它不让出现 select 这个单词的任意大小写形式,我们就用前面的字符串拼接函数 concat 就可以不出现 select 单词但是能执行 select 语句。我们还是在这个 cmd 里面去运行。
可以看到,我们利用 concat 函数和预编译的方式在全语句没有出现过 select 的情况下使用了 select 语句才能干的事。因为在 php 里面,执行语句的时候才会产生一个进程去执行 sql 语句,语句结束进程也就结束,如果我先 set @sql='xxx'那么再次查询不会保存这个变量的结果,这里就需要把多条语句整合成一条,这也是堆叠注入特有的一个优势吧。我们的payload如下:
1';set @sql=concat('se','lect flag from `1919810931114514`;');prepare ext from @sql;execute ext;
我们打进去的时候发现WAF还有一层检测。
strstr($inject, "set") && strstr($inject, "prepare")
这个很好绕过,因为这个函数它判断大小写的,我们对这两个关键字随便一个字符大写即可绕过,我们最后的payload就是:
1';Set @sql=concat('se','lect flag from `1919810931114514`;');Prepare ext from @sql;execute ext;
成功获得 flag。堆叠注入还有一个很厉害的姿势就是修改数据库,但是请注意不要删库,因为这样的话你可能就拿不到 flag。如果拿完 flag 再把 flag 删了,如果环境你专用你随便玩,公用的话就容易被别人喷了,万一环境不能重置,那你不是直接没了。第二种方式是把装 flag 的表改成本来的逻辑查询的表,也就是 words 表。我们把那个表的名字改成 words,然后它可能是根据 id 查询的,我们就把 flag 列改成 id 也许它是根据 words 查询的,我们到时候改一下就好了。先写出我们这几步的 sql。
rename table `words` to `111`;rename table `1919810931114514` to `words`;alter table `words` change `flag` `id` varchar(100);
如果成功的话我们只需要一个万能密码即可查出所有原 flag 表的所有记录。我们的payload就是
1';rename table `words` to `111`;rename table `1919810931114514` to `words`;alter table `words` change `flag` `id` varchar(100);
执行之后我们使用 1' or 1=1--+得到 flag。
堆叠注入为什么可以实现,下面就到了我们的源码环节了,没有官方的源码,只是从网上寻找到了差不多类似的,复现出来也基本一致。
<p> easy_sql 取材于某次真实环境渗透,只说一句话:开发和安全缺一不可 姿势: