首先这个cms是tp框架开发的,版本是3.2.3
直接看漏洞点函数
如图,经过 checkPost 检查后,就直接将源 $_GET[ 'id' ] 的参数值赋给了 $orderId 带入了 orderNotice 函数中
/**
* 检查参数列表
* @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,所以无法检测
在刚才的分析中,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
数据库记录如下: