NAS系统认证绕过到远程命令执行:漏洞链路分析与加固实践
朋友扔过来一个漏洞标题“某系统 远程代码执行链认证绕过”。说实话这类标题这几年并不算新鲜但我把整条链路完整复现完之后还是有点意外——从认证态失控到系统层命令执行中间几乎没有遇到像样的防御整个利用链顺畅得不像一个面向生产环境的系统。这篇东西基于我在虚拟靶场环境里的复现记录把认证绕过从根因分析、漏洞链路拆解、实操复现到加固方案完整写一遍过程尽量讲透末尾会补上我在排查和修复阶段踩过的一些坑。先说清楚本文所有请求和代码均来自本地模拟环境接口路径、参数名都做了脱敏处理目的是让思路可复用不是提供一份“拿来即用”的攻击脚本。安全研究的价值在于理解边界和逻辑而不是背 payload。1. 项目背景这个“认证绕过”到底绕过了什么1.1 先说清楚NAS 为什么是攻击者的“甜点”NAS网络附加存储这类设备在家庭和企业里的角色很特殊。它常年开机、暴露在局域网或公网、存储了大量高价值数据但大多数管理员对它安全状态的关注远低于对“备份是否成功”的关注。更麻烦的是NAS 系统往往集成了文件管理、应用中心、媒体服务、远程访问等多种功能代码量越大攻击面就越大。这次分析的 S 系统某自研 NAS下同就是典型代表。登录入口在 Web 端后端跑了好几个常驻服务有文件管理、应用商店、系统设置、日志导出等模块。单看表面登录页面做了密码输入限制、验证码、失败次数锁定防护做得像模像样。但认证绕过的问题从来不在“登录框”这层而在登录之后整个信任链的薄弱环节。1.2 分析范围与免责声明我强调一遍本文讨论的是教学场景下的漏洞模式不是针对任何具体产品的攻击指引。整个分析围绕三条主线展开认证机制中哪些位置容易出现“信任盲区”盲区如何被组合成一条从“未登录”到“远程命令执行”的完整链路修复时应该用什么样的设计思路替代“打补丁式”防御如果你只是听说过“认证绕过”这个词想搞清楚它到底怎么发生的这篇文章也能当个入门案例来看不需要你有很深的渗透基础但至少得知道 HTTP 请求、Cookie、Session 是什么。2. 漏洞根因拆解登录链路上的三个“信任盲区”认证绕过的高发区其实很集中翻来覆去就是那么几个套路初始化接口没做状态限制、Session 校验只看“存在性”不看“归属”、鉴权中间件只覆盖了部分路由。S 系统一次性把这三种坑全踩了一遍。2.1 第一层毛病初始化接口没有状态闸门任何设备首次上电都要有个“初始化”流程比如设置管理员密码、创建存储池。S 系统的后端有一个初始化接口用于首次部署和“管理员密码重置”场景。这个接口的逻辑一开始是这样的用户进入引导页前端调用初始化接口提交新密码后端判断当前系统是否处于“未初始化”状态如果未初始化则允许写入管理员密码听起来合理问题在于“初始化状态”这个字段存在数据库里但接口没有对请求来源做校验也没有处理“系统已经初始化过”的情况。在设计者的理解里只要前端引导页不展示入口普通用户就不会调用到这个接口。可惜攻击者从来不看前端页面他们直接看接口文档或者抓包。于是攻击者只需要在未登录状态下向这个接口发送一个合法的重置请求就能把管理员密码改掉或者直接让系统回到“待初始化”状态然后以新管理员身份接管设备。这就是“状态闸门缺失导致的认证绕过”。2.2 第二层毛病会话校验只是“查存在”不查来源绕过登录之后攻击者拿到了一个管理员身份的 Session ID。但这里还有个问题S 系统的 Session 校验函数只检查这个 SID 是否存在于内存表中不校验它对应的用户是谁、什么时候签发、来源 IP 是什么。这意味着几件事Session 一旦签发除非服务端主动销毁否则就可以一直用别人如果拿到了你的 SID就可以直接冒充你不需要知道密码服务端日志里如果意外把 SID 打印出来这些日志又恰好能被低权限用户读取就会造成横向泄漏在复现过程中我发现 S 系统的文件管理器自带一个“分享链接”功能它会把文件路径和访问令牌拼在 URL 里。正常流程中这条 URL 是发给外部协作者的但系统在生成 URL 时居然顺便把当前用户的 SID 也带进了某些内部 API 的调用参数中。设计本意大概是减少前端存储结果等于是把会话凭证写在了一个可被分享出去的链接里。2.3 第三层毛病鉴权中间件只管了一半这是最经典的路由级鉴权遗漏。S 系统的后端使用了统一入口框架理论上可以在框架层加一个全局拦截器对除登录页之外的所有接口做鉴权。但实际代码里鉴权拦截器只在“需要登录的模块”上手动注册。有一个管理模块的接口列表里部分接口因为历史原因被放在了“公共接口”目录下。开发者觉得“公共接口”就是给未登录用户访问“系统信息、公告、版本号”这类无敏感数据用的结果里面混进了一个“日志导出”接口。攻击者一旦拿到低权限 Session甚至在某些情况下绕过第一层认证后就能直接访问到这个接口从而把漏洞链往下推进。三层毛病叠加起来造成了一个非常尴尬的局面认证体系看起来到处都是实际上哪一层都没拦住关键路径。3. 完整利用链复现从绕过登录到远程执行3.1 复现环境准备复现需要一套隔离实验环境。我这边用的是本地虚拟化方案一台虚拟机跑 S 系统的系统镜像另一台虚拟机充当攻击机。网络配置上两台机器处于同一个隔离网段模拟“已拿到内网权限”的场景看起来有点站在巨人肩膀上但现实中这类设备本来就常暴露在公网。攻击机上准备的内容一个 HTTP 抓包工具用来观察登录接口和 Session 返回命令行终端方便构造原始请求一份常见的 Web 路径字典用于定位管理接口其实从 JS 文件里也能翻出来S 系统前端的路由配置是明文打包的算是白送的信息环境信息记录一下目标服务跑在 80 端口后端管理接口统一挂在/eapi/路径下前端登录页和静态资源在/根路径。3.2 第一步利用初始化接口获取管理员身份我先抓包看了登录流程。正常登录时前端把用户名、密码、验证码一起 POST 到/eapi/auth/login服务端校验通过后返回一个 JSON里面带session_id。这个流程本身没有太多花活问题出在另一个接口/eapi/setup/recovery上。我写了一个简单的 curl 请求去试curl -X POST http://192.168.10.10/eapi/setup/recovery \ -H Content-Type: application/json \ -d {action:reset,username:admin,password:Test123!}返回结果里的关键字段是这样的脱敏{ code: 0, message: ok, data: { session_id: 5f4dcc3b5aa765d61d8327deb8811a92 } }服务端没有检查系统是否已经初始化直接允许了管理员密码重置并且顺手签发了一个新的管理员 Session。这一步的意义不在于“重置密码”本身而在于攻击者拿到了一个合法的管理员会话凭证。继续验证一下这个 Session 的可用性curl -i -X GET http://192.168.10.10/eapi/system/info \ -H Cookie: SESSID5f4dcc3b5aa765d61d8327deb8811a92返回200 OK响应体里带出了系统版本、主机名、存储池状态等信息。到这里认证已经被完全绕过了。注意真实环境里你可能碰不到这样“一次调用就能重置管理员”的接口但思路是一致的——寻找那些应该在特定状态下才能访问、却没有状态约束的接口。比如注册接口、找回密码接口、设备解绑接口、初始化接口都值得过一遍。3.3 第二步借用文件导出功能寻找命令注入点有了管理员 Session接下来就是寻找命令执行点。S 系统的后台里有一个“系统日志导出”功能管理员可以下载近期的操作日志。抓包看到请求长这样POST /eapi/debug/log/export HTTP/1.1 Host: 192.168.10.10 Cookie: SESSID5f4dcc3b5aa765d61d8327deb8811a92 Content-Type: application/json {filename:operation.log}服务端收到文件名后会把它拼到一条tar命令里去执行。正常情况下文件名是固定传入但这里没有任何过滤攻击者可以直接往里面塞命令注入的 payload。我在靶场里验证了一下curl -X POST http://192.168.10.10/eapi/debug/log/export \ -H Cookie: SESSID5f4dcc3b5aa765d61d8327deb8811a92 \ -H Content-Type: application/json \ -d {filename:operation.log;id;.log}响应结果里出现了一段用户 ID 输出说明id命令已经被执行。这是整条漏洞链里最关键的一环——命令注入点。为什么选择“日志导出”这个功能坦白说这是我扫接口扫出来的。对这种系统我更关注那些看起来“不起眼”的功能比如日志、备份、恢复、模板导入导出它们经常需要调用系统命令来完成而对参数的处理又往往比核心功能粗糙得多。3.4 第三步从命令注入到稳定后门命令注入能不能打不仅要看命令是否执行还要看执行环境、输出回显、网络连通性。这一步我做了三个层面的测试先确认回显方式。S 系统会把命令的标准输出追加到返回的日志文件里所以不需要额外的“带外”通道。只要注入的 payload 输出结果就能直接从下载的日志里读出来。再测试执行权限。注入whoami返回root说明命令是在高权限下执行的。这并不奇怪很多 NAS 系统的后台服务为了保证能操作存储池经常以 root 身份运行。最后看网络层面。既然命令执行在 root 下建立稳定后门的方式有很多种常规思路是写一个 WebShell 或反弹 Shell。不过我这里用的是最直接的验证方式写一个脚本到定时任务目录脚本内容只是把当前时间追加到一个日志文件。这样做是为了确认持久化路径是否可行同时避免在实验环境里搞出不可控的外连动作。curl -X POST http://192.168.10.10/eapi/debug/log/export \ -H Cookie: SESSID5f4dcc3b5aa765d61d8327deb8811a92 \ -H Content-Type: application/json \ -d {filename:operation.log;echo \* * * * * root echo pwned /tmp/pwn.log\ /etc/cron.d/pwn;.log}到这一步攻击者已经实现了未认证 → 管理员 Session → 命令执行 → 持久化。整条链路没有使用任何 0day 级的内存破坏技巧纯粹是逻辑错误和配置疏漏的组合拳。4. 实操中的常见问题与排查实录复现过程中我遇到了不少“看起来应该成功、实际却翻车”的情况这些问题在当时很干扰判断但事后回看每一处都有明确的解释。4.1 构造的 Cookie 返回 401下一秒又 200第一次复现初始化接口时我在攻击机上拿到 Session ID 后立刻去访问/eapi/system/info结果竟然返回 401 Unauthorized。我以为方向错了回头检查才发现S 系统对 Session 的存储分了好几个哈希桶Session ID 里带了一个两位的前缀表明它属于哪个桶。攻击机上的请求头是因为 Cookie 键名写错了我一开始用了SESSION_ID而系统要求的是SESSID。排查问题时先不要急着怀疑“漏洞不成立”先确认请求构造是不是和正常登录流程完全一致。把正常的登录请求和攻击请求放在一起逐字对比是最快的方法。4.2 明明限制了管理员 IP还是被绕过了S 系统的安全配置里有一个“允许管理后台登录的 IP 白名单”功能按理说只允许特定 IP 访问管理页面。但复现时我发现这个白名单只作用在 Nginx 层只对/前缀的路径生效而/eapi/路径直接转发给了后端服务不走这条过滤规则。这是很典型的“边界不一致”问题。安全策略做在网关层却没覆盖所有路径后端服务又信任了网关节流以为自己已经在保护范围内。换句话说防御措施只在“入口”做了但真正的资源在“后院”放着结果入口再严也没用。4.3 修复后依然被“同一个洞”打穿在模拟修复过程中我给初始化接口加了一个状态判断只有setup_status0时才允许重置密码。我重新跑了一遍之前的攻击流程发现确实进不去了。但随后我换了条思路直接把数据库里的setup_status字段改回 0前提是已经通过其他漏洞拿到了一条 SQL 注入点然后再调用初始化接口照样可以重置管理员密码。这启发了我一个重要的教训修复单点漏洞不等于修复了整条链路。攻击者的目标是从 A 点到 B 点如果 A 点封死了他们只是换一条路而已。安全设计的核心不是每个点都严防死守而是让任何一条路径都走不通也就是“纵深防御”。4.4 陷阱排查速查表现象常见原因排查思路Cookie 带了却不生效Cookie 名字错误 / 作用域不匹配对比正常请求的 Cookie 头检查 Path、DomainSession 频繁失效服务端验签了时间戳或签名查看 Session 生成逻辑确认是否与 IP/UA 绑定命令注入无回显输出未追加到日志 / 被转义改用 DNS 或延时注入方式盲测检查拼接符号接口鉴权看起来有效网关层过滤不彻底尝试路径穿越/../、大小写、URL 双重编码修复后仍能利用状态判断可被绕过检查是否可以通过条件竞争或参数污染绕过这张表里的内容不算新鲜但大部分实际案例里拖垮交付进度的往往就是这些细节。5. 防御加固从“打补丁”到“重构信任模型”5.1 最小权限与默认拒绝我在复盘这条漏洞链时最深的一个感受是S 系统对“内部接口”的信任度太高了。开发者默认只有管理员能看到后台、只有认证用户才能调用 API所以所有接口都默认可以被信任。正确的设计应该是“默认拒绝”在框架层面做到除了显式声明为anonymous的接口登录页、初始化页、公开信息页其他接口一律要求认证认证通过的同时还要做越权检查确认当前用户是否有权限调用目标接口对于初始化、重置密码这类高敏感操作额外加一个“一次性令牌 状态机”校验避免接口重放这个原则很朴素但真正落地的项目不多。5.2 纵深防御下的接口设计具体到 S 系统的修复我从三个层面做了改造第一层是网关层。Nginx 里统一拦掉所有未经授权就访问/eapi/的请求并且加了一条路径归一化规则避免利用../绕过路径匹配。第二层是框架层。加一个全局鉴权中间件所有请求先过认证再进路由同时在中间件里绑定 Session ID 与登录 IP、User-Agent 的哈希值防止会话被复制使用。第三层是业务层。初始化接口除了判断状态位还增加了“随机引导码”校验引导码在首次部署时生成并展示给物理接触设备的管理员攻击者没有引导码就无法触发重置。三层改造做完我再跑一遍之前脚本第一层就被 Nginx 挡住了确实进不去。但这里我要提个醒这并不代表系统从此安全了。如果攻击者能拿到一份有效 Session比如通过钓鱼或者读取管理员浏览器 Cookie所有防护都还能绕过去。安全没有终点只有持续对抗。5.3 可落地验证的加固清单加固项验证方法常见坑所有接口默认鉴权遍历路由列表统计未认证可访问接口单纯靠代码 review 容易漏需要配合接口扫描初始化接口状态闸门初始化完成后重复调用重置接口只判断状态位不够要加引导码Session 绑定来源信息用 A 机器的 SID 从 B 机器访问要留好兼容性避免正常用户频繁掉线命令拼接处参数白名单传入;id、$(id)、反引号等测试黑名单过滤永远有漏直接用白名单日志脱敏登录后查看日志文件有无敏感字段不要只脱敏 passwordSession、Token 也要处理6. 写在最后一点个人经验复现这条链路花了不到半天真正花时间的是在“为什么能绕过”上。我以前也犯过同样的错误觉得登录页做得花里胡哨就算安全了后来才明白安全是贯穿前后端、网关、数据层的系统性设计而不是某个登录框的自嗨。如果你想往安全测试方向走建议从这类“逻辑漏洞”开始练手因为它们的复现门槛低、收益明显而且能逼着你把业务逻辑看透。不要一上来就追求什么高级漏洞利用先把“信任模型”这件事想明白。最后再分享一个小技巧当你面对一个系统不知道怎么入手时别急着扫端口跑漏洞扫描器先把前端 JS 文件下载下来看一遍。里面那些接口路径、参数名、状态字段往往比什么工具都好使——这次 S 系统的大部分接口细节就是这么来的。

相关新闻

Linux环境下Bytebase安装部署实战:数据库变更管理工具落地指南

Linux环境下Bytebase安装部署实战:数据库变更管理工具落地指南

接手一个新团队,我习惯先看数据库,不看代码。这不是职业病,是被事故教育过的。之前在某个项目里,测试环境的库和线上库的字段总是对不上,每次发版全靠 DBA 手动比对 SQL,结果有一次因为一条 ALTER TABLE 漏…

2026/10/16 18:05:46 阅读更多 →
Python 3.2.2 实用边界与安全替代方案

Python 3.2.2 实用边界与安全替代方案

简介:本资源提供Python 3.2.2官方Windows安装包及配套说明文档,面向编程初学者、教学实践者及需兼容旧项目环境的开发者,解决特定历史版本Python的获取与本地部署问题。压缩包为ZIP格式,共2个文件:16.72MB的python-3.2…

2026/10/16 18:05:46 阅读更多 →
碰碰好评卡系统开发实战 核心逻辑实现与上线运维完整指南

碰碰好评卡系统开发实战 核心逻辑实现与上线运维完整指南

碰碰好评卡系统开发实战 核心逻辑实现与上线运维完整指南 碰碰好评卡是当前电商、本地生活、到店服务场景下广泛应用的用户反馈激励工具,相比于传统的线下好评激励方式,具备可追踪、合规性强、数据可量化的优势,能够帮助商家高效收集真实用户…

2026/10/16 18:04:45 阅读更多 →

最新新闻

基于PID神经元网络的多变量解耦控制与Simulink仿真

基于PID神经元网络的多变量解耦控制与Simulink仿真

简介:面向多变量系统控制场景,PID神经元网络解耦控制算法将经典PID的稳定调节与神经网络的自学习能力相结合,把相互耦合的MIMO系统分解为多个独立子系统,适合自动化、控制工程方向的学生与研究人员学习。压缩包共6个文件&#xff…

2026/10/16 19:25:15 阅读更多 →
GBDT回归树原理与手写实现:从梯度提升到工业级可解释建模

GBDT回归树原理与手写实现:从梯度提升到工业级可解释建模

1. 为什么今天还要深挖GBDT回归树——它不是“老古董”,而是工业级建模的压舱石你可能在面试里被问过“GBDT和XGBoost有什么区别”,也可能在Kaggle比赛里抄过几行lightgbm的fit代码,但当你面对一份销售预测数据、一份设备剩余寿命评估表、或者…

2026/10/16 19:25:15 阅读更多 →
LangGraph多智能体系统设计:状态图编排与工程实践全解析

LangGraph多智能体系统设计:状态图编排与工程实践全解析

多智能体系统这两年听起来特别火,但真上手做过的都知道,绝大多数问题根本不在模型选择上,而在于你拿什么把多个Agent组织起来。我最早用纯Python写调度循环,状态机全靠手工维护,改一个分支逻辑要牵出一堆全局变量&…

2026/10/16 19:25:15 阅读更多 →
OpenCV直方图四件套:图像增强与检索的统计基石

OpenCV直方图四件套:图像增强与检索的统计基石

1. 先从像素的"人口普查"说起直方图是图像处理里被严重低估的一类工具。很多人学习 OpenCV 时,注意力全被各种滤波、边缘检测、特征点吸引走了,结果做到图像增强、目标识别、图像检索的时候才发现,直方图这个基础能力才是绕不开的底…

2026/10/16 19:25:15 阅读更多 →
LLM Judge偏差分析与优化:从位置偏差到评测一致性

LLM Judge偏差分析与优化:从位置偏差到评测一致性

1. 先搞清楚LLM Judge的偏差长什么样,再谈优化在做LLM评测落地时,很多团队一开始都会觉得“用大模型当裁判打分”是个省时省力的方案。但真正跑过一轮之后就会发现,LLM Judge远没有想象中那么“铁面无私”。它给出的分数不仅可能忽高忽低&…

2026/10/16 19:25:15 阅读更多 →
STM32F767驱动ILI9806G黑屏排查:RGB888时序与LTDC精准配置

STM32F767驱动ILI9806G黑屏排查:RGB888时序与LTDC精准配置

简介:本资源是一份面向嵌入式开发工程师与STM32进阶学习者的LCD显示驱动实战代码,聚焦STM32F767高性能MCU与ILI9806G TFT-LCD驱动芯片在RGB888真彩模式下的协同实现。解决工业人机界面、智能终端等场景中高分辨率彩色屏稳定驱动的核心问题,涵…

2026/10/16 19:24:15 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →