[CLAUDE] Scripts+Skill+Docs: hardening G-084 IPv4/IPv6 port hijack
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m53s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m53s
Bài học từ VietReport VPS shared (2026-04-23): Next.js app hijack port 3000 IPv4 → Gitea bị đẩy IPv6-only → IIS ARR localhost:3000 resolve IPv4 first → git.baocaogiaoduc.vn trả homepage VietReport. Apply 3 rules G-084 preemptively cho SOLUTION_ERP (risk thấp vì API in-process IIS, nhưng vẫn chuẩn hóa): 1. `scripts/deploy-iis.ps1` — HealthUrl `localhost` → `127.0.0.1` 2. `.claude/skills/iis-deploy-runbook/SKILL.md` — 7 ref localhost → 127.0.0.1 + section Hardening mới giải thích G-084 + 3 rules + note SOLUTION_ERP relevance (risk thấp vì no standalone Kestrel/no ARR proxy hiện tại, nhưng tương lai thêm phải tuân) 3. `docs/gotchas.md` — thêm entry #33 G-084 full writeup (triệu chứng, root cause, 3 rules, SOLUTION_ERP relevance) + update debug checklist 3 rules: - Reverse-proxy luôn IP literal 127.0.0.1, không localhost - Backend services bind loopback IPv4 explicit, không 0.0.0.0 - Service dependency cho boot order khi nhiều service cùng port family
This commit is contained in:
@ -52,8 +52,8 @@ Internet
|
||||
|
||||
**SPA web.config:** 2 FE có `URL Rewrite` rule:
|
||||
1. HTTP → HTTPS redirect (bắt buộc, CORS whitelist chỉ https)
|
||||
2. `/api/* → http://localhost:5443/api/*` (ARR reverse proxy)
|
||||
3. `/hubs/* → http://localhost:5443/hubs/*` (SignalR)
|
||||
2. `/api/* → http://127.0.0.1:5443/api/*` (ARR reverse proxy)
|
||||
3. `/hubs/* → http://127.0.0.1:5443/hubs/*` (SignalR)
|
||||
4. React Router fallback: `/*` → `/index.html`
|
||||
|
||||
## Quick commands
|
||||
@ -90,8 +90,8 @@ Get-Content "C:\inetpub\apps\SolutionErp\Api\Logs\stdout_*.log" -Tail 30
|
||||
|
||||
```powershell
|
||||
# Từ server
|
||||
curl http://localhost:5443/health/live
|
||||
curl http://localhost:5443/health/ready
|
||||
curl http://127.0.0.1:5443/health/live
|
||||
curl http://127.0.0.1:5443/health/ready
|
||||
|
||||
# Từ ngoài
|
||||
curl https://api.huypham.vn/health/ready
|
||||
@ -215,12 +215,12 @@ Xem gotcha #25 (docs/gotchas.md):
|
||||
### HTTP 502 Bad Gateway (Admin/User → API)
|
||||
|
||||
```
|
||||
1. Check API up: curl http://localhost:5443/health/live
|
||||
1. Check API up: curl http://127.0.0.1:5443/health/live
|
||||
- Down → restart API site + check stdout log
|
||||
2. Check ARR enabled: IIS Manager > server level > Application Request Routing
|
||||
- "Enable proxy" phải tick
|
||||
3. Check URL Rewrite rule fe web.config
|
||||
- action type="Rewrite" url="http://localhost:5443/{R:0}"
|
||||
- action type="Rewrite" url="http://127.0.0.1:5443/{R:0}"
|
||||
```
|
||||
|
||||
### SignalR 401 (WebSocket connect fail)
|
||||
@ -296,7 +296,7 @@ scp -r .\fe-user\dist\* user@server:C:/inetpub/apps/SolutionErp/User/
|
||||
|
||||
# Trên server:
|
||||
Restart-WebAppPool -Name "SolutionErp-Api"
|
||||
curl http://localhost:5443/health/ready
|
||||
curl http://127.0.0.1:5443/health/ready
|
||||
```
|
||||
|
||||
## Backup + recovery
|
||||
@ -322,11 +322,42 @@ Restore: xem `docs/guides/runbook.md`.
|
||||
- [ ] Gitea runner registration token (re-register service)
|
||||
- [ ] Admin default `Admin@123456` (đổi qua `/system/users` admin UI ngay sau deploy)
|
||||
|
||||
## Hardening — IPv4/IPv6 port hijack (G-084 VietReport incident)
|
||||
|
||||
**Bài học từ VPS shared với VIETREPORT (2026-04-23):** VietReport team
|
||||
deploy Next.js app chiếm port 3000 (0.0.0.0 bind) khiến Gitea bị đẩy
|
||||
sang IPv6-only `[::]:3000` → IIS ARR `localhost:3000` resolve IPv4
|
||||
first → hit Next.js thay vì Gitea → `git.baocaogiaoduc.vn` trả homepage
|
||||
VietReport.
|
||||
|
||||
**3 rules áp dụng cho mọi service trên VPS shared:**
|
||||
|
||||
1. **Reverse-proxy luôn dùng IP literal `127.0.0.1`**, không dùng `localhost`
|
||||
- IIS ARR rewrite rule: `http://127.0.0.1:5443/{R:0}` ✓
|
||||
- Health check curl: `curl http://127.0.0.1:5443/health/live` ✓
|
||||
- Windows DNS resolver có thể cache IPv6 first → fail nếu service bind IPv4-only
|
||||
|
||||
2. **Backend services bind loopback IPv4 explicit**, không `0.0.0.0`
|
||||
- ASP.NET Core Kestrel (standalone): `UseUrls("http://127.0.0.1:5443")` hoặc env `ASPNETCORE_URLS=http://127.0.0.1:5443`
|
||||
- IIS ASP.NET Core Module out-of-process: ANCM tự inject port ephemeral → KHÔNG cần manual (OK)
|
||||
- Nếu deploy Kestrel standalone qua NSSM (tương lai): hardcode 127.0.0.1 trong appsettings.Production.json
|
||||
|
||||
3. **Service dependency cho boot order** khi nhiều services cùng port family
|
||||
- NSSM: `nssm set <svc> DependOnService <other>`
|
||||
- Không cần cho SOLUTION_ERP hiện tại (API in IIS app pool, không NSSM service)
|
||||
|
||||
**Hiện trạng SOLUTION_ERP — risk THẤP:**
|
||||
|
||||
- API host trong IIS app pool out-of-process → ANCM quản lý port Kestrel ephemeral
|
||||
- FE gọi trực tiếp `https://api.huypham.vn` qua CORS (không ARR proxy)
|
||||
- Không có standalone Kestrel service trên port cố định
|
||||
- **Nhưng** tương lai nếu thêm reverse proxy (fe-admin/user → `/api` → api.huypham.vn, hoặc /hubs for SignalR) → PHẢI dùng 127.0.0.1 không localhost
|
||||
|
||||
## Related
|
||||
|
||||
- `docs/guides/deployment-iis.md` — first-time setup
|
||||
- `docs/guides/runbook.md` — operations guide chi tiết
|
||||
- `docs/guides/cicd.md` — CI/CD pipeline
|
||||
- `docs/gotchas.md` — #25 webSocket lock, #26 SignalR, #28 LibreOffice 404, #29 PS 5.1 UTF-16
|
||||
- `docs/gotchas.md` — #25 webSocket lock, #26 SignalR, #28 LibreOffice 404, #29 PS 5.1 UTF-16, **#33 IPv4/IPv6 port hijack (G-084)**
|
||||
- `scripts/deploy-iis.ps1` · `scripts/backup-sql.ps1` · `scripts/install-libreoffice.ps1`
|
||||
- `.gitea/workflows/deploy.yml` — CI/CD definition
|
||||
|
||||
@ -300,6 +300,39 @@ Write-Host "Setup IIS sites done" # thay vi "Hoan tat"
|
||||
<NavLink to={path} end={path.includes('?')}>
|
||||
```
|
||||
|
||||
## IIS / Windows Server (continued)
|
||||
|
||||
### 33. IPv4/IPv6 port hijack trên VPS shared (G-084)
|
||||
|
||||
**Triệu chứng:** `git.baocaogiaoduc.vn` trả về homepage Next.js của VietReport
|
||||
thay vì Gitea UI. Headers lộ `x-nextjs-cache: HIT` + `X-Powered-By: ARR/3.0`
|
||||
(request đã qua IIS ARR proxy rồi mới hit Next.js).
|
||||
|
||||
**Root cause:** Next.js app (NSSM service) được deploy lên VPS shared với
|
||||
Gitea, ignore env `PORT=3001 HOSTNAME=127.0.0.1` và bind `0.0.0.0:3000`.
|
||||
Gitea bind `0.0.0.0:3000` trước đó bị Windows fallback xuống IPv6-only
|
||||
`[::]:3000` (default `IPV6_V6ONLY=1`). IIS ARR rewrite `http://localhost:3000`
|
||||
→ Windows DNS resolve IPv4 first → hit Next.js → leak homepage cho TẤT CẢ
|
||||
subdomain có ARR proxy về `:3000`.
|
||||
|
||||
**Fix (VietReport applied):**
|
||||
1. Next.js NSSM env `PORT=3001 HOSTNAME=127.0.0.1` — bind loopback IPv4
|
||||
2. Gitea `HTTP_ADDR=127.0.0.1` — bind loopback IPv4 explicit
|
||||
3. IIS `web.config` rewrite URL dùng `127.0.0.1` thay `localhost`
|
||||
4. NSSM `DependOnService=gitea` — boot order tránh race
|
||||
|
||||
**3 rules rút ra — áp dụng mọi service trên VPS shared:**
|
||||
- Reverse-proxy luôn **IP literal `127.0.0.1`**, KHÔNG dùng `localhost`
|
||||
- Backend services bind **loopback IPv4 explicit**, KHÔNG `0.0.0.0`
|
||||
- Service dependency cho boot order khi nhiều service cùng port family
|
||||
|
||||
**SOLUTION_ERP relevance:**
|
||||
- API host trong IIS app pool out-of-process (ANCM tự quản lý port Kestrel ephemeral) → risk THẤP
|
||||
- FE gọi trực tiếp `https://api.huypham.vn` (không ARR proxy) → risk THẤP
|
||||
- **NHƯNG** nếu tương lai thêm ARR reverse proxy (fe-admin/user `/api` proxy) hoặc
|
||||
deploy Kestrel standalone qua NSSM → PHẢI apply 3 rules trên
|
||||
- Scripts + skill doc đã update `localhost` → `127.0.0.1` để đồng bộ
|
||||
|
||||
## Checklist debug bug mới
|
||||
|
||||
1. Build pass không? → fail → check using + package version compat
|
||||
@ -313,3 +346,4 @@ Write-Host "Setup IIS sites done" # thay vi "Hoan tat"
|
||||
9. Nếu workflow 403 → check FE `workflow.nextPhases` sync từ BE pinned policy
|
||||
10. Nếu SignalR 401 → dùng `accessTokenFactory` + BE OnMessageReceived hook (#26)
|
||||
11. Nếu PS 5.1 script fail → check encoding UTF-8 / BOM / ASCII-only (#30)
|
||||
12. Nếu subdomain trả sai content / bị hijack → check IPv4/IPv6 port collision trên VPS shared (#33)
|
||||
|
||||
Reference in New Issue
Block a user