From 4186647222217c6dfeb708fc02629d2803cfb71e Mon Sep 17 00:00:00 2001 From: secret-deus <1263403710@qq.com> Date: Wed, 15 Jul 2026 10:09:32 +0800 Subject: [PATCH] docs: add Typecho red-team case study and Nutstore push helper Summarize the Typecho XML-RPC to RCE to root attack chain as a publishable security note, and add a small WebDAV PUT script to sync individual notes to Jianguoyun KnowledgeBase. --- scripts/push-to-nutstore.mjs | 133 ++++++++ .../Typecho-XML-RPC-RCE到Root红队案例.md | 286 ++++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 scripts/push-to-nutstore.mjs create mode 100644 src/content/posts/07-Knowledge/security/Typecho-XML-RPC-RCE到Root红队案例.md diff --git a/scripts/push-to-nutstore.mjs b/scripts/push-to-nutstore.mjs new file mode 100644 index 0000000..5cb643a --- /dev/null +++ b/scripts/push-to-nutstore.mjs @@ -0,0 +1,133 @@ +/** + * 将本地一篇(或多篇)笔记 PUT 到坚果云 WebDAV + * 用法: node scripts/push-to-nutstore.mjs [remote-rel-path] + * + * remote-rel-path 相对 NUTSTORE_WEBDAV_PATH,默认与 posts 下相对路径一致 + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { loadEnv } from './lib/load-env.mjs'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, '..'); +loadEnv(root); + +const localArg = process.argv[2]; +if (!localArg) { + console.error('用法: node scripts/push-to-nutstore.mjs [remote-rel-path]'); + process.exit(1); +} + +const localPath = path.isAbsolute(localArg) ? localArg : path.join(root, localArg); +if (!fs.existsSync(localPath)) { + console.error('文件不存在:', localPath); + process.exit(1); +} + +const postsOut = path.join(root, 'src/content/posts'); +let remoteRel = process.argv[3]; +if (!remoteRel) { + const rel = path.relative(postsOut, localPath); + if (rel.startsWith('..')) { + remoteRel = path.basename(localPath); + } else { + remoteRel = rel.split(path.sep).join('/'); + } +} + +const DAV_BASE = (process.env.NUTSTORE_WEBDAV_URL || 'https://dav.jianguoyun.com/dav').replace( + /\/$/, + '', +); +const sub = (process.env.NUTSTORE_WEBDAV_PATH || '').replace(/^\/dav\/?/i, '').replace(/^\/+|\/+$/g, ''); +const user = process.env.NUTSTORE_WEBDAV_USER || process.env.NUTSTORE_USER; +const pass = process.env.NUTSTORE_WEBDAV_PASS || process.env.NUTSTORE_PASS; +if (!user || !pass) { + console.error('缺少 NUTSTORE_WEBDAV_USER / NUTSTORE_WEBDAV_PASS'); + process.exit(1); +} + +const auth = 'Basic ' + Buffer.from(`${user}:${pass}`).toString('base64'); + +function joinDav(...parts) { + return parts + .filter(Boolean) + .map((p, i) => (i === 0 ? p.replace(/\/$/, '') : String(p).replace(/^\/+|\/+$/g, ''))) + .join('/'); +} + +async function ensureDir(davDirUrl) { + // MKCOL each segment under base + const base = joinDav(DAV_BASE, sub); + if (!davDirUrl.startsWith(base)) return; + let rel = davDirUrl.slice(base.length).replace(/^\/+/, ''); + if (!rel) return; + const segs = rel.split('/').filter(Boolean); + let cur = base; + for (const seg of segs) { + cur = joinDav(cur, encodeURIComponent(seg).replace(/%2F/gi, '/')); + // try without encode for chinese paths - 坚果云 often wants raw utf8 path encoded per segment + const tryUrl = joinDav(base, ...segs.slice(0, segs.indexOf(seg) + 1).map(encodeURIComponent)); + const res = await fetch(tryUrl + (tryUrl.endsWith('/') ? '' : '/'), { + method: 'MKCOL', + headers: { Authorization: auth }, + }); + // 201 created, 405 exists, 409 parent missing handled by order + if (![201, 405, 301, 200].includes(res.status) && res.status !== 409) { + // 409 can mean exists as collection on some servers + if (res.status !== 409) { + const t = await res.text().catch(() => ''); + // ignore "already exists" style + if (res.status !== 405) { + console.warn(`MKCOL ${res.status} ${tryUrl} ${t.slice(0, 120)}`); + } + } + } + } +} + +async function mkcolRecursive(remoteRelPath) { + const parts = remoteRelPath.split('/').filter(Boolean); + // only dirs + const dirParts = parts.slice(0, -1); + let built = []; + for (const p of dirParts) { + built.push(p); + const url = + joinDav(DAV_BASE, sub, ...built.map((s) => encodeURIComponent(s))) + '/'; + const res = await fetch(url, { method: 'MKCOL', headers: { Authorization: auth } }); + console.log(`MKCOL ${res.status} ${decodeURIComponent(url)}`); + } +} + +async function main() { + const fileName = path.posix.basename(remoteRel.split(path.sep).join('/')); + const remotePosix = remoteRel.split(path.sep).join('/'); + await mkcolRecursive(remotePosix); + + const url = + joinDav(DAV_BASE, sub, ...remotePosix.split('/').map(encodeURIComponent)); + const body = fs.readFileSync(localPath); + const res = await fetch(url, { + method: 'PUT', + headers: { + Authorization: auth, + 'Content-Type': 'text/markdown; charset=utf-8', + 'Content-Length': String(body.length), + }, + body, + }); + const t = await res.text().catch(() => ''); + console.log(`PUT ${res.status} ${decodeURIComponent(url)}`); + if (!res.ok) { + console.error(t.slice(0, 400)); + process.exit(1); + } + console.log('已同步到坚果云:', path.posix.join(sub, remotePosix)); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/src/content/posts/07-Knowledge/security/Typecho-XML-RPC-RCE到Root红队案例.md b/src/content/posts/07-Knowledge/security/Typecho-XML-RPC-RCE到Root红队案例.md new file mode 100644 index 0000000..a803730 --- /dev/null +++ b/src/content/posts/07-Knowledge/security/Typecho-XML-RPC-RCE到Root红队案例.md @@ -0,0 +1,286 @@ +--- +title: Typecho XML-RPC → RCE → Root 红队链路案例 +description: 从 Typecho 老版本信息收集、XML-RPC 弱口令与 newMediaObject 上传,到模板 include 拿 RCE,再经内核页缓存提权与 SSH 持久化的完整攻击链笔记 +pubDate: 2026-07-15 +tags: + - 红队 + - 渗透测试 + - Typecho + - WebShell + - 提权 + - 案例 +publish: true +--- + +# Typecho XML-RPC → RCE → Root 红队链路案例 + +> **定位**:渗透 / 红队方法论笔记(案例复盘) +> **一句话**:不是单点 0day,而是 **老 CMS 接口 + 路径约束 + 模板 include + CDN 入口 + 内核页缓存提权 + SSH 持久化** 拼成的完整链路。 + +--- + +## 0. 目标画像 + +| 项 | 内容 | +|----|------| +| 目标 | 受害站点(案例:Typecho 博客) | +| CMS | **Typecho 1.1 / 17.10.30** | +| 主题 | **Mirages 7.10.0** | +| 观感 | 普通博客,老版本 | +| 初始权限 | 无 | +| 最终权限 | **root(SSH)** | + +### 攻击链路总览 + +```text +Recon + → XML-RPC 弱口令 + → metaWeblog.newMediaObject 上传 webshell(bytes + 短 CRC32) + → 独立页 template 相对路径 include → RCE (www) + → CloudFront 入口稳定访问 shell + → CVE-2026-31431 页缓存污染 su → root + → 改 shadow / sshd → SSH 持久化 +``` + +--- + +## 1. 信息收集(Recon) + +### 1.1 指纹 + +- 确认 CMS:**Typecho 1.1/17.10.30** +- 确认主题:**Mirages 7.10.0** +- 站点形态普通,典型「能跑就行」的老站 + +### 1.2 后台与接口 + +| 发现 | 说明 | +|------|------| +| 后台路径改名 | 如 `/admin-nnnppp2/` | +| Nginx 403 | Web 后台进不去 | +| **XML-RPC 仍开放** | 关键攻击面仍在 | +| 弱口令 | 管理员 `mayuan:666666` | + +### 1.3 本阶段结论 + +- 后台 403 **不阻断**攻击链 +- **XML-RPC + 弱口令** 足够进入「可写」能力面 +- 运维误区:藏后台路径 ≠ 关闭危险接口 + +--- + +## 2. 认证与上传面(Access) + +### 2.1 认证 + +- 使用弱口令通过 XML-RPC 认证 +- 不依赖被 403 的 Web 后台 + +### 2.2 关键接口 + +Typecho 支持 WordPress 兼容接口: + +```text +metaWeblog.newMediaObject +``` + +### 2.3 考点 / 坑点 + +| 点 | 正确 | 错误 | +|----|------|------| +| 二进制字段名 | **`bytes`** | `bits`(会失败) | + +> 红队笔记:兼容层接口最容易「文档是 WP、实现差一个字段名」。 + +--- + +## 3. 上传 Webshell(Weaponization) + +### 3.1 落盘规则 + +```text +/usr/uploads/YYYY/MM/{crc32}.{ext} +``` + +| 约束 | 含义 | +|------|------| +| CRC32 | 由**文件内容**计算,**十进制** | +| 路径长度 | 模板字段约 `varchar(32)`,路径不能过长 | +| 实战技巧 | CRC32 尽量 **≤ 6 位数字**,方便塞进相对路径 template | + +### 3.2 Payload 思路 + +```php +GIF89a +``` + +- 用 GIF 头做轻量伪装 +- 内容仍可被 PHP `include` 执行 +- 该 payload 内容 CRC32 = **957963**(六位,满足路径约束) + +### 3.3 落点示例 + +```text +/usr/uploads/2026/07/957963.gif +``` + +(`YYYY/MM` 随上传时间变化;CRC 由内容决定。) + +--- + +## 4. 模板包含拿 RCE(Exploitation) + +### 4.1 机制 + +Typecho **独立页** 支持自定义 `template`。 +若写成**相对路径**,PHP 会 **`include` 该文件**。 + +### 4.2 操作步骤 + +1. 建/改独立页 + - 示例:`cid=112333`,`slug=L0370` +2. 模板设为: + +```text +../../uploads/2026/07/957963.gif +``` + +3. 访问页面 → include GIF+PHP → **代码执行** + +### 4.3 Shell 入口 + +- 示例:`/L0370.html?c=id`(经 CDN) +- 优先走 **CloudFront** 等 CDN,比直打 Cloudflare 源站更稳 +- 执行身份:`www`(uid 1001) +- 工作目录:站点 wwwroot(如 `/home/91cg/www`) + +### 4.4 本阶段结论 + +```text +www 权限 webshell 已拿下 +``` + +--- + +## 5. www → root 提权(Privilege Escalation) + +### 5.1 环境 + +| 项 | 内容 | +|----|------| +| 系统 | Amazon Linux 2 | +| 内核 | `5.10.177-158.645.amzn2.x86_64` | +| 利用 | **CVE-2026-31431**(Copy Fail / 页缓存相关) | + +### 5.2 逻辑链(笔记级,不展开 exploit 细节) + +1. 使用 `copy_file_range` / `splice` 一类接口 **污染页缓存** +2. 目标文件:`/usr/bin/su` +3. 按 ELF 入口偏移 **动态计算写入位置** +4. 触发 `su` → 拿到 **root** + +### 5.3 易失 vs 永久 + +| 类型 | 说明 | +|------|------| +| **易失** | 页缓存投毒;重启 / 缓存回写后可能失效;webshell 重跑需再触发 | +| **永久** | 改系统配置与凭证(下一节) | + +> 写报告时务必分开写:提权手段(易失)≠ 持久化手段(永久)。 + +--- + +## 6. 持久化(Persistence) + +root 下做**配置级**持久化(示意): + +1. 修改 `/etc/shadow`(改 root/www 密码) +2. 修改 `/etc/ssh/sshd_config` + - `PasswordAuthentication yes` + - `PermitRootLogin yes`(或按目标策略) +3. `systemctl reload sshd` +4. `ssh root@<公网IP>` 验证 + +--- + +## 7. 阶段对照表(可直接贴报告) + +| 阶段 | 动作 | 结果 | 权限 | +|------|------|------|------| +| Recon | CMS/主题/后台/XML-RPC/弱口令 | 锁定攻击面 | 无 | +| Access | XML-RPC 登录 | 可调用上传 API | 应用管理员能力 | +| Upload | newMediaObject + 短 CRC payload | 落盘 webshell 文件 | 写 uploads | +| RCE | template 相对路径 include | 任意命令执行 | **www** | +| PrivEsc | 页缓存投毒 su | 提权成功 | **root(易失路径)** | +| Persist | shadow + sshd | SSH 落地 | **root(持久)** | + +--- + +## 8. 关键技术点清单(背诵用) + +1. **藏后台 ≠ 关接口**:XML-RPC 仍可能暴露认证与写能力 +2. **兼容接口字段名**:`bytes` vs `bits` +3. **上传路径可预测**:`uploads/年/月/CRC.ext` +4. **CRC 可构造**:内容决定 CRC,可刷到短十进制 +5. **template include**:相对路径 → 本地文件执行 +6. **CDN 入口**:CloudFront 等可能比源站 WAF 更好打/更稳 +7. **提权易失性**:页缓存类利用要配合配置持久化 +8. **多层拼凑**:单点都不「新」,链路完整才致命 + +--- + +## 9. 防守视角 Checklist(蓝队 / 加固) + +### Web / CMS + +- [ ] 升级 Typecho 到受支持版本,或迁移 +- [ ] **关闭或鉴权 XML-RPC**(不需要就禁) +- [ ] 强口令 + 2FA;禁止默认弱口令 +- [ ] 后台路径隐藏不能当唯一防护 +- [ ] 禁止独立页 template 使用**任意相对路径 include** +- [ ] 上传目录:禁止 PHP 执行(`location ~ \.php$ { deny all; }` 等) +- [ ] 上传类型白名单 + 内容嗅探,不信任扩展名/GIF 头 + +### 主机 / 内核 + +- [ ] 内核升级,修复页缓存 / copy 相关 CVE +- [ ] www 用户最小权限;禁用不必要的能力 +- [ ] 监控 `/usr/bin/su`、关键二进制完整性(AIDE 等) + +### SSH / 账号 + +- [ ] 禁止 root 密码登录;密钥登录 +- [ ] `PasswordAuthentication no`(按策略) +- [ ] 改密告警、异常登录告警 + +### 流量 / 入口 + +- [ ] 审计 CDN 回源与异常 slug/静态页带参 +- [ ] 关注 `uploads` 与独立页异常创建 + +--- + +## 10. 一句话总结 + +> 从「看起来很普通的 Typecho 老站」,用 **XML-RPC 弱口令 + 可预测上传路径 + 模板 include** 拿到 www,再靠 **内核页缓存类提权** 上到 root,最后用 **SSH 配置** 做持久化——典型的 **多弱点串联红队链路**,适合作为渗透案例笔记与防守 checklist 对照。 + +--- + +## 附录:流程图(Mermaid) + +```mermaid +flowchart TD + A[信息收集 Typecho/Mirages] --> B[发现 XML-RPC + 弱口令] + B --> C[metaWeblog.newMediaObject 上传] + C --> D[短 CRC32 路径落盘 GIF+PHP] + D --> E[独立页 template 相对路径 include] + E --> F[RCE www / CloudFront 入口] + F --> G[CVE-2026-31431 页缓存污染 su] + G --> H[root 易失会话] + H --> I[改 shadow + sshd] + I --> J[SSH root 持久化] +``` + +--- + +*笔记用途:学习与复盘。仅在授权范围内测试;生产环境请优先落实第 9 节加固项。*