SQL注入(宽字节注入、长字符截断)

本文仅记录SQL注入漏洞学习过程中的基本知识。
SQL注入漏洞是完全可以避免的安全问题。从其形成原因来看,就是用户输入的数据(可能带有恶意)被SQL解释器当作代码执行,从而获得更多数据库的信息或者获得更高的权限。从原理上来说,SQL注入可以分为两大类,数字型注入;字符串型注入;(《web安全深度剖析 张炳帅》)。

数字型注入

输入类型为int类型,如ID,年龄,页码等。一般来说,数字型注入只存在于弱类型语句中,如:PHP,ASP,python,而对于C,java等强类型语言则不存在。弱类型语言会自动判断变量类型,从程序员角度来说,这提供了很大的便利,但从安全角度来说,也带来了安全隐患。比如,id=8,弱语言会将id判断为int类型;而id= 8 and 1=1,则会判断为字符串型string。这为注入提供了可能。对于强类型语言,由于必须提前声明变量类型,赋值类型错误则会抛出异常。

字符型注入

输入类型为字符串时,称为字符型注入。字符型与数字型的最大区别就是字符型需要使用单引号来闭合。如:

select * from table where id = 8  //数字型注入
select * from table where usename = 'admin' //字符型注入

对于字符型注入,关键的是输入的闭合及多余部分的注释,对于Mysql来说,共有三种注释方式:

(1) select * from stu -- 注释  //注释时,–与注释之间必须要一个空格,注意stu与--之间必须有空格,--与注释之间也必须有空格
(2) select * from stu #注释  //使用#对格式要求不高,stu与#,#与注释之间均不需要空格,当然为了格式,也可以留有空格
(3) select * from stu /*注释*/  //(1)(2)为单行注释,(3)为多行注释,留不留空格均可,使用此需要注意

严格来说,数字也是字符串,查询时where id = 8 与 where id = '8’均可。通常在数字查询时,不加单引号。

更细致的分类,如 POST注入、cookie注入、盲注、延时注入等,均是根据注入位置不同,对字符型注入和数字型注入的具体叫法。

POST注入:注入字段位于POST数据中;
Cookie注入:注入字段位于cookie数据中;
延时注入:使用数据延时特点注入;
搜索注入:注入处为搜索地点;
base64注入:注入字段经Base64编码;

mysql中的注入问题:

注释问题

由于使用的是kali linux,默认安装的是mariaDB,两者基本兼容。关于两者的历史渊源及对比,文末给出参考链接。对于mysql中三种注释方式,多行注释/* */需要补充说明:

select * from user;  #正常读取表user的所有列内容
select user from user;  #读取user列的内容
select user /* */ from user;  #此处/* */为注释,对语句不产生影响,mysql语句以‘;’为语句结束标志
select user /*!,password*/ from user;  #此处并不是注释,注意'!'的使用

SQL注入(宽字节注入、长字符截断)_第1张图片
SQL注入(宽字节注入、长字符截断)_第2张图片需要特别注意的是 **select user /!,password/ from user;**此语句中/!,password/ 被正常执行,’!'具有特殊意义,它代表着条件判断,其后如果加上条件,则必须为版本号,如
/!100324,password/ 表示如果版本号高于或等于10.03.24时,其后的语句才会被执行。如果像上述不加条件,则语句直接执行。如下:

select version();  #查询版本号,版本号为10.3.24
select user /*!100324,password*/ from user;  #版本号需要变为10.03.24
select user /*!100325,password*/ from user;  #当版本号高于10.03.24时,语句不会执行

SQL注入(宽字节注入、长字符截断)_第3张图片
SQL注入(宽字节注入、长字符截断)_第4张图片

宽字节注入

在PHP5.4以前,配置文件php.ini中,存在设置:magic_quotes_gpc ,默认情况下,其处于开启状态(On)时,使用 G E T 、 GET、 GETPOST、$Cookie,会对特殊字符单引号(’)、双引号(")、反斜线()、空字符( NULL)进行转义防止SQL注入危险。但是在PHP5.4版本后,删除了这一选项,即默认其为关闭状态(Off),则需要通过如addslashes,mysql_real_escape_string等函数对危险字符进行转义。
如下,php版本为7.4,默认关闭状态,在test.php中输入以下语句:

 
echo "

input: ". $_GET['id'] ."

"
; ?>

在firefox中输入网址:https://127.0.0.1/test.php?id=%3c%27,结果如下:
SQL注入(宽字节注入、长字符截断)_第5张图片
可见,其并没有对危险字符()进行转义。关于apache+php的搭建,这里不做详细叙述了。

宽字节注入原理 :所谓的宽字节,实际上就是两字节,在转换编码的过程中,将单字节的编码结合其后的编码看做两字节,即 将两个字符误认为是一个宽字符而解码
下面结合具体的例子对其说明:
在文件夹相应位置有t.php文件:

 
header("Content-type: text/html; charset=gb2312");
//header("Content-type: text/html; charset=utf-8");
if (!empty($_GET['id']))
    {
     
        $input  = addslashes($_GET['id']);
        //addslashes()返回在预定义的字符(即上述四种特殊字符)前添加反斜杠的字符串。
    }
echo "

input:". $input."

"
; ?>

补充说明:utf-8为单字节编码,GBK(gb2312)为宽字节编码,如果采用utf-8编码,则不会出现宽字节注入。
打开firefox浏览器,输入:https://127.0.0.1/t.php?id=%27,出现以下返回结果:
SQL注入(宽字节注入、长字符截断)_第6张图片
此时,单引号被转义,输入:https://127.0.0.1/t.php?id=%d5%27。此时:

发现单引号并没有被转义,就此分析:
(1).浏览器输入id=%d5%27
(2).通过GET方式获取到数据,进行判断输入数据中是否存在预定义字符,发现存在单引号,愉快的将其转换为’, 同时对其编码,此时id=%d5%5c%27(\对应的编码为%5c),准备返回数据
(3).接收端对其解码,首先对%d5进行GBK解码,发现无对应字符,再对%d5%5c解码,发现正好对应一个汉字,快乐的解码了,结果留下%27,被解码成单引号,相当于没转义

相应的编码如下:(编码解码工具)
SQL注入(宽字节注入、长字符截断)_第7张图片
SQL注入(宽字节注入、长字符截断)_第8张图片
SQL注入(宽字节注入、长字符截断)_第9张图片
注:mysql及php默认采用的是utf-8编码,即单字符编码,此时不会产生宽字符注入;在php中使用GB2312、GBK、GB18030、BIG5、Shift_JIS等编码方式时,即采用宽字节,此时要注意安全问题。

mysql长字符截断

mysql中有一个环境变量配置sql_mode,定义了mysql应该支持的sql语法,数据校验等,可以通过以下方式查看当前数据库使用的sql_mode:

select @@sql_mode

SQL注入(宽字节注入、长字符截断)_第10张图片
具体的参数解释见参考资料5:MySQL的sql_mode模式,mysql5.0版本以上支持了三种sql_mode模式,如下:

ANSI 宽松模式,对插入数据进行校验,如果不符合定义类型或长度,对数据类型调整或截断保存,报warning警告。
TRADITIONAL 严格模式,当向mysql数据库插入数据时,进行数据的严格校验,保证错误数据不能插入,报error错误。用于事物时,会进行事物的回滚。
STRICT_TRANS_TABLES 严格模式,进行数据的严格校验,错误数据不能插入,报error错误。

默认情况下,mysql选择使用的是严格模式,此时如果插入的数据超过限制长度,则会报错error(如果超出的长度是由空格引起的,可能只会警告warning,实际操作证明,三种模式下,如果插入的超出长度是由空格引起的,并不会报错,仅仅会警告,本节最后给出结果):

describe users;
insert into users values (1,'admin','123');#正常插入
insert into users values (2,'admin   ','123');
#username限制7个字符,这里插入8个字符(后3个为空格,插入成功,出现警告),出现截断情况
insert into users values (3,'admin   x','1234');#报错,提醒超长字符

SQL注入(宽字节注入、长字符截断)_第11张图片
对于id=2的插入,可以查看其usernsme的长度,发现其长度为7:

select length(username) from users where id = 2;

SQL注入(宽字节注入、长字符截断)_第12张图片
下面重点讨论当sql_mode模式为ANSI时引起的长字符截断问题:
首先将sql_mode设置为ANSI模式:

SET @@sql_mode=ANSI;


接下来依次创建table,插入数据,这里发现username='admin x’也插入成功了:

create table users(
    -> id int(11) NOT NULL,
    -> username varchar(7) NOT NULL,
    -> password varchar(12) NOT NULL);#创建users表格
insert into users values (1,'admin',123);#正常插入
insert into users values (2,'admin   ',1234);#警告,插入成功
insert into users values (3,'admin  x',12345);#警告,插入成功

SQL注入(宽字节注入、长字符截断)_第13张图片
观察一下各个用户的长度,可以发现id=2,id=3的username均被截断,长度都变成了7:

select *from users;
select length(username) from users where id =1;
select length(username) from users where id =2;
select length(username) from users where id =3;

SQL注入(宽字节注入、长字符截断)_第14张图片
SQL注入(宽字节注入、长字符截断)_第15张图片
如果此时选择username= 'admin’会出现下面情况:

select username from users where username = 'admin';

SQL注入(宽字节注入、长字符截断)_第16张图片
此时,我们只查询了用户名为admin的用户,但另外两个长度不一致的用户却出现,这会造成安全问题。假如,某个管理员的用户名就是admin,他采用下面的语句登录:
$sql = "select count ( * ) from users where username = ‘admin’ and password = ‘*****’ ";
此时,我们只要伪造用户’admin x’便可以获得管理员的信息,从而进入后台。

补充: 对于三种模式下的空格插入溢出,并不会报错,结果如下:

SET @@sql_mode=STRICT_TRANS_TABLES;
insert into users values (4,'admin     ',12345);
insert into users values (5,'admin     x',12345);

SET @@sql_mode=TRADITIONAL;
insert into users values (6,'admin     ',123456);
insert into users values (7,'admin     x',123456);

SQL注入(宽字节注入、长字符截断)_第17张图片
由此可见,预防长字符截断问题可能需要从其他地方入手,如账号由管理员分配并限制更改;对数据库内容加密,即使获取到相关信息也无法破解。

延时注入

延时注入属于盲注的一种。所谓盲注,即页面无差异注入。延时注入基于时间差异技术,需要使用到数据库的相关函数。在此仅作原理上的说明。
在mysql中存在函数sleep(time),即在给定的时间tine(s)后才会执行对应的语句。如:
http://www.abc.com/user.php?id=1 and sleep(3) //页面返回正常,3秒后才打开
此时,则可以确定用户输入的数据被当作语句执行了,则存在SQL注入漏洞,结合其他函数则可以获取用户信息。思路是:首先判断用户名长度,再截取字符串第一个字符转换成ASCII码,对比ASCII表,获得信息。接着循环判断便可以获得用户名 ,这里不具体说明了。

参考资料:

  1. MariaDB和MySQL全面对比:选择数据库需要考虑这几点
  2. SQL注入教程——(四)宽字节注入
  3. PHP安全转义函数详解
  4. Kali安装Apache、MySQL、PHP
  5. MySQL的sql_mode模式

你可能感兴趣的:(web安全,安全漏洞,sql,安全)