수정 전후 동일 테스트
100분 안팎
학습 목표
동일한 테스트로 결함과 수정 효과를 비교합니다.
개념
같은 검사가 필요한 이유
수정 전에는 타인의 요청을 보내고 수정 후에는 소유자 요청만 보내면 성공 문장이 두 개 있어도 결함이 사라졌다는 비교가 아닙니다. 입력·기대값·자료 준비·검사 함수가 같아야 변경 효과를 좁힐 수 있습니다. 이번 실습은 regression-pair.cjs의 probe를 두 구현에 그대로 적용합니다. 구현을 함수 인자로 받으므로 검사 파일을 복사하다 한쪽 기대값을 바꾸는 실수를 줄입니다.
정책은 수정 후에도 고정합니다
타인 자료를 감추는 이 앱의 계약은 404입니다. 일반적인 모든 앱이 같은 상태를 써야 한다는 규칙은 아닙니다. probe의 assert.equal은 타인 응답을 404와 비교합니다. 취약 fixture에서는 실제 200이라 foreign_read_denied라는 지정 실패가 생깁니다. 수정 앱에서는 그 실패가 없어야 합니다. 기대값을 200으로 고쳐 통과시키면 결함을 고친 것이 아니라 정책을 바꾼 것입니다.
실패를 좁게 승인합니다
수정 전 명령이 0이 아닌 종료 코드를 내는 이유는 여러 가지입니다. 문법 오류·모듈 누락·자료 생성 실패도 실패이며 결함 재현으로 인정할 수 없습니다. probe는 타인 조회 assertion에서만 ERR_ASSERTION을 받아 actual 200, expected 404를 구조화합니다. paired-check는 그 구조와 케이스 이름까지 비교합니다. 다른 단계의 예외는 그대로 전파하므로 취약 비교 성공으로 감추지 않습니다.
취약 fixture의 역할
vulnerable-fixture.cjs는 수정 앱을 호출한 뒤 인증된 타인의 기존 자료 GET에 한해 저장소 자료를 반환합니다. 무인증 요청과 없는 자료에는 같은 우회를 적용하지 않습니다. 이것은 소유자 확인 누락을 설명하는 의도적인 교재 모델입니다. 운영에서 발견한 과거 버전의 바이너리가 아니고 원래 모든 결함을 동일하게 재현하는 구현도 아닙니다. 보고서에는 비교 fixture의 제한을 명시합니다.
정상 업무도 함께 확인합니다
모든 요청을 거절하는 앱은 타인 404만 보면 안전한 것처럼 보입니다. 하지만 소유자의 정상 조회 200과 생성 201이 같이 유지되어야 합니다. 무인증 401은 로그인 경계를, 없는 자료 404는 자료 존재 처리를 확인합니다. 조회 후 Alice 자료를 다시 읽어 원본과 깊은 비교를 합니다. 이 대조가 없다면 거절 응답 전에 자료가 변경되는 부작용을 놓칠 수 있습니다.
상태를 서로 격리합니다
before와 after가 같은 SQLite 파일을 공유하면 앞 구현의 변경이 뒤 결과에 섞일 수 있습니다. probe는 호출할 때마다 새 임시 폴더와 데이터베이스를 생성하고 finally에서 정리합니다. 계정과 제목·내용은 같지만 세션 난수와 실제 파일 경로가 같을 필요는 없습니다. 비교에서 의미 있는 조건과 매 실행 달라도 되는 값을 구분하면 불필요한 토큰 일치 검사 대신 정책 결과에 집중할 수 있습니다.
starter 실패를 읽습니다
starter의 createVulnerable은 처음에 수정 앱을 그대로 가리킵니다. 그래서 before.failure가 null이며 paired-check의 구조 비교가 실패합니다. 이는 보안 통제를 고칠 과제가 아니라 지정된 결함을 비교 전용 코드에 구성하는 과제입니다. 수정 앱과 regression-pair.cjs는 변경하지 않습니다. 실패 메시지에서 actual null과 기대 객체를 보면 아직 취약 조건이 만들어지지 않았다는 뜻입니다.
조건 분기 구현 순서
먼저 수정 앱 호출 결과를 받고 GET 자료 경로인지 확인합니다. 응답이 404이고 같은 id의 자료가 실제 저장소에 있으며 세션이 유효할 때만 합성 본문을 200으로 반환합니다. 세션 유효 여부는 인증된 /csrf 조회로 확인합니다. 정상 소유자와 무인증 경로는 원래 결과를 유지합니다. 이 코드를 secure-app.cjs에 옮기면 수정 대상을 다시 취약하게 만들므로 파일 위치도 검토합니다.
기계가 읽을 결과를 남깁니다
paired-check는 before와 after의 owner·foreign·anonymous·missing·preserved·failure를 pair.json에 씁니다. scope는 scope-008이며 mode는 in-process입니다. 세션 문자열과 자료 본문은 결과에 넣지 않습니다. 필드 이름을 고정하면 보고서가 타인 거절과 정상 기능 유지의 근거를 정확히 참조할 수 있습니다. 테스트 중 실제 자료 비교는 하되 전달물에는 필요한 관찰만 남기는 방식입니다.
PASS 문장의 범위를 해석합니다
지정된 assertion 실패를 확인한 검사 전체는 정상 종료할 수 있습니다. 따라서 before의 취약 검사 실패와 paired-check 명령 자체의 성공을 구분합니다. PASS identical regression은 두 결과를 기대한 대로 관찰했다는 뜻입니다. 이후 previous-check의 입력·출력·교체 검사까지 성공해도 소켓 연결·브라우저 렌더·TLS 성공을 포함하지 않습니다. 누적 보고서에는 실행 층을 같이 기록합니다.
다음 결함으로 확장할 때
새 결함을 추가한다면 지정 케이스와 관찰 구조를 따로 정의합니다. 임의 오류를 전부 허용하거나 취약 구현의 정상 업무 검사를 삭제하지 않습니다. 수정 후에는 부정 요청 거절, 허용 요청 성공, 원본 보존을 함께 검사합니다. 여러 결함을 하나의 catch로 묶으면 무엇을 재현했는지 알기 어려우므로 비교 단위를 작게 유지합니다. 더 읽기에서는 node:test의 실패 위치와 actual·expected 읽기를 이어서 연습합니다.
짝 결과를 검토할 때 검사 파일의 변경 이력도 살펴봅니다. before에서 기대 200을 사용하고 after에서 기대 404를 사용하는 두 파일은 비교 정책이 일치하지 않습니다. 이번 구조는 하나의 probe가 항상 404를 기대하고 구현만 바꿉니다. 검사 변경이 필요하면 먼저 정책 변경 이유를 기록하고 두 구현에 새 검사를 다시 적용합니다. 수정 코드와 검증 코드를 한꺼번에 바꾼 결과는 검토자가 변경 의미를 확인해야 합니다.
따라하기
같은 기대값으로 비교합니다
const expected=404;for(const actual of [200,404]) console.log(actual===expected?'pass':'foreign_read_denied');실행 결과
foreign_read_denied pass
starter의 지정 실패를 읽습니다
security-paired-tests starter에서 bash check.sh를 실행합니다. before.failure의 actual null과 기대 객체 차이를 찾아 취약 조건이 아직 없음을 확인합니다.
비교 fixture를 완성합니다
README 기준으로 vulnerable-fixture.cjs만 고칩니다. 정상 앱 반환값을 유지하고 인증된 타인의 기존 자료 GET에만 합성 본문을 반환합니다. 검사와 수정 앱은 변경하지 않습니다.
짝 결과를 검사합니다
bash check.sh 성공 뒤 verification-evidence/pair.json을 읽습니다. before의 foreign 200과 지정 실패, after의 foreign 404·owner 200·preserved true를 확인합니다.
확인 문제
실습
vulnerable-fixture.cjs TODO를 완성합니다. 동일 probe의 지정 타인 조회 실패만 재현하고 수정 앱·검사는 유지합니다. 정상 업무·무인증·없는 자료·원본 보존 대조도 통과해야 합니다.
실행 명령
bash check.sh
기대 결과
PASS: identical regression; designated failure only
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 취약점 수정 전후의 테스트 내용을 설명합니다.