Step 268. HTB Medium 1대 (누적 3) — 열거의 깊이
Level 3 — CTF 실전과 공격 스킬 심화 | 난이도 ★★★★☆ | 예상 소요 시간 2일(하루 2~3시간)
전제: Step 265~267(Medium 누적 2 + 복기)을 마쳤다. 막힘 통계 v1에서 어느 단계가 약한지 알고 시작합니다.
- 준비물: HTB VPN 환경, 머신별 폴더를 만들 파일 시스템, 파이썬 3(vhost 원리 실측용).
- ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. HackTheBox(HTB)는 합법 학습 플랫폼입니다 — 그 머신들 외에는 오늘의 기술을 쓰지 않습니다.
- 검증 안내: vhost 서버와 퍼징 모사 출력은 2026-09-09에 localhost에서 실측했습니다. HTB 머신 공략 장면은 화면 예시입니다.
Step 267의 막힘 통계가 가리키는 사실 하나 — Medium에서 막히는 대부분은 기법이 아니라 "본 데를 안 봄"입니다. 정답은 대개 첫 정찰의 반경 안에 있었는데, 우리가 그 반경을 얕게 훑고 지나갔을 뿐입니다. 오늘의 주제는 열거의 깊이입니다 — "넓게"와 "깊게"를 구분해 균형을 잡는 법, 2차 열거 기법(vhost, 서브도메인, 숨은 파라미터)의 정리, 그리고 모든 발견이 새지 않게 받아 적는 열거 로그 체계. 세 번째 Medium은 이 체계를 들고 들어갑니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 열거의 "넓게"와 "깊게"를 구분하고, 막힘의 종류에 맞는 쪽을 선택한다
- vhost가 Host 헤더 분기라는 원리를 localhost 실측으로 설명한다
- 응답 크기·상태 차이로 숨은 vhost를 찾는 퍼징의 원리를 실행한다
- 서브도메인·숨은 파라미터 등 2차 열거 기법의 목록과 각각의 도구를 안다
- 머신 폴더 체계(01_scan~04_privesc)와 파일명 규칙으로 열거 로그를 정착시킨다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | HTB VPN + Kali + 파이썬 3(vhost 원리 실측) |
| 오늘의 명령 | gobuster vhost, ffuf -H "Host: FUZZ.도메인", ffuf -u "...?FUZZ=1", nmap -oN, 재독용 grep |
| 필요한 개념 | 넓게/깊게의 구분, Host 헤더와 가상 호스트, 응답 차이 기반 퍼징, 로그 체계 |
| 오늘의 산출물 | 누적 3대 root + 열거 로그 체계(폴더+파일명 규칙) + "로그 재독으로 찾은 것" 기록 |
2-1. 넓게와 깊게 — 열거의 두 축
열거의 구멍은 두 종류입니다.
- 넓게(breadth): 아예 보지 않은 표면이 있습니다. UDP를 안 봤거나, 비표준 포트를 놓쳤거나, 서브도메인을 모릅니다. 처방은 "스캔의 범위를 넓히는 것"입니다.
- 깊게(depth): 본 표면을 얕게 봤습니다. 80번을 보긴 했는데 경로 스캔이 common.txt로 끝났거나, vhost를 안 훑었거나, 파라미터를 안 뒤졌습니다. 처방은 "이미 본 것을 한 층 더 파는 것"입니다.
막힐 때 둘을 구분하세요. "내가 아직 안 본 표면이 있는가" → 넓게의 문제. "본 표면에서 안 캐낸 층이 있는가" → 깊게의 문제. Step 265의 [열거/기법] 구분보다 한 단계 세분화된 진단이고, Medium 후반으로 갈수록 깊게 쪽의 비중이 커집니다.
2-2. vhost의 원리 — 같은 IP, Host 헤더가 사이트를 가른다
한 서버(한 IP)가 여러 웹사이트를 서비스하는 기술이 가상 호스트(virtual host)입니다. 웹 서버는 요청의 Host 헤더를 읽어 어느 사이트의 내용을 돌려줄지 결정합니다 — IP와 포트가 같아도 Host가 다르면 다른 사이트가 나옵니다.
이것이 공격에서 중요한 이유: nmap은 "80번 열림"까지만 알려 주고, 그 80번 뒤에 몇 개의 사이트가 사는지는 말해 주지 않습니다. target.htb로 접속하면 공개 사이트가 나오는데, dev.target.htb를 Host로 넣으면 개발 중인 내부 사이트가 나오는 식입니다. 그 숨은 사이트에 입구가 있는 것이 Medium의 단골 설계입니다.
발견 방법은 대입입니다 — 후보 이름을 Host에 넣어 보내 보고, 응답의 상태 코드나 크기가 기준과 다른 것을 찾습니다. 없는 이름들은 전부 같은 기본 응답을 돌려주니, 다른 응답 하나가 곧 "실재하는 vhost"입니다. 이 원리를 3-1에서 직접 재현합니다.
2-3. 2차 열거 기법 목록 — 첫 패스가 끝난 뒤의 목록
기본 정찰(nmap TCP, 경로 스캔)이 끝났는데 입구가 안 보이면, 이 목록을 위에서부터 소화합니다.
| 기법 | 무엇을 찾는가 | 도구 예 |
|---|---|---|
| UDP 스캔 | TCP만 봐서 놓친 서비스(SNMP 등) | nmap -sU --top-ports 100 |
| vhost 퍼징 | 같은 IP 뒤의 다른 사이트 | gobuster vhost, ffuf -H "Host: FUZZ.도메인" |
| 서브도메인 열거 | 별도 호스트의 서비스 | ffuf, amass, 인증서 투명성 로그(crt.sh) |
| 더 큰/다른 경로 리스트 | common.txt에 없는 경로 | directory-list-2.3-medium, raft 계열 |
| 확장자 스캔 | .bak, .old, .zip, .conf 백업 파일 |
gobuster -x bak,old,zip,conf |
| 숨은 파라미터 | 문서에 없는 GET/POST 인자 | ffuf -u "URL?FUZZ=1", arjun |
| 소스 정독 | JS 안의 엔드포인트·주석·자격증명 | 브라우저 개발자 도구, linkfinder 계열 |
이 목록의 가치는 "다 했다"의 정의입니다. 막혔을 때 이 표의 모든 행에 체크가 있어야만, 비로소 기법 부족을 의심할 자격이 생깁니다.
2-4. 열거 로그 체계 — 발견이 새지 않게 받아 적는다
깊게 보는 만큼 출력이 늘어납니다. 출력을 화면에만 흘려내면, 나중에 "그 줄을 본 것 같은데"를 증명할 방법이 없습니다. 그래서 폴더와 이름의 체계가 필요합니다.
htb/machine03/
├── 01_scan/ ← nmap 등 스캔 원본 (20260909_nmap_full-tcp.txt)
├── 02_enum/ ← 경로/vhost/서비스 열거 결과
├── 03_exploit/ ← 침투 시도와 증거
├── 04_privesc/ ← 상승 열거(linPEAS 등)와 시도
└── timeline.txt ← 단계 전환과 막힘 기록
파일명 규칙은 날짜_도구_대상입니다 — 20260909_ffuf_vhost.txt. 규칙이 있어야 나중의 grep이 먹힙니다. 그리고 막혔을 때의 재독 루틴: nmap 출력 → 웹 경로 → 버전 정보 순으로 로그를 처음부터 다시 읽습니다. "놓쳤다가 재독으로 찾은 것"을 기록하는 것이 오늘의 완료 조건 중 하나입니다.
3. 따라 하기
3-1. 실측 — vhost 서버를 직접 세우고 퍼징해 본다
도구를 쓰기 전에 원리를 손으로 만듭니다. 아래 파일을 vhost_enum268.py로 저장하세요 — 표준 라이브러리만으로, Host 헤더에 따라 다른 사이트를 돌려주는 서버와 퍼징 클라이언트입니다.
"""vhost(가상 호스트) 실측 — 같은 IP, Host 헤더가 사이트를 가른다."""
import http.server
import threading
import urllib.request
HOST, PORT = "127.0.0.1", 18090
PAGES = {
"main.lab": (200, "<h1>Welcome to main.lab</h1><p>public site</p>"),
"dev.lab": (200, "<h1>dev.lab staging</h1>"
"<p>internal build 0.9.3</p>"
"<a href=\"/internal-docs\">docs</a>"),
}
DEFAULT = (200, "<h1>Welcome to main.lab</h1><p>public site</p>")
class VHostHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
host = self.headers.get("Host", "").split(":")[0]
status, body = PAGES.get(host, DEFAULT)
data = body.encode()
self.send_response(status)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def log_message(self, fmt, *args):
pass
def get(path="/", host_header=None):
req = urllib.request.Request(f"http://{HOST}:{PORT}{path}")
if host_header:
req.add_header("Host", host_header)
with urllib.request.urlopen(req, timeout=5) as r:
return r.status, len(r.read())
def main():
srv = http.server.HTTPServer((HOST, PORT), VHostHandler)
threading.Thread(target=srv.serve_forever, daemon=True).start()
print(f"[vhost 서버 기동] {HOST}:{PORT} — Host 헤더로 사이트가 갈림")
print("\n=== 장면 1: Host 헤더 없이 접근 ===")
s, n = get("/")
print(f"HTTP {s}, {n}바이트")
print("\n=== 장면 2: Host: dev.lab 으로 접근 ===")
s, n = get("/", "dev.lab")
print(f"HTTP {s}, {n}바이트")
print("\n=== 장면 3: vhost 퍼징 모사 (후보명 대입, 응답 크기 차이로 발견) ===")
_, base = get("/", "nonexistent-zz9.lab")
print(f"기준(없는 이름) 응답: {base}바이트")
for name in ["www", "api", "dev", "dev.lab", "admin", "mail", "main.lab"]:
s, n = get("/", name)
mark = " <-- 응답 크기 다름: 별도 사이트 존재!" if n != base else ""
print(f"Host: {name:<12} HTTP {s} {n:>4}바이트{mark}")
srv.shutdown()
print("\n[종료] 실측 완료")
if __name__ == "__main__":
main()
입력:
python -u vhost_enum268.py
출력 (2026-09-09 실측):
[vhost 서버 기동] 127.0.0.1:18090 — Host 헤더로 사이트가 갈림
=== 장면 1: Host 헤더 없이 접근 ===
HTTP 200, 46바이트
=== 장면 2: Host: dev.lab 으로 접근 ===
HTTP 200, 84바이트
=== 장면 3: vhost 퍼징 모사 (후보명 대입, 응답 크기 차이로 발견) ===
기준(없는 이름) 응답: 46바이트
Host: www HTTP 200 46바이트
Host: api HTTP 200 46바이트
Host: dev HTTP 200 46바이트
Host: dev.lab HTTP 200 84바이트 <-- 응답 크기 다름: 별도 사이트 존재!
Host: admin HTTP 200 46바이트
Host: mail HTTP 200 46바이트
Host: main.lab HTTP 200 46바이트
[종료] 실측 완료
읽는 법: 세 장면이 vhost 퍼징의 전부입니다. 장면 1·2에서 같은 주소(127.0.0.1:18090)가 Host 값에 따라 다른 사이트(46바이트/84바이트)를 돌려줬습니다 — nmap이 볼 수 있는 것은 여기까지가 아니라 "18090 열림"뿐입니다. 장면 3이 발견의 방법입니다 — 없는 이름은 전부 기본 응답(46바이트)인데 dev.lab만 84바이트로 달랐습니다. 퍼징은 "다른 응답 하나"를 찾는 작업이고, 그래서 기준 응답의 크기(또는 상태 코드)를 먼저 재는 것이 필수입니다.
3-2. 실전 도구로 옮기기 — ffuf와 gobuster의 vhost 모드
원리를 봤으니 도구의 출력이 읽힙니다 (화면 예시):
ffuf -u http://$TARGET -H "Host: FUZZ.target.htb" \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-fs 46
출력 예시:
dev [Status: 200, Size: 8431, Words: 1205, Lines: 210]
읽는 법: -fs 46은 "크기 46짜리(기본 응답)는 숨겨라"는 필터 — 3-1의 장면 3에서 기준 크기를 잰 것과 같은 일입니다. 도구가 자동으로 기준을 재주는 경우(-ac)도 있지만, 원리를 알면 필터가 왜 필요한지, 결과가 왜 저렇게 한 줄만 남는지가 설명됩니다. gobuster vhost -u http://target.htb -w 리스트도 같은 일을 하는 도구입니다.
3-3. 2차 열거의 나머지 — 파라미터와 확장자
입구가 발견된 페이지에도 층이 있습니다 (화면 예시):
# 숨은 파라미터 — 문서에 없는 GET 인자 찾기
ffuf -u "http://$TARGET/page.php?FUZZ=1" -w params.txt -fs 0
# 백업·설정 파일 — 경로 스캔에 확장자를 바꿔 재실행
gobuster dir -u http://$TARGET -w common.txt -x bak,old,zip,conf
읽는 법: 파라미터 퍼징은 "응답이 달라지는 인자 이름"을 찾습니다 — ?debug=1이 다른 화면을 보여 주는 식의 Medium 단골 장치입니다. 확장자 스캔은 개발자가 남긴 index.php.bak 같은 파일을 찾습니다 — 소스가 평문으로 내려오는 입구입니다. 둘 다 "본 표면을 한 층 더 파는" 깊게 쪽 기법입니다.
3-4. 로그 체계 실전 — 폴더를 만들고 시작한다
세 번째 머신은 체계를 갖추고 들어갑니다 (화면 예시):
mkdir -p htb/machine03/{01_scan,02_enum,03_exploit,04_privesc}
cd htb/machine03
date '+시작: %F %H:%M' | tee timeline.txt
nmap -sV -p- $TARGET -oN 01_scan/$(date +%Y%m%d)_nmap_full-tcp.txt
nmap -sU --top-ports 100 $TARGET -oN 01_scan/$(date +%Y%m%d)_nmap_udp-top100.txt
모든 열거 명령에 -oN/-o(ffuf는 -o 결과.json)를 붙이고, 웹 요청·응답은 Burp 프로젝트로 저장합니다.
읽는 법: 귀찮음은 처음 이틀뿐입니다. 사흘째 "그 결과를 어디 저장했더라"가 사라지는 순간 체계의 값어치가 나옵니다. 그리고 이 폴더는 그대로 Step 266식 복기와 최종 Write-up의 뼈대가 됩니다.
3-5. 막힘에서의 재독 루틴 — 로그가 답을 갖고 있다
막히면 새 스캔보다 재독이 먼저입니다. 순서를 정해 둡니다.
# 재독 루틴: 스캔 → 경로 → 버전 순으로 로그를 처음부터
grep -iE "open|http-title" 01_scan/*.txt
grep -iE "301|302|401|403" 02_enum/*.txt # 200이 아닌 응답이 단서인 경우가 많다
grep -iE "Server:|X-Powered-By|version" 02_enum/*.txt
재독으로 발견하면 기록합니다 — "놓쳤다가 로그 재독으로 찾은 것: ____". 이 한 줄이 쌓이면 "나는 어떤 종류의 줄을 자주 놓치는가"가 보입니다.
읽는 법: 301/302는 리다이렉트(따라가면 새 표면), 403은 존재의 증거(우회 대상), 401은 인증 지점(자격증명의 소비처)입니다. 재독의 눈은 "200 외의 응답"을 향하게 하세요 — 첫 패스에서는 모두가 200만 봅니다.
4. 미션과 연습문제
미션 — 누적 3대 돌파와 열거 로그 체계 정착
- 3-1의 vhost 실측 스크립트를 실행해 세 장면(기본/개발/퍼징 발견)을 캡처하세요. PAGES에 세 번째 사이트를 추가하고 퍼징이 그것도 찾는지 확인하세요.
- 누적 3대째 Medium을 3-4의 폴더 체계로 시작하세요 — 모든 스캔·열거 출력이
날짜_도구_대상규칙의 파일로 남아야 합니다. - 공략 중 막힐 때마다 2-1의 구분(넓게/깊게)을 적고, 2-3의 2차 열거 표에서 아직 안 한 행부터 소화하세요.
- root 후 "놓쳤다가 로그 재독으로 찾은 것"을 3-5 형식으로 기록하세요 — 없었다면 "재독 없이 통과"를 기록하고 그 이유를 씁니다.
- 이 열거 체계를 루틴 문서에 정식 편입(v3)하세요.
연습문제
문제 1. vhost 퍼징에서 "기준 응답의 크기를 먼저 재는" 이유를, 3-1 실측의 장면 3을 근거로 설명해 보세요.
문제 2. "넓게"와 "깊게"의 차이를 각각의 대표 기법 하나씩을 예로 들어 설명하고, 막혔을 때 어느 쪽부터 점검할지의 판단 기준을 써 보세요.
문제 3. 재독 루틴에서 200이 아닌 응답(301, 401, 403)을 먼저 보는 이유를 각 코드의 의미와 함께 설명해 보세요.
문제 4. 파일명 규칙(날짜_도구_대상)이 없으면 로그가 많아졌을 때 어떤 문제가 생기는지, grep 기반 재독 관점에서 설명해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
1번: 2026-09-09 실측에서 세 장면이 확인됐습니다 — Host 없음 46바이트, dev.lab 84바이트, 퍼징에서 dev.lab만 크기가 달라 발견. 사이트 추가 검증은 PAGES에 "api.lab": (200, "...")를 넣고 후보 목록에 api.lab을 추가하면, 퍼징 결과에서 그 줄도 기준과 다른 크기로 표시되어야 합니다.
2~3번 기록 형태 예시 (화면 예시):
htb/machine03/01_scan/20260909_nmap_full-tcp.txt (22, 80, 3000)
htb/machine03/02_enum/20260909_ffuf_vhost.txt (dev.target.htb 발견)
htb/machine03/02_enum/20260909_gobuster_dev.txt (/staging, /.git 발견)
timeline.txt: 14:20 막힘 [깊게] — 2차 표 점검 → '숨은 파라미터' 미실시 발견
14:35 ffuf 파라미터 퍼징 → ?debug=1에서 소스 노출 → 돌파
4번 예시: "놓쳤다가 재독으로 찾은 것: 첫 nmap의 3000번을 ‘기타 포트’로 넘겼으나, 재독에서 http-title 확인 후 두 번째 표면으로 재분류 — 거기가 입구였음."
검증하는 법: ① vhost 실측 캡처와 사이트 추가 검증이 있는가. ② 머신 폴더에 4개 하위 폴더와 규칙 있는 파일명이 있는가. ③ 막힘 기록에 [넓게/깊게] 구분이 있는가. ④ "재독으로 찾은 것" 또는 "재독 없이 통과 + 이유"가 있는가. ⑤ 루틴 문서 v3에 열거 체계 절이 추가됐는가.
연습문제 해답
문제 1 해답. 퍼징은 "기준과 다른 응답"을 찾는 작업이라, 기준이 없으면 "다르다"를 정의할 수 없습니다. 장면 3에서 없는 이름들은 전부 46바이트(기본 사이트)를 돌려줬고, 기준을 46으로 잡았기 때문에 84바이트의 dev.lab이 튀었습니다. 기준을 재지 않으면 모든 응답이 200으로 보여 실재하는 vhost를 구분하지 못합니다 — 실전 도구의 -fs(크기 필터)가 바로 이 기준 재기의 자동화입니다.
문제 2 해답. 넓게는 "안 본 표면을 보는 것"으로 대표 기법은 UDP 스캔이고, 깊게는 "본 표면을 더 파는 것"으로 대표 기법은 vhost 퍼징이나 파라미터 퍼징입니다. 판단 기준: "스캔 범위에 빠진 축(TCP만 봄, 상위 포트만 봄)이 있는가" → 넓게부터. "표면은 다 봤는데 각각을 얕게 봤는가" → 깊게부터. Medium에서는 깊게가 더 흔한 원인이지만, 넓게의 구멍(UDP 미스캔)은 깊게로는 절대 메워지지 않으므로 체크리스트는 넓게→깊게 순이 안전합니다.
문제 3 해답. 200은 "정상 페이지"라 첫 패스에서 이미 본 것이 대부분이지만, 나머지 코드는 첫 패스에서 무시되기 쉬운 미개척지입니다. 301/302는 다른 표면으로의 이정표(리다이렉트 목적지가 미발견 영역), 401은 인증 지점(얻은 자격증명의 소비처 후보), 403은 "존재하되 거부됨" — 파일이 있다는 증거이자 우회 가설의 대상입니다. 재독은 첫 패스와 다른 눈으로 해야 하므로, "200이 아닌 것"을 모아 보는 것이 그 다른 눈입니다.
문제 4 해답. 파일명에 규칙이 없으면 "그 결과가 어느 파일에 있었는지"를 기억에 의존하게 되고, 로그가 수십 개가 되면 재독 자체가 불가능해집니다. 규칙이 있으면 grep -iE "open" 01_scan/*.txt처럼 폴더와 패턴으로 탐색 범위를 기계적으로 좁힐 수 있습니다 — 날짜는 시점을, 도구명은 명령을, 대상은 무엇을 본 것인지를 파일명만으로 알려 줍니다. 재독은 검색이고, 검색은 인덱스가 있는 곳에서만 빠릅니다. 파일명 규칙이 그 인덱스입니다.
완료 기준 체크리스트
- [ ] vhost 원리(같은 IP, Host 헤더 분기)를 실측으로 확인했다
- [ ] vhost 퍼징이 "기준과 다른 응답 찾기"임을 설명할 수 있다
- [ ] 2차 열거 표의 각 행이 무엇을 찾는 기법인지 안다
- [ ] 누적 3대째를 폴더 체계(01_scan~04_privesc)로 진행했다
- [ ] 모든 출력이
날짜_도구_대상규칙의 파일로 남아 있다 - [ ] 막힘에 [넓게/깊게] 구분을 붙였다
- [ ] "로그 재독으로 찾은 것"(또는 재독 없이 통과의 이유)을 기록했다
- [ ] 열거 체계를 루틴 문서 v3에 편입했다
6. 흔한 실수와 해결
벽 1. "vhost 서버가 안 뜬다 / curl이 연결을 못 한다"
증상 (2026-09-09 실측, 서버 미기동 포트에 curl):
curl: (7) Failed to connect to 127.0.0.1 port 18099 after 2032 ms: Could not connect to server
원인: 주소까지는 갔는데 그 포트에서 듣는 프로그램이 없습니다 — 서버를 띄우기 전이거나, 이미 종료됐거나, 포트 번호가 다릅니다. Step 163 벽 1과 같은 해석입니다.
해결: 스크립트 실행 터미널에 "[vhost 서버 기동]" 줄이 있는지 확인하고, 같은 터미널에서 스크립트가 살아 있는 동안에만 접속하세요. 스크립트는 main() 끝에서 스스로 종료하므로, curl 실험은 실행 중에 해야 합니다.
벽 2. "퍼징이 전부 같은 크기라 아무것도 못 찾는다"
증상: 후보 수백 개가 전부 같은 Size로 나옵니다 (화면 예시):
www [Status: 200, Size: 15324, ...]
api [Status: 200, Size: 15324, ...]
admin [Status: 200, Size: 15324, ...]
원인: 둘 중 하나입니다. ① 기준 필터(-fs/-ac)가 없어 기본 응답이 전부 출력되는 것 — 저 출력은 "없는 이름들의 무덤"입니다. ② 대상이 Host를 무시하고 같은 페이지를 주는 서버(가상 호스트 미사용)입니다.
해결: 없는 이름 하나로 기준 크기를 재고 -fs 크기로 숨기세요 — 3-1의 장면 3과 같은 절차입니다. 필터 후에도 남는 게 없으면 "이 서버는 vhost가 없다"가 열거의 결론입니다. 못 찾은 게 아니라 없음을 확인한 것입니다.
벽 3. "vhost를 찾았는데 접속하면 이상한 페이지가 나온다"
증상: dev.target.htb를 발견했는데 브라우저로 열면 DNS 오류가 납니다.
원인: 그 이름이 공인 DNS에 없기 때문입니다 — 발견은 Host 헤더 대입으로 했는데, 브라우저는 이름을 IP로 풀지 못합니다.
해결: /etc/hosts(윈도우는 C:\Windows\System32\drivers\etc\hosts)에 TARGET_IP dev.target.htb를 추가하세요. curl 실험에는 -H "Host: dev.target.htb"로 충분하고, 브라우저 탐색에는 hosts 등록이 편합니다.
벽 4. "로그는 남겼는데 나중에 못 찾는다"
증상: 폴더에 파일 40개, 어느 것이 vhost 결과였는지 모릅니다.
원인: 파일명 규칙 없이 out1.txt, scan2.txt로 저장해서입니다 — 로그의 양이 검색 능력을 넘었습니다.
해결: 연습문제 4의 답 그대로입니다. 지금부터라도 날짜_도구_대상으로 개명하고, 재독은 폴더 단위로 좁혀 grep하세요. "로그를 남긴다"의 절반은 저장이고, 절반은 찾을 수 있게 저장하는 것입니다.
벽 5. "2차 열거를 다 했는데도 입구가 없다"
증상: 2-3의 표 전부에 체크가 있는데 진전이 없습니다.
원인: 이제야 비로소 기법 부족(Step 265의 구분)을 의심할 지점입니다. 또는 체크가 성급했습니다 — "돌렸다"와 "유의미한 리스트로 돌렸다"는 다릅니다.
해결: 두 가지를 점검하세요. ① 각 행의 워드리스트가 충분했는가(common.txt 1회는 "안 했다"에 가깝습니다 — medium 리스트까지가 1회분). ② 그래도 없으면 리서치로 전환합니다 — 주연 서비스의 HackTricks 페이지(Step 267)를 다시 읽으세요. 열거의 깊이에는 끝이 있고, 그 끝을 확인하는 것도 실력입니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 넓게 / 깊게 | 안 본 표면 보기 / 본 표면 더 파기 — 막힘의 두 처방 |
| 가상 호스트(vhost) | 같은 IP에서 Host 헤더로 사이트를 가르는 구조 |
| vhost 퍼징 | 후보명 대입 후 기준과 다른 응답(크기·상태)을 찾는 기법 |
| 기준 응답 | "없는 이름"의 응답 — 퍼징의 필터 기준 (-fs) |
| 숨은 파라미터 | 문서에 없는 GET/POST 인자 — debug, admin 등 |
| 열거 로그 체계 | 01_scan~04_privesc 폴더 + 날짜_도구_대상 파일명 |
| 재독 루틴 | 막히면 스캔→경로→버전 순으로 로그를 다시 읽는 절차 |
오늘의 명령어·도구
| 명령 | 하는 일 |
|---|---|
ffuf -u http://$TARGET -H "Host: FUZZ.target.htb" -w 리스트 -fs 크기 |
vhost 퍼징 (기준 크기로 필터) |
gobuster vhost -u http://target.htb -w 리스트 |
vhost 퍼징의 다른 도구 |
ffuf -u "http://$TARGET/page.php?FUZZ=1" -w params.txt |
숨은 파라미터 탐색 |
gobuster dir -u URL -w 리스트 -x bak,old,zip,conf |
백업·설정 파일 스캔 |
grep -iE "301|302|401|403" 02_enum/*.txt |
재독 — 200 아닌 응답 모아 보기 |
python -u vhost_enum268.py |
vhost 원리 실측 모형 |
명령어보다 중요한 감각
오늘 실측의 요점은 하나입니다 — 같은 주소가 Host 헤더 하나로 다른 세계를 돌려줬고, 그 세계의 존재는 "기준과 다른 응답 하나"로 드러났습니다. 열거의 깊이란 결국 이것입니다 — 보이는 것 뒤에 몇 층이 더 있는지 아는 눈과, 그 층을 기계적으로 확인하는 목록.
그리고 로그 체계를 믿으세요. Medium 후반의 막힘은 "모름"이 아니라 "놓침"이고, 놓침의 처방은 새로 보는 것보다 남긴 것을 다시 읽는 것이 빠릅니다. 누적 3대의 통계와 로그가 갖춰졌습니다 — 다음 챕터부터는 이 발견들을 서로의 열쇠로 잇는, 취약점 조합의 구간입니다.
전부 체크되면 Step 268 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.