Step 322. 두 번째 CVE 독립 분석 — 혼자 서는 분석 사이클
Level 4 — 제보·CVE 분석·오픈소스 기여 | 난이도 ★★★★☆ | 예상 소요 시간 3시간
전제: Step 320~321의 전 사이클(선정 → diff 분석 → 노트 → 재현 → 보고서)을 한 번 완주했다.
- 준비물: Git Bash와 파이썬, Step 320~321의 산출물(연습 저장소, 분석 노트, PoC, 보고서), 그리고 시간을 잴 타이머.
- 주의: ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. 오늘도 모든 실험은 여러분이 직접 만든 로컬 저장소 안에서만 진행합니다.
Step 320~321은 책이 앞에서 길을 안내하는 분석이었습니다. 오늘은 안내를 뺍니다. "선정 → 패치 확보 → diff 분석 → 랩 구축 → 재현 → 보고서"의 전 사이클을 혼자 힘으로, 그리고 지난번보다 빠르게 수행하는 것이 목표입니다. 마무리에는 한 단계 더 나아가 "이 패치는 완전한가?"라는 질문 — 변형 입력으로 패치 우회를 시도하는 연구자의 마지막 검증을 실측합니다. 이 사이클이 몸에 배면, 새 취약점이 뜰 때마다 남들보다 먼저 이해하는 연구자가 됩니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 1-day 분석 전 사이클을 체크리스트만 보고 혼자 수행한다
- 분석할 CVE(또는 연습 대상)를 "패치 공개 여부/재현 가능성/내 언어" 기준으로 선정한다
- 선정부터 재현까지의 소요 시간을 측정해 첫 분석과 비교한다
- 변형 입력으로 패치의 완전성(우회 가능 여부)을 검증한다
- 막혔을 때 돌아갈 지점(권고문 → 커밋 메시지 → diff → 노트)을 스스로 정한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | Git Bash + 파이썬 3.10+ (표준 라이브러리만) |
| 오늘의 도구 | 독립 분석 체크리스트, 소요 시간 기록표, 변형 입력 목록 |
| 필요한 개념 | Step 320의 분석 노트 4칸, Step 321의 재현 대비 구조, 패치 우회(bypass) |
| 오늘의 산출물 | 두 번째 분석 보고서 1편 + 소요 시간 기록 + 패치 완전성 검증 결과 |
2-1. 독립 분석이란 — 빠진 것은 정답지
첫 분석과 두 번째 분석의 차이는 대상이 아니라 정답지의 유무입니다. 처음에는 책이 "이 diff를 이렇게 읽으세요"라고 안내했습니다. 오늘부터는 여러분이 안내자입니다.
독립 분석의 진짜 목표는 속도가 아니라 막혔을 때의 회복력입니다. 실제 CVE 분석은 아무 분석 글도 없는 상태에서 시작하니까요. 막히는 것은 실패가 아니라 정상 상태이고, 그래서 가치가 있습니다.
2-2. 독립 분석 체크리스트 — 사이클의 골격
Step 320~321을 압축한, 혼자 볼 수 있는 일곱 칸짜리 체크리스트입니다. 오늘의 작업 절차이자, 앞으로 모든 분석의 템플릿입니다.
[ ] 1. 선정 — 패치 커밋이 공개됐고, 재현 환경을 만들 수 있고, 내가 읽는 언어인가?
[ ] 2. 확보 — 취약/패치 두 버전의 코드를 손에 넣었는가? (git log / git show)
[ ] 3. diff — 세 질문(사라진 것/온 것/없으면 어떻게)에 답했는가?
[ ] 4. 노트 — 유형(CWE)/트리거/영향/패치 원리 네 칸을 채웠는가?
[ ] 5. 재현 — 취약 버전 성공 + 패치 버전 차단을 둘 다 확인했는가?
[ ] 6. 보고서 — 개요/원인/영향/재현/수정 다섯 칸을 완성했는가?
[ ] 7. 완전성 — 변형 입력으로 패치 우회를 시도해 봤는가?
2-3. 막혔을 때의 후퇴 지점 — 단서의 사슬
실제 CVE는 친절하지 않습니다. diff가 거대하거나 코드베이스가 낯설면, 다음 사슬을 거슬러 올라가세요.
막힘 → 공식 권고문(advisory) 읽기 → 커밋 메시지의 단서
→ 보안 패턴(검증/이스케이프/길이 체크)으로 diff 좁히기
→ 찾은 함수의 호출 경로 추적 → 트리거 가설 → 랩에서 검증
권고문과 커밋 메시지가 최소한의 단서입니다. 거기서 출발해 코드를 읽는 과정 자체가 이 Step의 목표입니다 — 답을 찾는 것보다, 찾는 절차를 혼자 밟는 것이 중요합니다.
2-4. 패치 우회 — "이 패치는 완전한가?"
패치가 원래 공격은 막아도, 변형 입력까지 막는지는 별개의 문제입니다. 따옴표 하나만 걸렀는데 대문자 변형이나 인코딩 우회가 뚫리는 패치가 실제로 흔합니다. 우회를 발견하면 그것은 곧 새 취약점 — Step 318~319의 제보로 이어지는 지점입니다.
오늘은 Step 320의 패치 버전에 변형 입력 세 가지를 던져, 패치가 정말 완전한지 실측으로 검증합니다.
3. 따라 하기
오늘의 실습 대상은 새 취약점 하나입니다 — Step 320 연습 저장소에 없던 "세 번째 취약점"을 스스로 만들지 않고, 대신 여러분이 Step 320 미션에서 직접 만든 search.py 패치를 대상으로, 오늘은 "혼자 사이클을 밟는 절차"와 "패치 완전성 검증"에 집중합니다. 실제 CVE로 할 때도 절차는 동일합니다.
3-1. 타이머를 켜고 선정하기
먼저 시작 시각을 기록합니다. 소요 시간 측정이 오늘의 핵심 데이터입니다.
분석 시작: 2026-09-09 __:__
대상: (Step 320 미션의 search.py 패치 / 또는 실제 CVE 번호)
선정 근거: 패치 커밋 존재 / 재현 환경 있음 / 읽을 수 있는 언어
읽는 법: 실제 CVE를 고를 때도 이 세 조건을 먼저 검사합니다. 패치 커밋이 없으면 diff 분석이 불가능하고, 재현 환경을 못 만들면 증명이 불가능하고, 못 읽는 언어면 분석이 표류합니다. 세 조건을 통과하는 대상이 "오늘의 시험지"입니다.
3-2. 체크리스트 1~4 — 확보·diff·노트를 혼자 밟기
책의 안내 없이, 2-2 체크리스트만 보며 진행합니다. 명령은 이미 알고 있습니다.
cd oneday-lab
git log --oneline # 취약/패치 커밋 해시 확인
git diff <취약해시> <패치해시> # 패치 diff
diff를 읽고 분석 노트를 씁니다. 이번에는 책의 예시를 보지 말고 네 칸을 혼자 채워 보세요 — 유형, 트리거, 영향, 패치 원리.
혼자 하는 것의 요령: 막히면 2-3의 사슬로 후퇴합니다. "이 diff의 핵심 변경이 뭐지?" → 세 질문(사라진 것/온 것/없으면 어떻게)부터. 이 세 질문은 책이 없어도 여러분의 것입니다.
3-3. 체크리스트 5~6 — 재현과 보고서
Step 321 미션에서 만든 검색 기능 PoC를 다시 실행해 대비를 확인합니다.
git show <취약해시>:search.py > search_v1.py
git show <패치해시>:search.py > search_v2.py
python search_poc.py search_v1.py
python search_poc.py search_v2.py
결과가 "취약 버전 성공 / 패치 버전 차단"이면 보고서를 씁니다. 여기까지가 Step 320~321의 복습 사이클입니다. 시간을 기록하세요.
재현 완료: 2026-09-09 __:__ (시작부터 ___시간 ___분)
왜 재는가: 첫 분석과 두 번째 분석의 시간 차이가 성장의 자입니다. 선정 30분 + diff 분석 1시간 + 재현 40분 같은 분할 기록이 있으면, 어느 단계가 느린지도 보입니다.
3-4. 체크리스트 7 — 패치 완전성 검증 (실측)
이제 오늘의 새로운 한 걸음입니다. 패치가 원래 공격은 막았습니다. 그런데 변형 입력도 막을까요? Step 320의 패치 버전(app_v2.py)에 인젝션 변형 세 가지를 던져 봅니다.
입력 (Git Bash):
python -c "
import importlib.util
spec = importlib.util.spec_from_file_location('t', 'app_v2.py')
app = importlib.util.module_from_spec(spec); spec.loader.exec_module(app)
conn = app.init_db()
for payload in [\"admin' --\", \"admin' OR '1'='1' --\", \"' OR 1=1 --\"]:
ok = app.login(conn, payload, 'x')
print(f' payload={payload!r} ->', '취약' if ok else '차단')
"
출력 (2026-09-09 실측):
[FAIL] 로그인 실패
payload="admin' --" -> 차단
[FAIL] 로그인 실패
payload="admin' OR '1'='1' --" -> 차단
[FAIL] 로그인 실패
payload="' OR 1=1 --" -> 차단
읽는 법: 세 변형이 모두 차단됐습니다. 이 패치는 완전합니다. 바인딩 방식은 특정 페이로드를 걸러 내는 것이 아니라 입력이 문법이 되는 통로 자체를 닫기 때문에, 변형의 여지가 없습니다.
왜 하는가: 반대 결과 — 변형이 뚫렸다면 — 가 바로 "패치 우회 취약점"의 발견입니다. 검증 목록을 만들 때는 ① 원래 공격, ② 조건을 바꾼 변형(다른 필드, 대소문자, 인코딩), ③ 유사 경로(같은 입력이 쓰이는 다른 함수) 순으로 넓히세요. 오늘의 대상은 구조적으로 우회가 없지만, 문자열 블랙리스트로 막은 패치라면 이 세 번의 시도가 새 취약점을 잡아냅니다.
3-5. 보고서 완성과 공개
두 번째 보고서에는 첫 번째에 없던 두 줄이 추가됩니다.
[분석 과정 기록]
- 소요 시간: 선정 __분 + 확보 __분 + diff 분석 __분 + 재현 __분 + 보고서 __분 = 총 __시간
- 첫 분석 대비: (빨라진 단계 / 여전히 느린 단계)
[패치 완전성 검증]
- 시도한 변형: admin' -- / admin' OR '1'='1' -- / ' OR 1=1 --
- 결과: 전부 차단 — 패치 완전 (우회 없음)
(실제 보고서에서는 각 [칸]을 마크다운 소제목으로 씁니다.)
공개는 Step 321의 3-6 체크리스트를 통과한 뒤에만입니다. 이것으로 누적 분석 보고서 2편입니다.
3-6. 실제 CVE에 적용할 때의 차이점 (화면 예시)
연습 저장소와 실제 CVE의 차이는 세 가지뿐입니다.
- 선정 — GitHub Security Advisories, NVD, 벤더 릴리스 노트에서 "fix commit" 링크가 있는 것을 고릅니다 (화면 예시 — 외부 접속 환경에서 확인하세요).
- 확보 —
git clone후 권고문의 두 태그/커밋 사이를git diff합니다. 저장소가 크면 커밋 페이지의 Files changed 탭이 더 빠릅니다. - 재현 환경 — 오늘의 파일 추출 대신, 취약 버전을 설치한 VM이나 Docker 이미지가 필요합니다.
Dockerfile에 취약 버전 설치 과정을 적어 두면 다음 분석에서 재사용됩니다.
절차 — 세 질문, 네 칸 노트, 대비 재현, 다섯 칸 보고서, 완전성 검증 — 는 오늘 연습한 것과 하나도 다르지 않습니다.
4. 미션과 연습문제
미션 — 네 번째 취약점의 완전 독립 분석
oneday-lab에 새 취약점을 직접 설계합니다 — 예: 프로필 파일을 읽는 기능에서 경로 조작(../../etc/passwd류)이 가능한profile.py. 취약 커밋 → 패치 커밋 순서로 만듭니다- 만들 때 적은 "설계 의도" 메모는 덮어 두고, 2-2 체크리스트만 보며 분석합니다 — 마치 남의 코드처럼요
- 선정부터 재현까지 단계별 소요 시간을 기록합니다
- 다섯 칸 보고서 + 분석 과정 기록 + 패치 완전성 검증(경로 조작 변형 입력 시도 포함)까지 완성합니다
- Step 321 3-6 체크리스트 통과 후 공개(또는 공개용 파일로 저장)합니다 — 누적 3편
연습문제
문제 1. 독립 분석에서 CVE 선정의 세 조건을 말하고, "패치 커밋이 공개되지 않은 CVE"를 골랐을 때 어떤 문제가 생기는지 설명해 보세요.
문제 2. 거대한 diff 앞에서 막혔을 때의 후퇴 사슬(2-3)을 순서대로 말해 보세요.
문제 3. "패치가 원래 공격을 막았다"와 "패치가 완전하다"의 차이를, 오늘 실측의 변형 입력 실험과 연결해 설명해 보세요.
문제 4. 소요 시간을 단계별로 나눠 기록하면 무엇을 알 수 있나요? 그리고 그 데이터를 다음 분석에 어떻게 활용하나요?
5. 모범 답안과 완료 기준
미션 모범 답안
경로 조작 예시의 골격입니다. 취약 버전:
def read_profile(name):
with open(f"profiles/{name}.txt", encoding="utf-8") as f:
return f.read()
패치 버전 (검증 추가 패턴):
import os
def read_profile(name):
base = os.path.realpath("profiles")
target = os.path.realpath(os.path.join(base, f"{name}.txt"))
if not target.startswith(base + os.sep):
raise ValueError("허용되지 않는 경로입니다")
with open(target, encoding="utf-8") as f:
return f.read()
분석 노트 예시: 유형 CWE-22(Path Traversal) / 트리거 name에 ../ 포함 / 영향 서버 임의 파일 읽기 / 패치 원리 — 경로 정규화 후 기준 폴더 밖 접근 차단. 완전성 검증 변형 예시: ../, ..\..\(Windows), 이중 우회 ....//, URL 인코딩 %2e%2e%2f 등.
검증하는 법: ① 설계 의도 메모를 보지 않고 diff에서 취약 지점을 혼자 특정했는가. ② 취약/패치 대비 재현이 성공했는가. ③ 변형 입력 목록을 만들어 시도했는가. ④ 소요 시간이 단계별로 기록됐는가. ⑤ 보고서 다섯 칸 + 과정 기록 + 완전성 칸이 있는가.
연습문제 해답
문제 1 해답. 세 조건은 ① 패치 커밋(또는 전후 버전)이 공개됐는가, ② 재현 환경을 만들 수 있는가, ③ 내가 읽을 수 있는 언어/플랫폼인가입니다. 패치가 공개되지 않은 CVE는 diff 분석 자체가 불가능합니다 — "무엇이 고쳐졌는가"라는 지도가 없어 코드 전체에서 바늘을 찾아야 하고, 패치 비교라는 대조군도 없어 재현 증명이 약해집니다.
문제 2 해답. 막히면 ① 공식 권고문을 읽어 영향 컴포넌트를 좁히고, ② 커밋 메시지에서 보안 단서(fix/security/CVE)를 찾고, ③ 보안 패턴(검증 추가/이스케이프/길이 체크)으로 diff 범위를 좁히고, ④ 찾은 함수의 호출 경로를 거슬러 트리거 가설을 세우고, ⑤ 랩에서 검증합니다. 어느 단계든 진전이 없으면 한 단계 더 위로 후퇴합니다.
문제 3 해답. "원래 공격을 막았다"는 하나의 입력에 대한 사실이고, "완전하다"는 변형 입력 전체에 대한 주장입니다. 오늘 실측에서는 세 가지 변형(admin' --, ' OR '1'='1 계열)이 모두 차단돼 바인딩 패치가 완전함을 확인했습니다. 반대로 변형이 뚫렸다면 그것은 패치 우회 — 새 취약점이며, 제보로 이어집니다.
문제 4 해답. 어느 단계가 병목인지 알 수 있습니다. 예를 들어 diff 분석은 빨라졌는데 환경 구축이 여전히 느리다면, 다음에는 Dockerfile 템플릿을 미리 만들어 환경 단계를 자동화하는 식의 개선이 가능합니다. 시간 기록은 감이 아니라 데이터로 자기 사이클을 개선하는 도구입니다.
완료 기준 체크리스트
- [ ] 2-2의 일곱 칸 체크리스트만 보고 전 사이클을 수행할 수 있다
- [ ] CVE 선정의 세 조건(패치 공개/재현 가능/내 언어)을 적용할 수 있다
- [ ] 막혔을 때 후퇴 사슬(권고문→커밋 메시지→패턴→호출 경로→검증)을 설명할 수 있다
- [ ] 단계별 소요 시간을 기록하고 첫 분석과 비교했다
- [ ] 변형 입력으로 패치 완전성을 검증했다
- [ ] 두 번째(또는 세 번째) 분석 보고서를 완성했다
6. 흔한 실수와 해결
벽 1. 정답을 아는데 분석하는 척이 안 된다
증상: 직접 만든 취약점이라 diff를 보기도 전에 답을 압니다.
원인: 오늘의 훈련 대상은 "취약점을 모르는 척"이 아니라 절차를 혼자 밟는 것입니다. 답을 알아도 세 질문과 네 칸 노트를 거치는 손동작이 남습니다.
해결: 설계 메모를 덮고 diff부터 읽으세요. 그리고 실전 감각은 결국 실제 CVE에서 옵니다 — 체크리스트가 몸에 배면, 분석 글이 아직 없는 최신 CVE 하나를 골라 같은 절차를 적용해 보세요. 그것이 이 챕터의 진짜 졸업 시험입니다.
벽 2. 아무 분석 글도 없는 CVE 앞에서 손이 멈춘다
증상: 검색해도 워크스루가 없어 시작점을 못 찾습니다.
원인: 그게 정상입니다. 분석 글이 없으니 가치가 있는 것입니다.
해결: 2-3의 사슬 첫 칸으로 가세요 — 공식 권고문과 커밋 메시지가 최소한의 단서입니다. "어느 파일이 고쳐졌나"만 알아도 출발점입니다. 코드를 읽는 능력이 성장하는 과정 자체가 이 Step의 목표입니다.
벽 3. 시간 재는 것을 잊고 몰두했다
증상: 재현까지 끝내고 나니 시작 시각을 적지 않았습니다.
원인: 몰입은 좋은 일입니다. 데이터만 날아간 것입니다.
해결: Git이 이미 시간을 알고 있습니다 — git log --format="%h %ad %s"로 각 커밋 시각을 보면 대략의 단계별 시간이 복원됩니다 (2026-09-09 실측으로 확인 가능한 명령). 다음부터는 체크리스트 첫 칸 옆에 시작 시각을 먼저 적는 습관을 붙이세요.
벽 4. 변형 입력이 뚫렸는데 확신이 안 선다
증상: 패치 버전에서 변형 입력이 성공한 것 같은데, 버그인지 실수인지 모릅니다.
원인: 우회 발견은 항상 "내가 잘못한 건 아닌가"에서 시작합니다.
해결: 재현의 대비 구조로 돌아가세요 — ① 같은 변형이 취약 버전에서도 되는가(되어야 함), ② 원래 공격은 패치 버전에서 막히는가(막혀야 함). 둘 다 ‘예’이고 변형만 패치 버전에서 뚫리면, 그것은 실제 우회입니다. 연습 저장소라면 설계를 의심하고, 실제 CVE라면 Step 318~319의 제보 절차로 갑니다.
벽 5. 속도에 매여 분석이 얕아진다
증상: 첫 분석보다 빨리 끝냈는데 보고서가 성깁니다.
원인: 이 챕터의 목표는 속도가 아니라 독립성과 회복력입니다. 빠르고 얕은 분석은 워크스루 베끼기보다 못합니다.
해결: 완료 기준을 시간이 아니라 칸으로 확인하세요 — 노트 네 칸, 보고서 다섯 칸, 완전성 검증. 전부 채워진 뒤의 시간만 의미가 있습니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 독립 분석 | 정답지 없이 전 사이클을 혼자 밟는 분석 |
| 선정 3조건 | 패치 공개 / 재현 가능 / 읽을 수 있는 언어 |
| 후퇴 사슬 | 권고문 → 커밋 메시지 → 보안 패턴 → 호출 경로 → 랩 검증 |
| 패치 우회(bypass) | 원래 공격은 막히지만 변형 입력이 뚫리는 불완전 패치 |
| 완전성 검증 | 원래 공격 + 변형 입력 + 유사 경로 순의 검증 |
| 소요 시간 기록 | 단계별 시간 데이터로 자기 사이클을 개선하는 습관 |
오늘의 명령어와 형식
| 도구 | 하는 일 |
|---|---|
| 독립 분석 체크리스트 | 선정→확보→diff→노트→재현→보고서→완전성 |
git log --format="%h %ad %s" |
커밋 시각으로 단계별 시간 복원 |
| 변형 입력 목록 | 원래 공격 → 조건 변형 → 유사 경로 |
| 보고서 + 과정 기록 | 다섯 칸 + 소요 시간 + 완전성 검증 결과 |
명령어보다 중요한 감각
오늘 여러분은 체크리스트 일곱 칸을 얻었습니다. 이 일곱 칸은 어느 언어, 어느 CVE, 어느 코드베이스에도 그대로 올라타는 범용 절차입니다. 막혔을 때 어디로 후퇴하는지 아는 사람은, 막히지 않는 사람보다 멀리 갑니다.
그리고 마지막 칸 — "이 패치는 완전한가?" — 를 기억하세요. 오늘 실측처럼 변형이 전부 차단되면 분석의 확신이 깊어지고, 하나라도 뚫리면 그것이 곧 새 취약점입니다. 분석자의 사이클이 익숙해지는 곳에서, 발견자의 사이클이 시작됩니다.
전부 체크되면 Step 322 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.