퍼징은 결함을 찾기 위해 예상치 못한 입력을 소프트웨어에 자동으로 넣는 테스트 기법입니다. 이 페이지는 작동 방식과 자체 코드에 적용하는 방법을 설명합니다(타인의 시스템 테스트는 다루지 않습니다).
퍼징이란
OWASP는 퍼징을 버그, 취약점 또는 예상치 못한 동작을 식별하기 위한 소프트웨어 테스트 기법으로 설명합니다. 이는 잘못되었거나, 예상치 못했거나, 일부만 잘못된 입력을 자동으로 공급하고 프로그램이 오작동하는지 살피는 방식입니다. NIST 용어집(NIST SP 800-95 인용)도 마찬가지로, 퍼저라고 불리는 도구가 테스트 대상에 입력 조합을 제출해 그 반응을 드러내는 것을 퍼즈 테스팅으로 설명합니다.
이 기법의 강점은 시스템이 어떻게 동작해야 하는지에 대한 가정이 적다는 점입니다. 사람이 직접 작성한 테스트는 누군가 떠올린 경우만 다룹니다. 퍼저는 아무도 상상하지 못한 입력에서 깨지는 지점을 찾을 수 있습니다.
작동 방식
대부분의 최신 퍼저는 커버리지 기반입니다. 실행된 코드를 측정하고, 새 코드에 도달한 입력을 보관한 뒤 이를 변형해 다음 입력을 만듭니다. 이는 무작위 입력만 쓸 때보다 프로그램 깊숙이 도달하게 합니다. LLVM의 libFuzzer는 테스트 대상 라이브러리에 링크되어 프로세스 내부에서 동작하는, 커버리지 기반의 진화형 퍼징 엔진으로 문서화되어 있습니다.
주요 도구
| 도구 | 주요 대상 | 참고 |
|---|---|---|
| libFuzzer | C/C++ 및 기타(LLVM) | 대상에 링크됩니다. 문서에 따르면 원 개발진은 Centipede 엔진으로 옮겨갔고, 현재는 중요한 버그 수정만 받습니다 |
| AFL++ | C/C++ 및 기타 | 널리 쓰이는 커버리지 기반 퍼저입니다 |
| Go 내장 퍼징 | Go(1.18 이상) | go test -fuzz로 실행합니다. 실패한 입력은 testdata에 저장되고 일반 테스트에서 재실행됩니다 |
| OSS-Fuzz | 오픈 소스 소프트웨어 | Google의 무료 지속 퍼징 서비스로, 여러 엔진과 새니타이저를 사용합니다 |
libFuzzer 퍼즈 타깃은 입력 바이트와 길이를 받는 함수(LLVMFuzzerTestOneInput)입니다. Go에서는 *testing.F를 받는 FuzzXxx라는 이름의 테스트 함수를 작성하고 f.Add로 시드 입력을 추가합니다.
자체 소프트웨어에 적용하는 방법
- 입력을 해석하는 함수를 고릅니다(파일 형식 파서, 설정 로더, 프로토콜 핸들러 등)
- 입력을 그 함수에 그대로 넘기는 작은 퍼즈 타깃을 작성합니다. 빠르고 결정적으로 동작하게 하며, 호출 사이에 상태를 유지하지 마세요
- 일반적인 유효 입력 몇 개를 시드로 추가합니다
- AddressSanitizer 같은 새니타이저를 켜고 정해진 시간 동안 실행합니다(Go에서는
-fuzztime으로 길이를 지정합니다) - 실패한 각 입력을 재현 가능한 테스트로 만들고, 수정 후 회귀 테스트로 보관합니다
- 계속 진행하려면 CI에서 짧은 세션을 실행하고, 오픈 소스라면 OSS-Fuzz 참여를 고려하세요
Go 퍼징은 실행당 1초 제한이 있으며, 커버리지 가이드는 AMD64와 ARM64에서 동작합니다. 환경별 제한은 공식 문서에서 확인하세요.
직접 관리하는 소프트웨어만 퍼징하세요
퍼징은 잘못된 형식의 입력을 계속 대량으로 보냅니다. 테스트할 권한이 없는 서비스나 서버에 이를 수행하면 권한 없는 접근이나 서비스 방해로 간주될 수 있습니다. 직접 개발하거나 관리하는 소프트웨어, 자신의 환경에서만 수행하세요.
할 수 있는 일과 할 수 없는 일
OWASP는 퍼징이 수동 코드 리뷰와 디버깅을 대체하지 않고 보완한다고 말합니다. 또한 퍼저는 단순한 버그를 찾는 경향이 있으며, 블랙박스 테스트에서는 발견 사항의 영향을 판단하기가 더 어렵다고 설명합니다.
따라서 퍼징은 "기계가 코드가 깨지는 방법을 찾게 하는" 일에는 강하지만, 권한 누락 같은 설계 수준의 문제에는 약합니다. IDOR처럼 이런 문제는 의도한 규칙을 검사하는 테스트에 의존하세요.
이 사이트의 견해: 입력을 찾는 것은 절반의 작업이며, 회귀 테스트로 보관해야 합니다
중요한 것은 퍼저를 실행하는 것만이 아니라, 찾아낸 것을 회귀 테스트로 저장하는 것입니다. 실패한 입력을 testdata에 저장하고 일반 테스트에서 재실행하는 Go 내장 퍼징이 이 아이디어를 따릅니다. 이는 이후 변경으로 수정된 버그가 다시 나타나지 않도록 막아줍니다. 입력을 해석하는 코드를 소유하고 있다면, 현실적인 첫걸음은 함수 하나를 퍼징하는 것입니다.
출처(1차 자료)
- OWASP Community, "Fuzzing" — owasp.org (정의, 특성, 한계)
- NIST CSRC 용어집, "fuzz testing" — csrc.nist.gov (출처: NIST SP 800-95)
- LLVM, "libFuzzer" — llvm.org (작동 방식, 퍼즈 타깃, 유지보수 상태)
- Go, "Fuzzing" — go.dev (Go 1.18 이상, 실행 방법, 제한)
- Google, "OSS-Fuzz" — google.github.io (개요, 지원 엔진). 버그 수는 시간에 따라 변하므로 여기서는 인용하지 않습니다. AFL++의 현재 사용법은 공식 문서를 확인하세요
다음으로 읽을거리
- 용어집: DAST(동적 애플리케이션 보안 테스팅)란 · 경로 순회란 · CVE란
- 용어집: OWASP Top 10이란
- 가이드: OSV-Scanner로 의존성 감사하기