首先这个cms是tp框架开发的,版本是3.2.3

直接看漏洞点函数

如图,经过 checkPost 检查后,就直接将源 $_GET[ 'id' ] 的参数值赋给了 $orderId 带入了 orderNotice 函数中

检查参数函数 checkPost

/**
 * 检查参数列表
 * @param  array      &$post         待检查参数列表
 * @param  array      $notCheck      设置数字检测列表,和忽略字段 例子:必须 ['is_numeric'=>['age','mobile'], 'nickname']
 * @param  boolean    $isCheckNumber 是否检查数字
 * @param  array|null $validate      需要检测的参数
 * @return boolean
 */
public static function checkPost(
    array &$post, 
    array $notCheck = array('is_numeric' => array()), 
    $isCheckNumber  = false, 
    array $validate = null)
{
    if (empty($post) || !is_array($post)) return false;
    static $flag = 0;
    //必须存在的键适用于一维数组
    if (!empty($validate))
    {
        foreach ($validate as $key => $value)
        {   //检验建名是否是$post中的建名
            if (!array_key_exists($value, $post)) {
                return false;
            }
        }
    }

    foreach ($post as $key => &$value)
    {
        if (in_array($key, $notCheck)){//屏蔽不检测的键
            $flag++;
            continue;
        }

        if (is_array($value))
        {
            return self::checkPost($value, $notCheck, $isCheckNumber);
        }
        else
        {
            if ($isCheckNumber === true 
                && !is_numeric($value) 
                && isset($notCheck['is_numeric']) 
                && in_array($key, $notCheck['is_numeric'], true)) { 
                return false;
            }
            if (in_array($key, $notCheck)){//屏蔽不检测的键
                $flag++;
                continue;
            }
            if ((!in_array($key, $notCheck) && empty($value))) {
                if ($value == 0 ) {
                    $flag++;
                } else {
                    return false;
                }
            } else {
                $value = addslashes(strip_tags($value));
                $flag++;
            }
        }
    }
    return $flag === 0 ?  false : true;
}

就像函数说明一样,正常情况下,在 is_numeric 键下的字段,都会使用 is_numeric 函数来检查其值,如果不符合条件,那么返回 false ,自然就无法进行下一步操作
一眼看去感觉这函数考虑的很周全,数组也考虑进去了,那么我们知道 tp 的注入大多数情况下是依赖数组形式的payload才行(除非直接字符串拼接,那就不用提了),那么就仔细分析下在传入的字段是数组的情况下,它处理是否正确

现在假设我们需要传入的字段,受其检测限制

这里就注意到了,检查的判断点在于 $key ,进行对数组的递归检查的时候,只是传入了 $key 对应的 $value 和 检测规则,原始的 $key 值已经丢失,这怎么可能在检测数组元素的时候还能保持原意呢,规则照样不变,但是注意上图中的小红框部分,其 $key 是我们可以任意构造的了,已经无法检测

例如,传入 test[1]=have_test

原意本是只要在检测规则中,这个 test 的所有值都必须是数字形式的,但是在递归的过程中 $key 已经变成了 1 ,而不是 test,所以无法检测

产生注入的 orderNotice 函数

在刚才的分析中,checkPost 函数实际上对数组在一定程度下是无效的,那么继续向下看,跟进 orderNotice 函数中

$orderId 就是 $_GET['id'],发现 $orderId 带入了很多地方,在进入 where 字句前,最值得我们注意的就是 paySuccessEditStatus 函数,因为如果其返回值如果为空的话,将提前结束函数流程,进而导致注入失败,我们跟进 paySuccessEditStatus 去看看

绕过一些限制

paySuccessEditStatus 函数如下:

这里是简简单单的将 $orderId 作为订单状态判断标准进行修改数据库,其实这个过程不用管的,因为我们知道修改数据库操作,那怕是 $orderId 出错也会有正常状态返回,因为关联参数不可能只有它一个。只需注意上图中红框部分,判断 int 强制转换后,是否为 0,通过前面我们知道,首先要绕过检查必须用数组,然后where 字句中造成注入,也必须要用数组,所以这里转换的也是数组,那么只要数组不为空,这个强制转换的结果是 1
想要造成注入,那么数组就必不可能为空,到这里,就全部绕过了

但是呢,为了确保注入成功,我们就继续跟入 saveStatus 查看一下

先是经过强制转换,这里和前面 (int) 形式的结果一样,只要不为空就返回 1,那么 $param 里的所有 key 和 value 都是OK的,没毛病了,这肯定可以 update 成功的

也就是说 paySuccessEditStatus 函数返回的 $status 应该不是 false 了,那么回到 orderNotice 函数中,在后续的流程中不需要绕过啥了,直接进入到了 where 字句中,就造成了 sql 注入

最后的构造反而最简单....
id[0]=exp&id[1]=%3d1=1 or 1%23orich1 test

数据库记录如下:

源链接

Hacking more

...