시큐어코딩 체화 드릴 _index
목적은 “취약점을 아는 것”이 아니다. 시큐어코딩가이드의 58개 Bad→Good 치환이 코드 리뷰 중 3초 안에 손에서 나오게 만드는 것. 템플릿 체화 드릴이 “요구사항 → 뼈대”를 체화시켰다면, 이 드릴은 “뚫린 코드 → 막힌 코드” 를 체화시킨다. 그래서 이 폴더의 드릴도 템플릿 체화 드릴과 같은 세 가지로 이루어진다. ① 요구사항서 → ② pseudo code 템플릿(보고 쳐서 익힌다) → ③ 테스트 코드(통과하면 끝).
코드는 GitHub repo에서 손으로 친다 → sweetpark/c-playgound
이 노트(요구사항서·pseudo code·테스트·회차 규칙)가 원본이고, repo에는 실제로 타이핑한 .c/.h 파일만 커밋한다.
드릴 폴더명은 sd??_이름 (템플릿 드릴의 d?_이름 과 같은 자리, common/ 헤더도 그대로 재사용).
이 폴더와 템플릿 체화 드릴의 차이
| 템플릿 체화 드릴 | 시큐어코딩 체화 드릴 (이 폴더) | |
|---|---|---|
| 출발점 | 요구사항 한 장 (백지에서 설계) | 이미 뚫려 있는 Bad 코드 한 장 |
| 목표 | 뼈대가 손에서 나온다 | 취약점을 보자마자 치환 패턴이 나온다 |
| 코드 개수 | 정답(Good) 1개 | Bad + Good 두 벌 — Bad는 취약점이 실제로 관찰돼야 함 |
| 시험 | 정상/경계/NULL 3종 | Bad 가 뚫리는지 / Good 이 막는지 / 정상은 안 깨졌는지 4종 |
| 성공 기준 | 뼈대가 10분 안에 나온다 | Bad 코드를 보자마자 무슨 CWE인지 맞히고, Good으로 고치는 데 목표시간 안에 끝낸다 |
둘 다 “손으로 쳐서 체화한다”는 원칙은 같다. 대신 이 폴더는 정답 하나가 아니라 대조군(Bad)이 있어야 실감이 난다.
드릴 노트의 공통 구성 (SD01 ~ SD58)
| 순서 | 내용 | 하는 일 |
|---|---|---|
| ① | 요구사항서 | 어떤 코드가 뚫려 있고 무엇을 만드는지, 신뢰 경계·공격 입력표·함수 선언으로 확정한다. 변형 A / B / C 가 각각 있다 |
| ② | pseudo code 템플릿 | Bad 와 Good 의 뼈대. [ ] 빈칸이 있는 주석이다. 1회차는 전부, 2회차는 번호와 제목만, 3회차는 백지 |
| ③ | 테스트 코드 | Bad 가 뚫리는지 / Bad 도 정상은 되는지 / Good 이 막는지 / Good 이 정상까지 막지 않는지. 이게 PASS 면 끝이다. 빌드는 Makefile(gcc), 순수 gcc 명령은 접힌 참고 |
| ④ | 자주 하는 실수 | 흔히 밟는 지뢰와 그 증상 |
| ⑤ | 모범답안 | 맨 뒤에 접어 둔다 (Bad + Good 한 벌, 변형 A) |
자가 채점표와 회차 기록표는 없앴다
합격 기준은 아래 합격 기준 (공통) 하나다.
재현 전략 — “진짜로 뚫리는 걸 보여줄 수 있는가”
SQL 삽입처럼 실제 DB·LDAP 서버 없이는 취약점을 눈으로 볼 수 없는 항목이 있다. 계열별로 재현 방식을 다르게 쓴다.
| 계열 (Part) | 재현 방식 | 확인 도구 |
|---|---|---|
| 메모리 경계·정수 (1-3·1-4) | 진짜 UB — 그대로 컴파일하면 실제로 깨진다 | ASan/UBSan이 크래시·오버플로우를 리포트 |
| 삽입 (1-1) | 초소형 mock 파서/실행기를 직접 만들어 “질의·명령·경로의 구조가 바뀌는지”를 눈으로 본다 | assert 기반 자체 테스트 |
| 외부제어·우회, 보안기능(Part 2) | mock 세션/파일/키 저장소 객체로 “판단 근거가 어디 있는지”를 재현 | assert 기반 자체 테스트 |
| 시간및상태, 에러처리, 캡슐화, API오용 (Part 3·4·6·7) | 표준 POSIX 함수 그대로 사용, 실패 주입(모의 실패 콜백)으로 경로를 강제 | assert + 경고 0 |
Bad 코드도 "정상적으로 컴파일"되어야 한다
여기 Bad 코드는 문법 오류가 아니라 논리적 결함이다.
-Wall -Wextra -Werror를 통과하는 게 오히려 정상이다. 메모리 계열은expect_crash시험이 자식 프로세스의 크래시를 확인해서 “Bad 가 실제로 뚫린다”를 증명한다. gcc 가 컴파일 타임에 잡아내는 일부 Bad(SD38 · SD47 · SD49)는#pragma GCC diagnostic으로 의도적 재현임을 알린다.
드릴 번호 체계
58개 전부에 SD01~SD58 통합 번호를 새로 매긴다(시큐어코딩가이드의 Part별 번호와는 다른, 진도 추적 전용 번호).
원본 항목·CWE는 각 드릴 노트 상단에 링크로 표기.
| SD 번호 | Part | 원본 항목 수 | 비고 |
|---|---|---|---|
| SD01~SD07 | Part 1-1 삽입 | 7 | mock 파서 필요 |
| SD08~SD10 | Part 1-2 외부제어·우회 | 3 | mock 세션 |
| SD11~SD16 | Part 1-3 메모리 경계 | 6 | 진짜 UB, ASan/UBSan |
| SD17~SD19 | Part 1-4 정수·타입변환 | 3 | 진짜 UB, UBSan |
| SD20~SD23 | Part 2-1 인가·권한 | 4 | mock 세션·UID 테이블 |
| SD24~SD27 | Part 2-2 자격증명 관리 | 4 | mock 키 저장소 |
| SD28~SD35 | Part 2-3 암호화 | 8 | OpenSSL 있으면 실암호화, 없으면 알고리즘 선택 검증만 |
| SD36 | Part 2-4 네트워크 | 1 | 실제 소켓 옵션 |
| SD37~SD39 | Part 3 시간 및 상태 | 3 | open(O_CREAT|O_EXCL) 원자성 검증 |
| SD40~SD42 | Part 4 에러 처리 | 3 | 실패 주입 |
| SD43~SD51 | Part 5 코드 오류 | 9 | ASan(자원)·UBSan(정수) 혼합 |
| SD52~SD53 | Part 6 캡슐화 | 2 | 빌드 매크로 검증 |
| SD54~SD58 | Part 7 API 오용 | 5 | 실제 POSIX 함수 |
전체 진도표는 맨 아래 진도표 참고.
반복 학습 방법 — 3회차 규칙 (템플릿 드릴과 동일)
| 회차 | 볼 수 있는 것 | 하는 일 | 합격선 |
|---|---|---|---|
| 1회차 (모방) | 요구사항서 + pseudo code 전체 | 1. Bad 를 그대로 쳐서 취약점을 관찰한다. 2. Good pseudo 를 한 줄씩 코드로 바꾼다. 3. 테스트를 실행한다. | 테스트 PASS |
| 2회차 (재구성) | 요구사항서 + pseudo code 의 번호와 제목만 | Bad 를 치고 Good 의 뼈대를 기억해서 쓴다. 막히면 그때만 연다 | 연 횟수 3회 이하 + 테스트 PASS |
| 3회차 (재현) | 요구사항서만 (테스트는 먼저 읽는다) | Bad 부터 Good 까지 백지에서 전부 재현 | 목표 시간 안 + 테스트 PASS (경고 0) |
회차마다 요구사항 컨텍스트를 바꾼다(변형 A/B/C) — 같은 코드를 세 번 베끼면 손이 아니라 눈으로 외운 것이다.
간격
D+0 1회차(변형 A) → D+1 2회차(변형 B) → D+4 3회차(변형 C) → D+14 점검(랜덤 변형)하루 루틴 (25분)
[3분] 드릴 노트 읽기 — 신뢰 경계·⚠️한줄경고 다시 확인
[15분] Bad 타이핑 → 빌드/시험 → Good 타이핑 → 재빌드/시험
[5분] 테스트를 돌려 PASS 확인 + 아래 진도표에 체크, 막힌 지점은 한 줄 메모
[2분] 커밋 (아래 컨벤션)repo 사용법 (sweetpark/c-playgound)
빌드는 템플릿 체화 드릴의 공통 Makefile(gcc)을 그대로 쓴다.
각 드릴 노트 §3-1 에 그 드릴의 make 명령이 있다.
# 예: SD01 (소스 목록은 노트마다 다르다)
make D=sd01_sqli V=main SRC="common/mock_db.c sd01_sqli/src/sql_auth.c sd01_sqli/test/test.c"
make D=sd01_sqli V=main SRC="..." check # PASS / FAIL 만 출력커밋 컨벤션은 템플릿 드릴과 동일 — 같은 파일을 덮어쓰고 커밋해 git log가 회차별 변화(Bad만 있음 → Bad+Good → 백지 재현)를 보여주게 한다.
- 커밋 메시지:
<드릴> r<회차>(<변형>) <소요시간> <결과>예:sd01_sqli r1(A) 12min PASS - 1회차는 Bad와 Good을 분리 커밋해도 좋다(
sd01_sqli r1-bad(A) ...→sd01_sqli r1-good(A) ...) — diff 자체가 치환 규칙을 보여주는 기록이 된다.
합격 기준 (공통)
각 드릴의 합격 기준은 테스트가 PASS 인 것이다.
| 항목 | 기준 |
|---|---|
| 테스트 4종 | test_bad_is_vulnerable / test_bad_normal / test_good_blocks_* / test_good_normal 이 모두 통과한다 |
| 치환 규칙 | 치환 규칙과 다른 방식으로 “우연히” 막은 게 아니다 (예: 길이만 늘림) |
| 정상 동작 | Good 이 정상 입력까지 막지 않는다 (과잉 차단 없음) |
| 빌드 위생 | 경고 0 (메모리 계열은 Good 단계 기준) |
| 시간 | 목표 시간 이내 (목표의 2배를 넘으면 그 회차는 다시 한다) |
테스트가 PASS 가 아니면 그 회차는 통과가 아니다. 같은 회차를 다시 한다.
전체 진도표
| SD | 드릴 | 1회차(A) | 2회차(B) | 3회차(C) | D+14 점검 |
|---|---|---|---|---|---|
| 01 | SQL 삽입 | ☐ | ☐ | ☐ | ☐ |
| 02 | 자원 삽입 | ☐ | ☐ | ☐ | ☐ |
| 03 | 크로스사이트 스크립트(XSS) | ☐ | ☐ | ☐ | ☐ |
| 04 | OS 명령어 삽입 | ☐ | ☐ | ☐ | ☐ |
| 05 | LDAP 삽입 | ☐ | ☐ | ☐ | ☐ |
| 06 | 디렉터리 경로 조작 | ☐ | ☐ | ☐ | ☐ |
| 07 | LDAP 처리 | ☐ | ☐ | ☐ | ☐ |
| 08 | 보호 메커니즘 우회 | ☐ | ☐ | ☐ | ☐ |
| 09 | 시스템·구성 설정 외부 제어 | ☐ | ☐ | ☐ | ☐ |
| 10 | 프로세스 제어 | ☐ | ☐ | ☐ | ☐ |
| 11 | 스택 버퍼 오버플로우 | ☐ | ☐ | ☐ | ☐ |
| 12 | 힙 버퍼 오버플로우 | ☐ | ☐ | ☐ | ☐ |
| 13 | 버퍼 시작 이전 쓰기 | ☐ | ☐ | ☐ | ☐ |
| 14 | 범위 초과 읽기 | ☐ | ☐ | ☐ | ☐ |
| 15 | 검사되지 않은 배열 인덱싱 | ☐ | ☐ | ☐ | ☐ |
| 16 | 널 종료 문제 | ☐ | ☐ | ☐ | ☐ |
| 17 | 정수 오버플로우 | ☐ | ☐ | ☐ | ☐ |
| 18 | 의도치 않은 부호 확장 | ☐ | ☐ | ☐ | ☐ |
| 19 | 무부호→부호 변환 오류 | ☐ | ☐ | ☐ | ☐ |
| 20 | 부적절한 인가 | ☐ | ☐ | ☐ | ☐ |
| 21 | 중요 자원 잘못된 권한허용 | ☐ | ☐ | ☐ | ☐ |
| 22 | 잘못된 권한 부여 | ☐ | ☐ | ☐ | ☐ |
| 23 | 최소 권한 적용 위배 | ☐ | ☐ | ☐ | ☐ |
| 24 | 하드코드된 패스워드 | ☐ | ☐ | ☐ | ☐ |
| 25 | 하드코드된 사용자 계정 | ☐ | ☐ | ☐ | ☐ |
| 26 | 하드코드된 암호화 키 | ☐ | ☐ | ☐ | ☐ |
| 27 | 주석문안 주요정보 | ☐ | ☐ | ☐ | ☐ |
| 28 | 취약한 암호화 알고리즘 | ☐ | ☐ | ☐ | ☐ |
| 29 | 중요정보 평문 저장 | ☐ | ☐ | ☐ | ☐ |
| 30 | 충분하지 못한 키 길이 | ☐ | ☐ | ☐ | ☐ |
| 31 | 부적절한 난수 값 | ☐ | ☐ | ☐ | ☐ |
| 32 | 패스워드 평문 저장 | ☐ | ☐ | ☐ | ☐ |
| 33 | 솔트 없는 일방향 해쉬 | ☐ | ☐ | ☐ | ☐ |
| 34 | 부적절한 RSA 패딩 | ☐ | ☐ | ☐ | ☐ |
| 35 | 하드코드된 솔트 | ☐ | ☐ | ☐ | ☐ |
| 36 | 같은 포트 다중 연결 | ☐ | ☐ | ☐ | ☐ |
| 37 | TOCTOU 경쟁조건 | ☐ | ☐ | ☐ | ☐ |
| 38 | 제어되지 않은 재귀 | ☐ | ☐ | ☐ | ☐ |
| 39 | 심볼릭명 매핑 오류 | ☐ | ☐ | ☐ | ☐ |
| 40 | 오류 메시지 정보 노출 | ☐ | ☐ | ☐ | ☐ |
| 41 | 오류상황 대응 부재 | ☐ | ☐ | ☐ | ☐ |
| 42 | 적절하지 않은 예외처리 | ☐ | ☐ | ☐ | ☐ |
| 43 | 널 포인터 역참조 | ☐ | ☐ | ☐ | ☐ |
| 44 | 부적절한 자원 해제 | ☐ | ☐ | ☐ | ☐ |
| 45 | 부호→무부호 변환 오류 | ☐ | ☐ | ☐ | ☐ |
| 46 | 정수를 문자로 변환 | ☐ | ☐ | ☐ | ☐ |
| 47 | 스택 변수 주소 리턴 | ☐ | ☐ | ☐ | ☐ |
| 48 | 매크로의 잘못된 사용 | ☐ | ☐ | ☐ | ☐ |
| 49 | 스택 주소 해제 | ☐ | ☐ | ☐ | ☐ |
| 50 | 스레드 조기 종료 | ☐ | ☐ | ☐ | ☐ |
| 51 | 무한 자원 할당 | ☐ | ☐ | ☐ | ☐ |
| 52 | 남은 디버그 코드 | ☐ | ☐ | ☐ | ☐ |
| 53 | 시스템 데이터 정보노출 | ☐ | ☐ | ☐ | ☐ |
| 54 | DNS lookup 의존 보안결정 | ☐ | ☐ | ☐ | ☐ |
| 55 | 위험 함수 사용 | ☐ | ☐ | ☐ | ☐ |
| 56 | chroot Jail 작업디렉터리 | ☐ | ☐ | ☐ | ☐ |
| 57 | 문자열 관리 오용 | ☐ | ☐ | ☐ | ☐ |
| 58 | 다중 스레드 getlogin() | ☐ | ☐ | ☐ | ☐ |
순서를 건너뛰지 말 것
SD11
SD19(메모리·정수)가 가장 “눈으로 보는 재미”가 크다 — ASan이 실제로 crash 리포트를 찍어준다. 하지만 SD01SD07(삽입)을 먼저 해야 “신뢰 경계” 감각이 잡힌다.
관련 노트
- 시큐어코딩가이드 목록 — 드릴이 체화시키려는 대상(왜 위험한지)
- 템플릿 체화 드릴 목록 — 회차 규칙·공통 Makefile 의 원본
- c-playgound (GitHub) — 실제로 손으로 치는 코드 저장소
- C언어 목록 — 상위 허브