낯선 시스템 앞에서 · 증거를 모으는 순서와 봉인 · 실습
사라지는 것부터 모으고 봉인한다
목표
RFC 3227 의 휘발성 차례대로 증거를 모으는 계획을 파일로 굳히고, 그 차례를 그대로 따르는 수집기를 만든다. 수집 시각·수집자·명령·sha256 을 매니페스트에 적고 매니페스트를 봉인한 뒤, 봉인이 깨졌는지 검증하는 도구를 만들고, 고객 개인정보가 든 항목에 선을 긋는다.
왜 중요한가
현장에 들어가면 손이 먼저 로그로 간다. 로그를 내려받는 10분 동안 프로세스 목록이 바뀌고 연결이 닫히고 임시 파일이 지워진다. 로그는 내일도 그 자리에 있지만 그것들은 지금뿐이고, 사라진 뒤에는 "그때 그 프로세스가 떠 있었나요" 라는 질문에 답할 수 없다.
RFC 3227 의 2.1 절은 휘발성이 높은 것에서 낮은 것으로 나아가라고 적고 일곱 묶음의 예시 차례를 준다. 이 차례가 주는 것은 우선순위만이 아니라 판단의 근거다 — 왜 그것을 먼저 떴느냐는 질문에 감이 아니라 등급으로 답할 수 있다.
모으는 것만으로는 부족하다. 몇 주 뒤에 "이 파일이 그날 그 서버에서 나온 것이 맞느냐" 는 질문이 온다. 항목마다 언제·누가·무엇으로 모았는지와 내용 지문을 적고, 매니페스트 자체도 봉인한다. 파일 해시만 적어 두면 매니페스트를 고쳐 버리면 그만이기 때문이다.
반대로 다 모으려 들면 고객 개인정보를 통째로 들고 나오게 된다. 선을 긋되 조용히 빼지 말고, 뺐다는 사실과 이유를 기록에 남긴다.
채점기는 여러분이 적어 낸 문구를 믿지 않는다. 자기 계획과 자기 범위 파일로 여러분의 수집기를 실제로 돌려 차례와 시각과 해시를 대조하고, 만들어진 묶음에서 파일 하나를 몰래 고쳐 여러분의 검증기가 그 이름을 대는지 본다.
단계
1. /root/evidence/gen_scene.py 를 만들어 실행해 /root/evidence/host/ 를 만드세요. 설정 2개, 로그 2개, 중앙 로그 사본 1개, 임시 파일 6개, 운영 DB(customer 120행·charge 360행)가 들어갑니다.
2. /root/evidence/plan.json 에 항목 여덟 개를 휘발성이 높은 것부터 차례대로 적으세요. 항목마다 id·volatility_class·command·why 를 적습니다.
3. /root/evidence/collect.py 를 만들어 계획의 차례 그대로 모으고 매니페스트를 남기게 하세요.
4. 항목마다 sha256 과 바이트 수를, 매니페스트에는 수집자를 적게 하세요. --collector 를 더합니다.
5. 매니페스트 자체를 봉인해 MANIFEST.sha256 을 남기게 하세요.
6. /root/evidence/verify.py 를 만들어 봉인이 깨졌는지 검증하고, 깨진 파일의 이름을 찍게 하세요.
7. /root/evidence/scope.json 에 모으지 않기로 한 항목과 이유를 적고, --scope 를 더해 제외한 항목이 기록에는 남게 하세요.
8. 진짜 묶음을 /root/evidence/bundle/ 에 만들고 검증한 뒤 /root/evidence/evidence_report.md 에 네 절로 적으세요.
참고
- 수집할 항목 여덟 개는 이것입니다.
| id | 무엇인가 | 어디서 읽나 |
| --- | --- | --- |
| proc_table | 지금 떠 있는 프로세스 목록 | 이 파드의 프로세스 표 |
| net_state | 라우팅 표와 열린 연결 | /proc/net/route, /proc/net/tcp |
| kernel_stats | 커널 통계와 메모리 | /proc/stat, /proc/meminfo |
| tmp_files | 임시 파일 시스템에 남은 것 | host/tmp |
| app_logs | 애플리케이션 로그 | host/var/log |
| app_db | 운영 데이터베이스 | host/var/lib/app.db |
| remote_logs | 중앙 로그 서버에서 받은 사본 | host/remote |
| host_config | 설정과 물리 구성 | host/etc |
- 실행 계약:
python3 /root/evidence/collect.py --plan <계획> --out <묶음 디렉터리> [--collector <이름>] [--scope <범위 파일>]은 한 줄 요약을 표준출력에 내고 종료 코드 0 으로 끝납니다. 계획이나 범위 파일을 읽을 수 없으면 3 입니다. - 검증 계약:
python3 /root/evidence/verify.py --bundle <묶음 디렉터리>는 온전하면 0, 깨졌으면 0 이 아닌 값으로 끝나고 깨진 파일 이름이 든 줄을 표준출력에 냅니다. - 계획 파일:
{"items": [{"id": …, "volatility_class": 1..7, "command": "셸 명령", "why": "왜 이 자리인가"}]}. 항목의 차례가 곧 수집 차례입니다. 수집기는 정렬하지 않습니다. - 매니페스트:
<묶음>/manifest.json에{"created_at", "collector", "items": [...]}를 씁니다. 항목마다 id·order(1부터)·volatility_class·command·collected_at·status 를 적고, 모은 항목에는 file·exit_code·sha256·bytes 를, 제외한 항목에는 reason 을 적습니다. - 수집물 파일 이름은
<id>.txt이고 명령의 표준출력을 그대로 담습니다. - 봉인:
<묶음>/MANIFEST.sha256에<manifest.json 의 sha256> manifest.json한 줄을 씁니다. - 범위 파일:
{"policy": …, "excluded": [{"id": …, "reason": …}]}. 제외한 항목은 파일을 만들지 않고 매니페스트에 statusexcluded와 reason 으로 남깁니다. - 시각은 RFC 3339 의 UTC 표기로 적습니다. 항목마다 따로 적어야 차례가 증명됩니다.
- 흔한 실수: 계획을 등급으로 다시 정렬하기(판단이 코드 안으로 숨습니다), 묶음 전체에 시각 하나만 적기, 파일 해시만 적고 매니페스트를 봉인하지 않기, 제외한 항목을 조용히 빼기.
- 항목 여덟 개의 구성과
app_db를 제외 대상으로 삼는 것은 이 실습의 가정입니다. RFC 3227 은 묶음의 차례를 정할 뿐 어떤 항목을 뺄지는 정해 주지 않습니다. - 참고 문서: [RFC 3227](https://www.rfc-editor.org/rfc/rfc3227.html)의 2.1 절이 휘발성 차례를, 2.2 절이 피해야 할 것을 적습니다. [NIST SP 800-86](https://csrc.nist.gov/pubs/sp/800/86/final)이 같은 주제를 더 넓게 다루고, [파이썬 hashlib 문서](https://docs.python.org/3/library/hashlib.html)와 [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html)가 이 실습에서 쓰는 도구입니다.
단계 8개
- 현장을 손에 쥐기
- 휘발성 차례로 계획 세우기
- 계획대로 모으기
- 언제·누가·무엇으로를 적기
- 매니페스트도 봉인하기
- 깨진 것의 이름을 대기
- 모으지 않기로 한 것을 기록에 남기기
- 묶음을 만들고 보고하기