시큐어코딩 체화 드릴 _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~SD07Part 1-1 삽입7mock 파서 필요
SD08~SD10Part 1-2 외부제어·우회3mock 세션
SD11~SD16Part 1-3 메모리 경계6진짜 UB, ASan/UBSan
SD17~SD19Part 1-4 정수·타입변환3진짜 UB, UBSan
SD20~SD23Part 2-1 인가·권한4mock 세션·UID 테이블
SD24~SD27Part 2-2 자격증명 관리4mock 키 저장소
SD28~SD35Part 2-3 암호화8OpenSSL 있으면 실암호화, 없으면 알고리즘 선택 검증만
SD36Part 2-4 네트워크1실제 소켓 옵션
SD37~SD39Part 3 시간 및 상태3open(O_CREAT|O_EXCL) 원자성 검증
SD40~SD42Part 4 에러 처리3실패 주입
SD43~SD51Part 5 코드 오류9ASan(자원)·UBSan(정수) 혼합
SD52~SD53Part 6 캡슐화2빌드 매크로 검증
SD54~SD58Part 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 점검
01SQL 삽입☐☐☐☐
02자원 삽입☐☐☐☐
03크로스사이트 스크립트(XSS)☐☐☐☐
04OS 명령어 삽입☐☐☐☐
05LDAP 삽입☐☐☐☐
06디렉터리 경로 조작☐☐☐☐
07LDAP 처리☐☐☐☐
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같은 포트 다중 연결☐☐☐☐
37TOCTOU 경쟁조건☐☐☐☐
38제어되지 않은 재귀☐☐☐☐
39심볼릭명 매핑 오류☐☐☐☐
40오류 메시지 정보 노출☐☐☐☐
41오류상황 대응 부재☐☐☐☐
42적절하지 않은 예외처리☐☐☐☐
43널 포인터 역참조☐☐☐☐
44부적절한 자원 해제☐☐☐☐
45부호→무부호 변환 오류☐☐☐☐
46정수를 문자로 변환☐☐☐☐
47스택 변수 주소 리턴☐☐☐☐
48매크로의 잘못된 사용☐☐☐☐
49스택 주소 해제☐☐☐☐
50스레드 조기 종료☐☐☐☐
51무한 자원 할당☐☐☐☐
52남은 디버그 코드☐☐☐☐
53시스템 데이터 정보노출☐☐☐☐
54DNS lookup 의존 보안결정☐☐☐☐
55위험 함수 사용☐☐☐☐
56chroot Jail 작업디렉터리☐☐☐☐
57문자열 관리 오용☐☐☐☐
58다중 스레드 getlogin()☐☐☐☐

순서를 건너뛰지 말 것

SD11SD19(메모리·정수)가 가장 “눈으로 보는 재미”가 크다 — ASan이 실제로 crash 리포트를 찍어준다. 하지만 SD01SD07(삽입)을 먼저 해야 “신뢰 경계” 감각이 잡힌다.


관련 노트