从PHP命令执行到域内横向移动:内网靶场第五关实战解析

发布时间:2026/11/6 9:21:15
从PHP命令执行到域内横向移动:内网靶场第五关实战解析

很多人第一次进内网综合靶场,前三关都过得挺顺:扫端口、找Web入口、打个命令执行,屏幕上回显一串uid=xxx就觉得大功告成。可真到了这个以“域环境+横向移动”为设计核心的靶场第五关,卡住的往往不是技术,而是思路——你已经站在一台机器上了,接下来该干什么、凭什么能去下一台、拿到什么才算数,脑子里一片空白。这篇文章就把这条链路从头捋一遍:Web 层那个 PHP 框架 5.0 分支的远程命令执行漏洞,成因到底是什么;为什么打到命令执行之后,几乎没人用正向连接,而都要绕一圈做反向会话;进了内网之后,域环境里的“横向”究竟在搬动什么东西;以及练完之后,怎么把一次演练沉淀成能复用的方法论。写给谁看?刚接触内网渗透、想搞明白“原理而不是只抄命令”的朋友,以及做防守、想知道自己这边该在哪一环下手的同行。

1. 靶场第五关的设计意图:它为什么非要你离开 Web 服务器

1.1 从“单点突破”到“多主机信任链”的难度跃迁

单机靶场和内网靶场完全是两种游戏。单机靶场的评分标准很直白:拿到某个文件、读到某个 flag,一次成功的利用就结束了。而内网靶场从设计上就刻意把“终点”藏在你第一台机器看不到的地方,逼着你承认一个事实——第一台机器只是跳板,不是目标。

这背后的逻辑其实很贴近真实场景。现实中的业务系统,暴露在外的往往只是边缘的一层 Web 服务,它本身价值有限,但它和内网其他主机之间存在信任关系:同一套账号体系、同一个域、可互访的网段、共享的凭据缓存。攻击面从来不是“一台机器”,而是“一组彼此信任的机器”。所以靶场这一关的评分点,通常落在“你是否拿到了域内更高权限的凭据”、“你是否能访问到域控所在网段的主机”这类指标上,而不是“你有没有 shell”。

我个人的体会是:进这类靶场之前,先在纸上写三行字——我在哪、我要去哪、我凭什么过去。第一行是当前主机的网络位置和权限;第二行是目标(通常是域内某台高价值主机);第三行是可能被复用的身份凭据或信任关系。这三行写不出来,后面所有的命令都是瞎敲。很多人一进内网就开始无脑扫全网段,扫了两小时发现自己在浪费时间,就是因为第一行都没搞清楚。

1.2 环境里那些“看起来没用”的信息,其实全是伏笔

靶场作者放东西是有讲究的。桌面上的一个文本文件、浏览器历史里的一条内网地址、某个配置文件里的连接字符串、甚至hosts文件里的一行映射,单看都是垃圾信息,串起来就是地图。

我习惯在拿到第一台机器的操作权限后,先做一轮“低噪声信息收集”,顺序大致是这样:

收集目标典型位置你能得到什么
网络位置网卡配置、路由表、DNS 配置当前机器在几个网段、DNS 指向哪里(往往就是域控)
身份上下文当前用户、用户列表、本地管理员组你以什么身份在跑、有没有高权限账号
信任线索hosts 文件、浏览器记录、配置文件内网主机名、域名、可能的业务系统地址
凭据痕迹命令行历史、脚本文件、配置文件明文最直接的横向入场券
已装工具目录列表、服务列表判断这台机器平时被谁用来干什么

这张表不是让你照抄,而是提醒一件事:信息收集的价值不在于“收集了多少”,而在于“哪个信息和哪个信息能对上”。比如你在 A 机器上看到 hosts 里有个域名映射,在 B 机器的配置文件里又看到同一个域名加一组账号密码,那这两条信息一交叉,横向路径基本就出来了。单独看,两条都是无关紧要的碎片。

提示:信息收集阶段最大的浪费是“重复收集已知信息”。每拿到一条新信息,先问自己:它和我已有的哪条信息能组合?组合不出来就先记下来,别急着深挖。

1.3 重新定义练习目标:不是“拿到会话”,而是“拿到可持续访问的路径”

这是我认为这一关最值得反复练的地方。初学者拿到第一个 shell 就兴奋,然后把它当成终点;老手拿到 shell 之后想的是“这个会话断了怎么办、我下次从哪进”。

举个很实际的例子:你通过 Web 漏洞拿到了一个低权限的会话,这个会话可能随时因为服务重启、进程被清理而消失。如果你没有在拿到它的第一时间做权限维持的合规练习(在靶场里就是想办法留一个自己能重新进来的入口),那这个会话本质上是一次性的。靶场里无所谓,真实项目里这叫“返工”。

所以在这一关,我给自己定的验收标准是三条:能不能重新进入(会话失效后可恢复)、能不能横向到第二台(凭据可用)、能不能拿到域内高价值目标的可读证据。三条缺一条,这次演练就算没做完。这个标准比“拿到 shell 了”苛刻得多,但更接近真实工作的验收方式。

2. 入口环节拆解:那个 PHP 框架 5.0 分支的远程命令执行,错在哪儿

2.1 路由分发把“用户输入”变成了“程序调用”

要理解这类漏洞,得先理解现代 PHP 框架的基本工作方式。请求进来,框架先解析 URL,把它拆成“模块/控制器/方法”三段,然后去实例化对应的类、调用对应的方法,参数从请求里取。这套机制的设计初衷是让开发者写代码更省事——加个控制器文件,URL 就自动能访问了。

问题就出在这个“自动”上。当框架允许通过请求参数去影响“调用哪个类、调用哪个方法、传什么参数”时,用户输入就不再是数据,而变成了程序控制流的一部分。一旦某个环节没有对“类名”和“方法名”做白名单约束,理论上就可以让框架去实例化任意一个已加载的类,并调用它任意一个可访问的方法。

这类漏洞的可怕之处在于:它不需要你找到一个业务代码里的注入点,它打的是框架本身。也就是说,只要这个版本部署在公网上,漏洞面和业务代码写得好不好完全无关。这也是为什么版本号本身就是最重要的情报之一——它直接决定了“这台机器值不值得花时间”。

2.2 版本号为什么比端口更值钱

我做信息收集时有个习惯:先看“是什么”,再看“开了什么”。端口扫描告诉你 80 端口开着,但 80 端口开着的机器有几千万台;而一个能确定到具体版本的特征,直接把这台机器的可利用性从“未知”压缩到“已知”。

版本识别的常见来源包括:报错页面的固定文案、静态资源的路径特征、响应头里的框架标识、默认页面的版权信息、特定路径的响应码差异。在练这个靶场的时候,我就是靠一个默认路由的响应特征把版本锁定在了 5.0 这个分支上。

这里有个经验值得说:不要迷信工具的输出。指纹识别工具给的是“可能性”,你得手工验证。我见过太多次工具报了一个版本,实际是另一回事,结果按错误的版本去试,白白耗掉一小时。验证方法很简单——找一个该版本特有、而其他版本没有的路径或响应特征,能对上才算数。

2.3 验证思路与最小化验证原则

这里我不打算给具体的请求构造,原因有两个:一是这类公开漏洞的分析资料已经非常充分,编号和细节随手可查;二是真正值得练的不是“抄一个请求”,而是“怎么用最小的动作确认漏洞存在”。

最小化验证原则指的是:第一次验证只做“无副作用的信息读取”,绝不直接上高危动作。比如先确认能不能读到当前进程的用户身份,或者能不能读到一个固定路径下的系统文件。读到,说明命令执行成立;读不到,可能是权限限制、可能是函数被禁用、也可能是路径不对。

为什么要这么谨慎?因为靶场里你怎么打都行,但真实场景中,一个不谨慎的操作可能直接把服务打挂、把日志刷爆、把对方的监控触发。养成“先读后写、先轻后重”的习惯,是这一行最基本的职业素养。

注意:命令执行成立之后,第一件事不是执行一堆命令,而是确认三件事——当前用户是谁、能不能写文件、能不能出网。这三个答案决定了你后面所有方案的选型。

3. 一次性命令执行不够用:反向会话的工程学

3.1 正向连不上,不是你配错了,是拓扑决定的

这是新手最容易卡的地方。拿到命令执行后,第一反应往往是“在这台机器上开个监听端口,然后我连过去”。结果发现连不上,就开始怀疑人生。

问题不在你,在网络。绝大多数真实环境和这个靶场一样:Web 服务器处在 DMZ 或某个受限网段,它的入站规则只放行了 80/443 这类业务端口,其他端口一律拒绝;或者它根本没有公网可达的地址,中间隔着 NAT。这种情况下,你从外面主动连它,方向上是“由外向内”,被防火墙拦掉是必然的。

反向会话解决的正是这个问题:连接的发起方反过来。让目标机器主动往外连一个你能接收的地址,这样流量方向就变成了“由内向外”,而绝大多数环境的出站策略比入站策略宽松得多。这不是什么技巧,就是网络拓扑决定的必然选择。

3.2 出网方向、端口与传输方式的选型逻辑

知道了要反向连,接下来的问题是怎么选。我一般按这几个维度决策:

决策维度需要确认的事影响
出网能力目标能不能访问外网?能不能只访问特定网段?决定接收端放在哪
端口策略出站哪些端口是放行的?决定监听端口选 80/443 还是高位端口
协议限制是否只允许 HTTP/HTTPS 出站?决定用原始 TCP 还是走应用层封装
执行环境有没有常见的运行时可用?决定用什么方式建立会话
持久性需求是一次性验证还是要反复操作?决定是否需要可重连的机制

这张表的读取顺序是从上到下,因为上层的答案会直接否掉下层的选项。比如目标只能 HTTPS 出站,那你用原始 TCP 反向连基本没戏,得考虑把通信封装在 HTTP 里。

在靶场里练的时候,我建议每种方式都亲手试一遍,包括失败的那几种。失败的尝试带来的信息量往往比成功的更大——它会告诉你这个环境的具体限制在哪。

3.3 会话的交互体验,比“能不能连上”更影响效率

连上了只是开始。真正折磨人的是交互体验:命令没有回显、按退格键乱码、跑不了需要终端的程序、一执行长命令就超时断开。

这些问题的根源是:最初的会话往往不是一个真正的终端,而只是一个“把命令字符串丢给系统执行、再把输出抓回来”的通道。它没有伪终端(PTY),所以所有依赖终端的能力都没有。

解决的思路是分级提升:

  1. 第一级:能执行单条命令、能看到输出。够用来做信息收集。
  2. 第二级:能进入交互式环境,有基本提示符。够用来做文件操作。
  3. 第三级:有完整的伪终端,支持作业控制、支持交互式程序。够用来做需要“进入某个程序内部”的操作。

从第一级到第三级,每一步都需要额外的技巧,而这些技巧的实现方式取决于目标机器上有什么可用的运行时。我踩过最典型的坑是:费半天劲拿到了第三级会话,结果因为一次误操作(比如在前台跑了一个不会退出的程序)把它挂死了,只能从头再来。教训就是:在任何交互式会话里,长任务一律放后台,别赌它不会卡。

3.4 从“能执行命令”到“能搬东西”

命令执行和文件传输是两件事。很多时候你有命令执行,但目标机器上没有你需要的工具,你得把工具传上去。

传输方式的选择取决于通道能力:能出网就走下载,不能出网就用命令执行本身做编码传输(把文件内容编码后分段写进去),实在不行就用系统自带的工具做中转。这里的关键判断是“目标上有没有现成的下载能力”——有些运行时的标准库就能发 HTTP 请求,有些就得靠系统命令。

我在这一步的固定动作是:先测试目标能不能访问一个我自己可控的地址(用一个无害的请求验证),能访问就走下载;不能访问就老老实实用编码方式分段写。前者快,后者稳但慢,别在后者上耗太久,优先想别的路子。

4. 域内横向:真正被搬动的是“身份”,不是工具

4.1 先搞清楚自己站在哪张地图上

进了内网,第一件事不是扫,是画图。我通常按三个层次来画:

  • 网络层:当前主机能直达哪些网段?哪些网段被隔离?DNS 指向哪里?
  • 主机层:网段内有哪些存活主机?它们开放了什么?哪些是服务器、哪些是工作站?
  • 身份层:这些主机属于哪个域?当前凭据在这个域里是什么角色?

三层的顺序不能乱。跳过网络层直接扫全部网段,是最常见的低效操作——你以为在“全面收集”,其实大部分流量根本到不了,只是在自己骗自己。

画完图之后,你才会发现一个真相:内网里真正值得打的主机通常只有那么几台,其余的都是噪音。减少无效目标,比加快扫描速度重要一百倍。

4.2 凭据的三层形态:明文、哈希、票据

这是我认为这一关最核心的知识点,也是很多人练完还没真正搞懂的地方。

在域环境里,“身份”不是抽象概念,它有具体的存在形式,而且是分层的:

形态存在于哪里能用来做什么局限
明文口令配置文件、脚本、内存中的凭据缓存直接登录、直接认证拿到即用,但大多数场景下拿不到
口令哈希本地安全数据库、内存中的凭据在支持哈希认证的协议上直接使用依赖目标协议是否接受哈希
票据内存中的认证缓存直接以该身份访问服务有有效期,且绑定特定服务

理解这三层的意义在于:你不需要“破解”什么,你只需要“搬运”身份。这是内网横向和 Web 渗透最大的思维差异。Web 渗透的思路是“找漏洞、打进去”;内网横向的思路是“找到一份可用的身份,然后以这个身份去访问下一台”。

所以横向的本质是:凭据获取 → 凭据复用 → 权限提升 → 再获取凭据,一个循环。每转一圈,你手里的身份就高一级。这个循环转不动了,横向就停了。

我在练习时犯过一个典型错误:拿到一份哈希之后,第一反应是想办法去破解它。花了很多时间,最后发现根本不需要——直接用它去做认证就通了。能用就用,不能用再想别的,别把时间浪费在“我要搞明白这个密码是什么”上。

4.3 远程执行通道的几种载体与选择依据

域内横向的“移动”两个字,落到操作上就是“用某个身份,在另一台机器上执行点什么”。可用的载体通常有这么几类:

  • 系统管理协议:设计上就是用来远程管理的,认证方式成熟,日志记录完整。
  • 远程服务创建:通过管理接口在目标上创建一个服务来执行命令,权限要求高,动静大。
  • 远程计划任务:利用任务调度机制执行,同样需要较高权限。
  • 远程桌面:拿到交互式桌面,最直观但最慢,且容易被察觉。
  • 远程过程调用:利用系统 RPC 接口,隐蔽性好但依赖具体的接口实现。

选择依据不是“哪个最厉害”,而是三个很朴素的判断:当前身份有没有权限、目标上有没有对应的服务、这次操作会不会留下太明显的痕迹。

举个具体例子:如果当前身份是普通域用户,那远程服务创建这类需要管理员权限的路径基本走不通,得先想办法提权或者找到有权限的凭据。反过来,如果当前身份已经是域管理员,那用哪种载体都行,此时应该优先选“日志最少、动作最轻”的那一种。

4.4 横向过程中的失败信号与排查顺序

横向失败很常见,而且报错信息往往很模糊——“拒绝访问”三个字可能是十种不同的原因。我的排查顺序固定是这一套:

  1. 认证是否通过:先把“身份对不对”和“目标通不通”分开验证。用同样的凭据去访问一个已知开放的服务,能通说明凭据没问题,问题在目标;不能通说明凭据或网络有问题。
  2. 网络是否可达:从当前主机测目标的关键端口,别用 ping(很多环境禁 ICMP,会误导你)。
  3. 权限是否足够:确认你的身份在目标上是否属于管理员组。注意,本地的管理员组和域的管理员组是两回事。
  4. 服务是否可用:目标上对应的管理服务是否开启、是否被策略限制。
  5. 时间是否同步:域环境里认证对时间偏差非常敏感,偏差过大直接认证失败,而且报错不会告诉你原因。

第五条是我踩过的最深的坑。当时所有条件都对,就是认证不过,折腾了很久才发现目标主机和当前主机的时间差超出允许范围。这个坑的教训是:进域环境第一件事,看时间。

5. 演练记录怎么写才有复用价值

5.1 时间线、证据链与“可复现”

练完就忘,是这类靶场练习最大的浪费。我坚持写三类记录:

  • 时间线:几点几分做了什么、得到什么结果。不用详细,一行一条。
  • 证据链:每条结论对应的原始输出。比如“目标存在命令执行”这条结论,后面必须附上当时的验证输出。
  • 可复现说明:如果让我从零再做一遍,步骤是什么?哪些步骤是可以省略的?

第三条是最容易被忽略、也最有价值的。它逼着你去掉那些“当时试了很多次才碰巧成功”的操作,把路径压缩成最短链路。写不出来,说明你其实没真正掌握,只是运气好。

5.2 把“运气”从结论里剔除

演练中总有那么几个瞬间是“我也不知道为什么就成了”。这种情况必须在记录里单独标记出来,然后回头补课。

我的做法是:凡是标了“不确定”的步骤,事后必须重新做一遍,并且换一个环境变量(比如换一种会话方式、换一个时间点、换一台机器)再验证一次。两次都能成,才算真的会了。

这个习惯带来的收益很直接:下次遇到类似环境,你不用再从头摸索,因为你知道哪一步是关键、哪一步可以跳过。

6. 反过来看:这条链路上每一环的防守抓手

练攻击的意义,一半在于攻,一半在于知道怎么防。沿着这条链路倒推,每一环都有明确的防守切入点。

6.1 Web 层:版本管理与输入约束

框架层面,最有效的措施其实很朴素:盯住版本。这类公开漏洞一旦披露,厂商会很快给出修复版本,问题在于有多少部署没升级。做资产管理的同行应该都有体会,最大的风险从来不是“未知漏洞”,而是“已知漏洞没修”。

代码层面,核心原则是永远不要让用户输入决定程序控制流。凡是涉及“动态调用类、动态调用方法、动态拼接执行”的地方,一律白名单约束。这一条写进代码规范,能挡掉一大类问题。

运行环境层面,禁用不必要的危险函数、限制 Web 进程的系统权限、把 Web 目录设置为不可执行,这三条是成本最低、收益最高的加固。

6.2 主机层:出站策略比入站策略更值得关注

这一条是很多人忽略的。大家习惯性盯着“谁能进来”,却很少管“谁能出去”。而本文讲的整条链路里,最难防的一环恰恰是反向会话——因为它是从内往外发起的。

有效的做法是:默认拒绝所有出站,只放行明确需要的目标。一台 Web 服务器需要主动连外网的情况其实很少,把出站收紧到最小集合,能直接掐死反向会话这条路。

配套的措施还包括:限制 Web 进程能调用的系统命令、监控异常的子进程创建(比如 Web 服务进程突然起了个命令行解释器,这本身就是一个强信号)。

6.3 身份层与网络层:凭据管理和分段

域环境里最值钱的资产不是主机,是凭据。防守重点应该放在三处:

  • 减少凭据在高风险主机上的留存:能不用域管理员登录的机器,就别用。
  • 高权限账号分级使用:域管理员账号只用于域控,日常运维用委派权限。
  • 监控异常的认证行为:同一份凭据短时间内出现在多台不相关主机上,这本身就是异常。

网络层则是老生常谈但极其有效的分段。把 Web 区、办公区、服务器区、域控区隔开,只放行必要的访问路径。分段做得好,即使 Web 服务器被拿下,横向的每一步都要重新突破一道边界,成本和被发现的风险都会急剧上升。

我个人在实际操作中的体会是:这条链路上真正难补的不是技术,是“默认宽松”的习惯。默认放行所有出站、默认所有机器用同一个高权限账号、默认内网互相信任——这三个“默认”,才是让一条从 Web 到域控的完整链路得以成立的真正原因。练靶场的价值,就在于你亲手走通一遍之后,回头看自己负责的环境,会突然发现那些“一直这样也没出事”的配置,其实一直在等着出事。