前言
SQL注入是老生常谈的问题了,漏洞成因简单明了,入门安全的一般都是先从SQL注入学起。看到网上各个面试经验都提到经常会被问到SQL注入绕过相关的知识,可以见得SQL注入在安全界的地位。因为是科普所以不想写太长篇幅,而且网上已有很多很多很多的SQL注入的相关介绍。本篇仅仅抛个砖,记录一下和编码相关的一些相关知识。
编码在SQL注入中的作用
随着程序员安全意识的不断提高,也逐渐意识到了对单引号进行限制可以抵挡一部分的SQL注入攻击,之所以说一部分是像数字型注入以及一些编码方式依旧可以成功注入,而宽字节则是其中的典型,也是面试常被问起的点。
宽字节注入原理解析
"简单聊聊宽字节注入吧?"
自信回答:“在GBK
编码下当单引号被转义后,可以在前面加上%df
使得转义符\
被吃掉,以至于单引号可以逃出转义。”
"那么除了GBK
编码,还有哪些编码会出现宽字节问题?"
靓仔语塞。。。
为了避免上述面试尴尬场景的出现,于是梳理梳理宽字节原理。个人理解宽字节是一类编码,是具有高位和低位组成的编码。凡编码都有一个编码范围,比如Ascii码的范围0-127
,GBK
的范围0x8140~0xFEFE
。
GBK
作为双字节编码,则高位的范围为0x81-0xFE
,低位的范围为0x40-0xFE
。
前置知识已经铺垫好了,以一个例子来看。
<?php
$name=$_GET['name'];
$name=addslashes($name);
$conn = mysqli_connect('localhost', 'root', 'root', 'user');//连接MySQL服务
if (!$conn) {
die('Could not connect to MySQL: ' . mysqli_connect_error());
}
$conn->set_charset("GBK");
@mysql_select_db("test",$conn);
$sql="select * from user where username='".$name."'";
$result=mysqli_query($conn,$sql);
@$row = mysqli_fetch_assoc($result);
echo "Your password is:".$row['password']."</br>";
?>
set_charset("GBK")
的作用是设置数据库的编码方式为GBK
。这种设置方式是不安全的,一会再提。
zhhhya%df%27%20or%201=1%20%23
这串值是url编码之后的结果,而URL编码实际上是字符的ascii
码的十六进制再加上%
作为前缀。所以这串值在程序的眼里是长这样的:
zhhhya0xdf0x270x20or0x201=10x200x23
由于经过addslashes($name)的转义,所以值应该变成如下,其中0x5C 对应 \
zhhhya 0xdf0x5c 0x270x20or0x201=10x200x23
而此时程序判断出0xdf0x5c
在GBK
编码的范围之内(0x8140~0xFEFE)
,所以可以解码出有意义的字符,从而导致了转义符\
被吃掉。
从上述流程来看,构成宽字节注入的前提程序要用宽字节的编码,以及低位的编码范围包含了%5C。宽字节的编码有这些GB2312、GBK、GB18030、BIG5、Shift_JIS
。
修补方案
那如何修补呢?
上文中代码使用了mysql_query(“set names gbk”)是不安全的设置方式,而在mysql中是推荐mysql_set_charset(“gbk”);函数来进行编码设置的,这两个函数大致的功能相似,唯一不同之处是后者会修改mysql对象中的mysql->charset属性为设置的字符集。
并且使用过滤函数mysql_real_escape_string(),具有相同过滤功能的函数还有mysql_real_escape_string() magic_quote_gpc=On addslashes() mysql_escape_string()功能类似,他们之间的区别就是mysql_real_escape_string()会根据mysql对象中的mysql->charset属性来对待传入的字符串,因此可以根据当前字符集来进行过滤。
<?php
$name=$_GET['name'];
$conn = mysqli_connect('localhost', 'root', 'root', 'user');//连接MySQL服务
if (!$conn) {
die('Could not connect to MySQL: ' . mysqli_connect_error());
}
mysqli_set_charset($conn,"gbk");
$name=mysqli_real_escape_string($conn,$name);
@mysqli_select_db("user",$conn);
$sql="select * from user where username='".$name."'";
echo $sql."<br>";
$result=mysqli_query($conn,$sql);
@$row = mysqli_fetch_assoc($result);
echo "Your password is:".$row['password']."</br>";
?>
值得一提的是要先设置字符串编码,再进行转义,顺序反了也无法成功防御。而且两条语句都要写,单独使用任意一条都无法防御。在新开发一款产品的时候可以在编码时候就考虑安全防御的,可大多数都是维护已有的产品,很多都还是以addslashes()
的方式转义。那如何修补?P神博客对宽字节和编码问题进行了很深入的讨论此处就不再过多黏贴复制。其中的一个防御方式就是,以二进制的方式传输数据。
SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary
待续
没想到写着写着感觉又过于细致了,科普系列不想把篇幅写的过长。于是拆分成一、二两篇。下一篇讲一讲我所知道iconv
函数引起的编码问题。