SeaCMS SQL注入漏洞 | CVE-2025-15002 复现&研究

0x0 背景介绍

SeaCMS 13.3 及更早版本中,js/player/dmplayer/dmku/class/mysqli.class.php文件中的 显示_弹幕列表() 函数因未对 page limit 参数进行类型校验,导致远程 SQL 注入漏洞。攻击者可利用该漏洞读取数据库敏感信息,若 MySQL 配置不当(如 secure_file_priv = ''),还可写入Webshell 实现远程代码执行。

0x1 环境搭建

1、Ubuntu24+Docker搭建配置

  • 保存install.sh,并赋予执行权限chmod +x install.sh
bash 复制代码
#!/bin/bash
# SeaCMS v13.3 一键部署脚本

set -e

PROJECT_DIR="SeaCMS-sql"
ZIP_NAME="SeaCMS_V13.3_install.zip"
URL="https://www.seacms.net/download/%E5%AE%89%E8%A3%85%E5%8C%85/SeaCMS_V13.3_install.zip"

echo "[+] 创建项目目录..."
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"

echo "[+] 下载安装包..."
if [ ! -f "$ZIP_NAME" ]; then
    wget -O "$ZIP_NAME" "$URL"
fi

echo "[+] 在宿主机解压并标准化路径(解决 #Uxxxx 问题)..."
rm -rf sea_cms_clean 2>/dev/null || true
unzip -o "$ZIP_NAME" -d sea_cms_clean

UPLOAD_DIR=$(find sea_cms_clean -name "Upload" -type d | head -n1)
if [ -z "$UPLOAD_DIR" ]; then
    echo "❌ 未找到 Upload 目录!"
    exit 1
fi

echo "  -> 找到 Upload 目录: $UPLOAD_DIR"
rm -rf ./webroot 2>/dev/null || true
cp -r "$UPLOAD_DIR" ./webroot

echo "[+] 生成 Dockerfile..."
cat > Dockerfile << 'EOF'
FROM php:7.4-apache

RUN apt-get update && apt-get install -y \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libzip-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd mysqli pdo_mysql zip \
    && echo "allow_url_fopen = On" >> /usr/local/etc/php/conf.d/custom.ini \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

COPY webroot/ /var/www/html/

RUN chown -R www-data:www-data /var/www/html \
    && chmod -R 775 /var/www/html \
    && chmod 777 /var/www/html
EOF

echo "[+] 生成 docker-compose.yml..."
cat > docker-compose.yml << 'EOF'
version: '3'
services:
  web:
    build: .
    ports:
      - "8080:80"
    volumes:
      - webroot:/var/www/html
    depends_on:
      - db

  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: seacms
    command: --secure-file-priv=""
    volumes:
      - webroot:/var/www/html

volumes:
  webroot:
EOF

echo "[+] 构建并启动服务..."
docker compose down -v >/dev/null 2>&1 || true
docker compose build --no-cache
docker compose up -d

echo ""
echo " 部署成功!"
echo " 访问安装页: http://localhost:8080/"
echo "   数据库地址: db"
echo "   用户: root / 密码: rootpass / 库名: seacms"
echo ""
echo " 启用弹幕后,可测试漏洞:"

2、 环境配置:

  • 1、默认安装即可,数据库地址写db海洋CMS特性就是强制后台必须修改(这个dedecms可以学习),脚本实现了,下面这一步告知你新地址

  • 2、后台登录后,开启弹幕功能

  • 3、这一步我不太确认-添加随意一个影片

  • 3.1、我这一步想的是手动发送弹幕让数据库有数据可查询,但是没有成功

  • 4、进入容器插入数据

bash 复制代码
#进入数据库
docker exec -it seacms-sql-db-1 mysql -u root -prootpass seacms
#插入弹幕
INSERT INTO sea_danmaku_list(id, type, text, color, size, videotime, ip, time, user)
VALUES('1', 'right', 'test_for_shell', '#ffffff', '24', 1.000, '127.0.0.1', UNIX_TIMESTAMP(), 'hacker');

#查询弹幕记录-id就是对应的影片
SELECT * FROM sea_danmaku_list;

0x2 漏洞复现

1、YML检测

一直陷入一个迷茫区域,如果扫描是创建一个shell,方便去直接利用, 但是现在大家安全设备已经很成熟,可能简单一句话上去就被杀掉了,也可能在IDS设备中,CVE本身不是危急等级, 然而shell的落地,会让安全人员快速关注到

bash 复制代码
https://github.com/Kai-One001/cve-/blob/main/SeaCMS_SQLi_CVE-2025-15002.yml
  • 痛腚思非,YML尽量不实现恶意的(本次是创建一个txt,嘿嘿嘿)

2、手动复现步骤

  • 未授权接口查询弹幕列表
  • SQL写入恶意文件
  • 文件利用成功

3、复现流量特征 (PCAP)

  • 可以看到明显注入语句

0x3 漏洞原理分析

1、漏洞文件与函数

  • 公开描述中可以直接定位到јѕ/рlауеr/dmрlауеr/dmku/сlаѕѕ/mуѕԛli.class.php文件,查询弹幕能找到具体方法
php 复制代码
	public static function 显示_弹幕列表()
    {
        try {
            global $_config;
            $page = 1;
            if (isset($_GET['page'])) {
                $page = $_GET['page'];
            }
            $limit = $_GET['limit'];
			$conn = @new mysqli($_config['数据库']['地址'], $_config['数据库']['用户名'], $_config['数据库']['密码'], $_config['数据库']['名称'], $_config['数据库']['端口']);
            $conn->set_charset('utf8');
            $sql = "select count(*) from sea_danmaku_list ORDER BY time DESC";
            $res = $conn->query($sql);
            $length = $res->fetch_row();
            $count = $length[0];
            $index = ($page - 1) * $limit;	
            $stmt = self::$sql->prepare("SELECT * FROM sea_danmaku_list ORDER BY time DESC limit $index,$limit"); 		
            if($stmt->execute() == false) {
                throw new Exception($stmt->error_list);
            }
            $data = self::fetchAll($stmt->get_result());
            $stmt->close();
            return $data;

        } catch (PDOException $e) {
            showmessage(-1, '数据库错误:' . $e->getMessage());
        }
    }
  • 直接能敏感发现sql语句,虽然使用了 prepare(),但 $index$limitprepare前已直接拼接到 SQL字符串中,导致预编译完全失效,等同于普通 query(),无法防御SQL 注入
  • 定义的"安全函数"
php 复制代码
function secsql($str)  
{  
if (empty($str)) return false;  
	$str = htmlspecialchars($str);  
	$str = str_ireplace('/', "", $str);
	$str = str_ireplace('[', "", $str);
	$str = str_ireplace(']', "", $str);	
	$str = str_ireplace('>', "", $str);  
	$str = str_ireplace('<', "", $str);  
	$str = str_ireplace('?', "", $str);
	$str = str_ireplace('&', "", $str);
	$str = str_ireplace('|', "", $str);
	$str = str_ireplace('{', "", $str);
	$str = str_ireplace('}', "", $str);
	$str = str_ireplace('%', "", $str);
	$str = str_ireplace('=', "", $str);
	$str = str_ireplace(':', "", $str);
	$str = str_ireplace(';', "", $str);
	$str = str_ireplace('*', "", $str);
    $str = str_ireplace('@', "", $str);	
	$str = str_ireplace('--', "", $str);
	$str = str_ireplace('//', "", $str);
	$str = str_ireplace('\\', "", $str);
	$str = str_ireplace('#', "", $str);
	$str = str_ireplace('FROM', "FRO-", $str);
	$str = str_ireplace('SELECT', "SELEC-", $str);
	$str = str_ireplace('SLEEP', "slee-", $str);
	$str = str_ireplace('union', "unio-", $str);
	$str = str_ireplace('sea_', "sea-", $str);	
	$str = str_ireplace('null', "nul-", $str);
	$str = str_ireplace('hex', "he-", $str);
	$str = str_ireplace('file_', "fil-", $str);
	$str = str_ireplace('updatexml', "update-", $str);
	$str = str_ireplace('extractvalue', "extract-", $str);
	$str = str_ireplace('benchmark', "bench-", $str);
	$str = str_ireplace('load_file', "-", $str);
	$str = str_ireplace('outfile', "out-", $str);
	$str = str_ireplace('ascii', "asc-", $str);	
	$str = str_ireplace('char', "cha-", $str);
	$str = str_ireplace('chr', "ch-", $str);	
	$str = str_ireplace('substr', "sub-", $str);
	$str = str_ireplace('substring', "sub-", $str);
	$str = str_ireplace('script', "scr-i", $str);
	$str = str_ireplace('frame', "fra-", $str);
	$str = str_ireplace('information_schema', "info-", $str);
	$str = str_ireplace('exp', "ex-", $str);
	$str = str_ireplace('information_schema', "infor-", $str);
	$str = str_ireplace('GeometryCollection', "Geomet-", $str);
	$str = str_ireplace('polygon', "poly-", $str);
	$str = str_ireplace('multipoint', "multi-", $str);
	$str = str_ireplace('multilinestring', "multi-", $str);
	$str = str_ireplace('linestring', "lines-", $str);
	$str = str_ireplace('multipolygon', "multi-", $str);
	$str = str_ireplace('base64', "bas-", $str);	
return $str;
}
  • secsql():典型的黑名单式字符串替换(替换 union、select、sleep 等关键字),本身就很脆弱,容易被大小写/编码/拼接绕过。

  • 关键问题:在显示_弹幕列表()中,对$_GET['page']$_GET['limit'] 也没有调用 secsql()或任何类型强制/白名单校验,完全无用。

2、漏洞入口

  • 向上一级可以查看到inde.php文件
php 复制代码
error_reporting(0);
require_once('init.php');
require_once('class/danmu.class.php');
require_once('../admin/data.php');
if($yzm['danmuon']!='on'){echo '弹幕系统已关闭!';die;}
........

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    if ($_GET['ac'] == "report") {
        $text = $_GET['text'];
        sql::举报_弹幕($text);
        showmessage(-3, '举报成功!感谢您为守护弹幕作出了贡献');
    } else if ($_GET['ac'] == "dm" or $_GET['ac'] == "get") {
        $id = $_GET['id'] ?: showmessage(-1, null);
        $data = $d->弹幕池($id) ?: showmessage(23, []);
        showmessage(23, $data);
    } else if ($_GET['ac'] == "list") {
        $data = $d->弹幕列表() ?: showmessage(0, []);
        showmessage(0, $data);
    } else if ($_GET['ac'] == "reportlist") {
        $data = $d->举报列表() ?: showmessage(0, []);
        showmessage(0, $data);
    } else if ($_GET['ac'] == "del") {		
        $id = intval($_GET['id']) ?: succeedmsg(-1, null);
        $type = $_GET['type'] ?: succeedmsg(-1, null);
        $data = $d->删除弹幕($id) ?: succeedmsg(0, []);
        succeedmsg(23, true);
    } else if ($_GET['ac'] == "so") {
        $key = $_GET['key'] ?: showmessage(0, null);
        $data = $d->搜索弹幕($key) ?: showmessage(0, []);
        showmessage(0, $data);
    }
}
  • 只检查是否开启弹幕,未作鉴定
  • 关键参数,ac、page、limit
参数 说明 来源
ac 动作类型 $_GET['ac']
page 页码 $_GET['page'](默认为 1)
limit 每页数量 $_GET['limit'](无默认值)

3、业务逻辑层

  • 入口文件中有引用require_once('class/danmu.class.php');一个文件,我们查看是否有配置
php 复制代码
    public function 弹幕列表()
    {
        $data = sql::显示_弹幕列表();
        //print_r($data);
        if (empty($data)) return null;

        $arr = [];
        foreach ($data as $k => $v) {
            // 请不要随意调换下列数组赋值顺序
            $arr[$k][] = (string) $v['id'];  //弹幕id
            $arr[$k][] = (float) $v['videotime'];  //弹幕出现时间(s)
            $arr[$k][] = (string) $v['type'];   //弹幕样式  
            $arr[$k][] = (string) $v['color']; //字体的颜色
            $arr[$k][] = (string) $v['cid'];  //现在是弹幕id,以后可能是发送者id了
            $arr[$k][] = (string) $v['text'];  //弹幕文本
            $arr[$k][] = (string) $v['ip'];  //弹幕ip
            //$arr[$k][] = (string)$v['time'];  //弹幕系统时间
            $arr[$k][] = $date = date('m-d H:i', $v['time']);  //弹幕系统时间
            $arr[$k][] = (string) $v['size'];  //弹幕系统大小
			$arr[$k][] = (string) $v['user'];  //弹幕用户
        }

        return $arr;
    }
  • 封装数据格式,不涉及任何输入过滤
  • 调用链: index.php → danmu::弹幕列表() → sql::显示_弹幕列表()

4、利用细节

关键语句如下,目的则是想要尝试注入进去:

php 复制代码
- $index = ($page - 1) * $limit;
- 
- "SELECT * FROM sea_danmaku_list ORDER BY time DESC limit $index,$limit"

小知识:

1. PHP对字符串参与算术运算时会做"数字前缀取值"

  • $page = "1abc" → 在 ($page - 1) 中按数字 1 处理

  • $limit = "10 union select ..." → 在乘法时会按数字 10 处理计算 $index

  • $limit自身在拼接中仍是原始字符串 "10 union select ...",不会被自动截断

2. MySQL LIMIT 语法允许表达式

  • 例如:LIMIT 0, 10 UNION SELECT ... 是合法的整个查询的一部分。

  • 也可以写:LIMIT 0, 10 PROCEDURE ANALYSE(...) 等,这里逗号后是表达式,不要求是"纯数字字面量"

3.因此攻击面在第二个参数 $limit

  • $index虽然计算时会把$limit当数字使用,但SQL里使用的是原始字符串$limit

  • 攻击者只要构造一个以数字开头的字符串即可同时:

  • 通过PHP运算(不报错)

  • 又在SQL中注入后续恶意片段。

  • 用户请求:?ac=list&page=1&limit=10 union select ...

  • 拼出的 SQL:

sql 复制代码
SELECT * FROM sea_danmaku_list ORDER BY time DESC limit 0,10 union select ...

这就导致基于 LIMIT 子句SQL 注入

4. INTO OUTFILE 原理

  • 前提:root权限、数据库配置文件中secure_file_priv=''满足,知道数据库的绝对路径
  • 当执行SELECT 'hello' INTO OUTFILE '/tmp/test.txt'; MySQL 会:把字符串 'hello' 的字节表示直接写入文件
  • 然而查询配置文件config.inc.php也没有发现有限制,这导致可以写入任意目录

0x4 修复建议

修复方案

  1. 升级到最新版本?:我看官方好像也就最新在13了(14版本的更新日志没有发现处理这个功能),不清楚后续是否还继续更新,毕竟是免费开源的,多一些理解:海洋CMS地址
  2. 开发不是强项,只能给一些思路吧
bash 复制代码
1、强制类型转换 + 数值范围校验
场景:所有涉及 page/limit 的 SQL 查询(显示_弹幕列表()/显示_举报列表())
代码:js/player/dmplayer/dmku/class/mysqli.class.php
       //修复1:强制转换并校验 page
        $page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
        if ($page < 1) $page = 1;          // 最小值校验
        if ($page > 10000) $page = 10000;  // 防DoS(最大10000页)

        //修复2:强制转换并校验 limit(默认20,最大100)
        $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20;
        if ($limit < 1) $limit = 1;
        if ($limit > 100) $limit = 100;    // 防数据量过大

        $index = ($page - 1) * $limit;     // 此时 index 已为安全整数

        //修复3:安全拼接 SQL(仅使用整数变量)
        $sql = "SELECT * FROM sea_danmaku_list ORDER BY time DESC LIMIT $index,$limit";
        $stmt = self::$sql->prepare($sql); // 无需额外转整数
2、同步修复 显示_举报列表()
// 修复逻辑完全一致,仅替换函数名
3、安全加固:统一参数验证函数 + 预处理语句
// 文件顶部添加(所有函数共用)
function safe_int($param, $default = 1, $min = 1, $max = 10000) {
    if (!isset($param) || !is_numeric($param)) return $default;
    $val = (int)$param;
    return max($min, min($val, $max));
}
  1. 临时防护措施:
    Nginx WAF 规则 :拦截含SQL关键字的limit 参数
    添加访问控制: :防止未授权数据泄露
    关闭/限制INTO OUTFILE功能 :若无业务需求,改为·NULL,或者自定义目录
    项目审计:此项还有其它漏洞,建议审计代码

免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。

相关推荐
writeone2 小时前
【无标题】
数据库·oracle
小妖同学学AI2 小时前
开源AI语音机器人小智Xiaozhi-ESP32:低成本构建个人智能助理
人工智能·机器人·开源
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue英语学习系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
百***24372 小时前
Mistral 3深度解析:开源破局者如何重塑大模型竞争格局
开源
_OP_CHEN2 小时前
【C++数据结构进阶】从 Redis 底层到手写实现!跳表(Skiplist)全解析:手把手带你吃透 O (logN) 查找的神级结构!
数据结构·数据库·c++·redis·面试·力扣·跳表
名誉寒冰2 小时前
Redis 常用数据结构与实战避坑指南
数据结构·数据库·redis
少云清2 小时前
【接口测试】1_PyMySQL模块 _数据库操作应用场景
数据库·代码实现
zhengfei6112 小时前
开源的漏洞扫描工具——Sirius 扫描
开源
spssau2 小时前
正交试验设计全解析:从正交表生成到极差与方差分析
数据库·算法·机器学习