커버리지가 유도된 퍼즈 테스트

Tier: Ultimate Offering: GitLab.com, Self-Managed, GitLab Dedicated

커버리지가 유도된 퍼즈 테스트는 무작위 입력을 응용 프로그램의 계측된 버전에 보내 예상치 못한 동작을 유도하는 노력입니다. 이러한 동작은 해결해야 할 버그를 나타냅니다. GitLab은 퍼즈 테스트를 파이프라인에 추가할 수 있도록 합니다. 이를 통해 다른 QA 프로세스에서 놓칠 수 있는 버그 및 잠재적 보안 이슈를 발견할 수 있습니다.

GitLab Secure의 기타 보안 스캐너 및 내부 테스트 프로세스와 함께 퍼즈 테스트를 사용하는 것을 권장합니다. GitLab CI/CD를 사용하는 경우 CI/CD 워크플로우의 일부로 커버리지가 유도된 퍼즈 테스트를 실행할 수 있습니다.

개요는 커버리지 퍼징를 참조하세요.

커버리지가 유도된 퍼즈 테스트 프로세스

퍼즈 테스트 프로세스는 다음과 같습니다.

  1. 대상 응용 프로그램을 컴파일합니다.
  2. gitlab-cov-fuzz 도구를 사용하여 계측된 응용 프로그램을 실행시킵니다.
  3. 퍼저가 출력하는 예외 정보를 구문 분석합니다.
  4. 이전 파이프라인이나 COVFUZZ_USE_REGISTRYtrue로 설정된 경우 코퍼스를 다운로드합니다. 코퍼스 레지스트리.
  5. 이전 파이프라인에서 충돌 이벤트를 다운로드합니다.
  6. 구문 분석된 충돌 이벤트 및 데이터를 gl-coverage-fuzzing-report.json 파일에 출력합니다.
  7. 작업 파이프라인 또는 COVFUZZ_USE_REGISTRYtrue로 설정된 경우 코퍼스를 업데이트합니다.

커버리지가 유도된 퍼즈 테스트의 결과는 CI/CD 파이프라인에서 사용할 수 있습니다.

지원되는 퍼징 엔진 및 언어

지원되는 언어에 대해 다음 퍼징 엔진을 사용할 수 있습니다.

언어 퍼징 엔진 예시
C/C++ libFuzzer c-cpp-example
Go go-fuzz (libFuzzer support) go-fuzzing-example
Swift libFuzzer swift-fuzzing-example
Rust cargo-fuzz (libFuzzer support) rust-fuzzing-example
Java (Maven 전용)1 Javafuzz (권장) javafuzz-fuzzing-example
Java JQF (비권장) jqf-fuzzing-example
JavaScript jsfuzz jsfuzz-fuzzing-example
Python pythonfuzz pythonfuzz-fuzzing-example
AFL (AFL 위에서 작동하는 모든 언어) AFL afl-fuzzing-example
  1. Gradle 지원은 이슈 409764에서 계획 중입니다.

커버리지가 유도된 퍼즈 테스트 상태 확인

커버리지가 유도된 퍼즈 테스트 상태를 확인하려면 다음을 수행합니다.

  1. 왼쪽 사이드바에서 검색 또는 이동을 선택하고 프로젝트를 찾습니다.
  2. Secure > 보안 구성을 선택합니다.
  3. 커버리지 퍼징 섹션에서 상태는 다음 중 하나입니다:
    • 구성되지 않음
    • 활성화됨
    • GitLab Ultimate로 업그레이드할 것을 권장함

커버리지가 유도된 퍼즈 테스트 활성화

커버리지가 유도된 퍼즈 테스트를 활성화하려면 .gitlab-ci.yml을 편집합니다.

  1. fuzz 단계를 단계 디렉터리에 추가합니다.

  2. 응용 프로그램이 Go로 작성되지 않은 경우 해당하는 퍼징 엔진을 사용하여 매칭되는 퍼징 Docker 이미지를 제공합니다. 예:

    image: python:latest
    
  3. GitLab 설치의 일부로 제공되는 Coverage-Fuzzing.gitlab-ci.yml 템플릿포함합니다.

  4. my_fuzz_target 작업을 수정하여 귀하의 요구 사항을 충족시킵니다.

커버리지가 유도된 퍼징 구성 예시 추출

stages:
  - fuzz

include:
  - template: Coverage-Fuzzing.gitlab-ci.yml

my_fuzz_target:
  extends: .fuzz_base
  script:
    # 퍼징 대상 바이너리를 빌드한 후 gitlab-cov-fuzz로 실행합니다
    # 지원되는 언어로 어떻게 하면 이 작업을 수행할 수 있는지에 대한 예시는 다음 디렉터리을 참조하세요
    - ./gitlab-cov-fuzz run --regression=$REGRESSION -- <your fuzz target>

Coverage-Fuzzing 템플릿에는 각각의 퍼징 대상에 대해 숨은 작업 .fuzz_base가 포함되어 있습니다. 각 퍼징 대상마다 별도 작업을 확장해야 합니다. 예를 들어, go-fuzzing-example 프로젝트에는 단일 퍼징 대상에 대해 .fuzz_base를 확장하는 작업이 하나 포함되어 있습니다.

숨은 작업 .fuzz_base은 귀하의 작업에서 다음과 같은 YAML 키를 사용합니다. 귀하의 작업에서 이러한 키를 덮어쓰지 않아야 하며, 그렇다면 원본 내용을 복사해야 합니다:

  • before_script
  • artifacts
  • rules

사용 가능한 CI/CD 변수

CI/CD 파이프라인에서 커버리지가 유도된 퍼징 테스트를 구성하는 데 다음 변수를 사용하세요.

caution
GitLab 보안 스캔 도구의 모든 사용자 정의는 기본 브랜치로 Merge하기 전에 Merge Request에서 테스트되어야 합니다. 이를 하지 않으면 예기치 않은 결과가 발생할 수 있으며, 거짓 긍정이 많을 수 있습니다.
CI/CD 변수 설명
COVFUZZ_ADDITIONAL_ARGS gitlab-cov-fuzz에 전달되는 매개변수로, 기본적인 퍼징 엔진 동작을 사용자 정의합니다. 완전한 매개변수 디렉터리에 대해서는 퍼징 엔진의 문서를 참조하세요.
COVFUZZ_BRANCH 장기 실행되는 퍼징 작업을 실행할 브랜치. 다른 모든 브랜치에서는 퍼징 회귀 테스트만 실행됩니다. 기본값: 리포지터리의 기본 브랜치.
COVFUZZ_SEED_CORPUS 시드 코퍼스 디렉터리의 경로. 기본값: 없음.
COVFUZZ_URL_PREFIX 오프라인 환경에서 사용할 gitlab-cov-fuzz 리포지터리의 복제본 경로. 이 값을 변경하는 경우 오프라인 환경을 사용할 때에만 변경해야 합니다. 기본값: https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw.
COVFUZZ_USE_REGISTRY 코퍼스를 GitLab 코퍼스 레지스트리에 저장하려면 true로 설정합니다. 이 변수를 true로 설정하는 경우 COVFUZZ_CORPUS_NAMECOVFUZZ_GITLAB_TOKEN 변수가 필요합니다. 기본값: false.
COVFUZZ_CORPUS_NAME 작업에서 사용할 코퍼스의 이름.
COVFUZZ_GITLAB_TOKEN API 읽기/쓰기 권한이 있는 개인 액세스 토큰 또는 프로젝트 액세스 토큰으로 구성된 환경 변수.

씨드 코퍼스

씨드 코퍼스에 있는 파일은 매뉴얼으로 업데이트해야 합니다. coverage-guide 퍼즈 테스트 작업에 의해 업데이트되거나 덮어쓰이지 않습니다.

아웃풋

각 퍼징 단계는 다음과 같은 아티팩트를 출력합니다:

  • gl-coverage-fuzzing-report.json: 커버리지 가이드된 퍼즈 테스트와 그 결과에 대한 세부 내용이 포함된 보고서.
  • artifacts.zip: 이 파일에는 두 개의 디렉터리가 포함되어 있습니다:
    • corpus: 현재 및 이전 작업에서 생성된 모든 테스트 케이스를 포함합니다.
    • crashes: 현재 작업에서 찾은 모든 충돌 이벤트 및 이전 작업에서 수정되지 않은 충돌 이벤트를 포함합니다.

JSON 보고서 파일은 CI/CD 파이프라인 페이지에서 다운로드할 수 있습니다. 자세한 정보는 아티팩트 다운로드를 참조하세요.

코퍼스 레지스트리

코퍼스 레지스트리는 코퍼스의 라이브러리입니다. 프로젝트 레지스트리의 코퍼스는 해당 프로젝트의 모든 작업에서 사용할 수 있습니다. 프로젝트 전역 레지스트리는 기본 옵션인 한 작업당 하나의 코퍼스보다 효율적인 방법입니다.

코퍼스 레지스트리는 패키지 레지스트리를 사용하여 프로젝트의 코퍼스를 저장합니다. 레지스트리에 저장된 코퍼스는 데이터 무결성을 보장하기 위해 숨겨집니다.

코퍼스를 다운로드할 때 파일 이름은 초기 업로드 시 사용된 파일 이름과 상관없이 artifacts.zip로 지정됩니다. 이 파일에는 CI/CD 파이프라인에서 다운로드할 수 있는 아티팩트 파일과 다른 코퍼스만 포함됩니다. 또한, 리포터 이상의 프로젝트 구성원은 직접 다운로드 링크를 사용하여 코퍼스를 다운로드할 수 있습니다.

코퍼스 레지스트리의 세부 정보보기

코퍼스 레지스트리의 세부 정보를 보려면:

  1. 왼쪽 사이드바에서 검색 또는 이동을 선택하고 프로젝트를 찾습니다.
  2. Secure > 보안 구성을 선택합니다.
  3. 커버리지 퍼즈 테스트 섹션에서 코퍼스 관리를 선택합니다.

코퍼스 레지스트리에 코퍼스 생성

코퍼스 레지스트리에 코퍼스를 생성하려면 다음 중 하나를 수행합니다:

  • 파이프라인에서 코퍼스 생성
  • 기존 코퍼스 파일 업로드

파이프라인에서 코퍼스 생성

파이프라인에서 코퍼스를 생성하려면:

  1. .gitlab-ci.yml 파일에서 my_fuzz_target 작업을 편집합니다.
  2. 다음 변수를 설정합니다:
    • COVFUZZ_USE_REGISTRYtrue로 설정합니다.
    • COVFUZZ_CORPUS_NAME을 코퍼스 이름으로 설정합니다.
    • COVFUZZ_GITLAB_TOKEN을 개인 액세스 토큰의 값으로 설정합니다.

my_fuzz_target 작업 실행 후, 코퍼스는 COVFUZZ_CORPUS_NAME 변수가 제공하는 이름으로 코퍼스 레지스트리에 저장됩니다. 코퍼스는 모든 파이프라인 실행 시 업데이트됩니다.

기존 코퍼스 파일 업로드

기존 코퍼스 파일을 업로드하려면:

  1. 왼쪽 사이드바에서 검색 또는 이동를 선택하고 프로젝트를 찾습니다.
  2. Secure > 보안 구성을 선택합니다.
  3. 커버리지 퍼즈 테스트 섹션에서 코퍼스 관리를 선택합니다.
  4. 신규 코퍼스를 선택합니다.
  5. 필드를 작성합니다.
  6. 파일 업로드를 선택합니다.
  7. 추가를 선택합니다.

이제 .gitlab-ci.yml 파일에서 코퍼스를 참조할 수 있습니다. COVFUZZ_CORPUS_NAME 변수에 사용된 값이 업로드된 코퍼스 파일에 지정된 이름과 정확히 일치하는지 확인하세요.

코퍼스 레지스트리에 저장된 코퍼스 사용

코퍼스 레지스트리에 저장된 코퍼스를 사용하려면 이름으로 참조해야 합니다. 관련 코퍼스의 이름을 확인하려면 코퍼스 레지스트리의 세부 정보를 보세요.

전제 조건:

  1. .gitlab-ci.yml 파일에서 다음 변수 설정:
    • COVFUZZ_USE_REGISTRYtrue로 설정합니다.
    • COVFUZZ_CORPUS_NAME을 코퍼스의 이름으로 설정합니다.
    • COVFUZZ_GITLAB_TOKEN을 개인 액세스 토큰의 값으로 설정합니다.

커버리지 가이드된 퍼즈 테스트 보고서

gl-coverage-fuzzing-report.json 파일 형식에 대한 상세한 정보는 스키마를 읽으세요.

커버리지 가이드된 퍼즈 테스트 보고서 예시:

{
  "version": "v1.0.8",
  "regression": false,
  "exit_code": -1,
  "vulnerabilities": [
    {
      "category": "coverage_fuzzing",
      "message": "Heap-buffer-overflow\nREAD 1",
      "description": "Heap-buffer-overflow\nREAD 1",
      "severity": "Critical",
      "stacktrace_snippet": "INFO: Seed: 3415817494\nINFO: Loaded 1 modules   (7 inline 8-bit counters): 7 [0x10eee2470, 0x10eee2477), \nINFO: Loaded 1 PC tables (7 PCs): 7 [0x10eee2478,0x10eee24e8), \nINFO:        5 files found in corpus\nINFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes\nINFO: seed corpus: files: 5 min: 1b max: 4b total: 14b rss: 26Mb\n#6\tINITED cov: 7 ft: 7 corp: 5/14b exec/s: 0 rss: 26Mb\n=================================================================\n==43405==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000001573 at pc 0x00010eea205a bp 0x7ffee0d5e090 sp 0x7ffee0d5e088\nREAD of size 1 at 0x602000001573 thread T0\n    #0 0x10eea2059 in FuzzMe(unsigned char const*, unsigned long) fuzz_me.cc:9\n    #1 0x10eea20ba in LLVMFuzzerTestOneInput fuzz_me.cc:13\n    #2 0x10eebe020 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:556\n    #3 0x10eebd765 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:470\n    #4 0x10eebf966 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:698\n    #5 0x10eec0665 in fuzzer::Fuzzer::Loop(std::__1::vector\u003cfuzzer::SizedFile, fuzzer::fuzzer_allocator\u003cfuzzer::SizedFile\u003e \u003e\u0026) FuzzerLoop.cpp:830\n    #6 0x10eead0cd in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:829\n    #7 0x10eedaf82 in main FuzzerMain.cpp:19\n    #8 0x7fff684fecc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)\n\n0x602000001573 is located 0 bytes to the right of 3-byte region [0x602000001570,0x602000001573)\nallocated by thread T0 here:\n    #0 0x10ef92cfd in wrap__Znam+0x7d (libclang_rt.asan_osx_dynamic.dylib:x86_64+0x50cfd)\n    #1 0x10eebdf31 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:541\n    #2 0x10eebd765 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:470\n    #3 0x10eebf966 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:698\n    #4 0x10eec0665 in fuzzer::Fuzzer::Loop(std::__1::vector\u003cfuzzer::SizedFile, fuzzer::fuzzer_allocator\u003cfuzzer::SizedFile\u003e \u003e\u0026) FuzzerLoop.cpp:830\n    #5 0x10eead0cd in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:829\n    #6 0x10eedaf82 in main FuzzerMain.cpp:19\n    #7 0x7fff684fecc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)\n\nSUMMARY: AddressSanitizer: heap-buffer-overflow fuzz_me.cc:9 in FuzzMe(unsigned char const*, unsigned long)\nShadow bytes around the buggy address:\n  0x1c0400000250: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n  0x1c0400000260: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n  0x1c0400000270: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n  0x1c0400000280: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n  0x1c0400000290: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n=\u003e0x1c04000002a0: fa fa fd fa fa fa fd fa fa fa fd fa fa fa[03]fa\n  0x1c04000002b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x1c04000002c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x1c04000002d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x1c04000002e0: fa fa fa fa

## 커버리지 지도 기반 퍼즈 테스트의 기간

커버리지 지도 기반 퍼즈 테스트의 가능한 기간은 다음과 같습니다:

- 10분 동안 (기본값): 기본 브랜치에 권장됩니다.
- 60분 동안: 개발 브랜치 및 Merge Request에 권장됩니다. 더 긴 기간은 더 많은 커버리지를 제공합니다.
  `COVFUZZ_ADDITIONAL_ARGS` 변수에서 값 `--regression=true`를 설정합니다.

자세한 예는 [Go 커버리지 지도 기반 퍼즈 테스트 예제](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/-/blob/master/.gitlab-ci.yml)를 참조하세요.

### 지속적인 커버리지 지도 기반 퍼즈 테스트

또한 커버리지 지도 기반 퍼즈 테스트 작업을 더 오래 실행하고 주 파이프라인을 차단하지 않을 수 있습니다. 이 구성은 GitLab [부모-자식 파이프라인](../../../ci/pipelines/downstream_pipelines.md#parent-child-pipelines)을 사용합니다.

이 시나리오에서 권장하는 워크플로우는 주 또는 개발 브랜치에서 오랜 시간 동안 비동기식으로 퍼징 작업을 실행하고 다른 모든 브랜치와 MR에 대해 짧은 동기식 퍼징 작업을 실행하는 것입니다. 이렇게 함으로써 커밋당 파이프라인을 빠르게 완료하면서도 퍼저에게 앱을 완전히 탐색하고 테스트할 수 있는 많은 시간을 제공합니다. 커버리지 지도 퍼저에게는 코드베이스 내에서 깊은 버그를 찾기 위해 오랜 시간 동안 작동하는 퍼징 작업이 보통 필요합니다.

이것은 이러한 워크플로우를 위한 `.gitlab-ci.yml` 파일의 일부입니다. 전체 예제는 [Go 퍼징 예제 리포지터리](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/-/tree/continuous_fuzzing)를 참조하세요.

```yaml
sync_fuzzing:
  variables:
    COVFUZZ_ADDITIONAL_ARGS: '-max_total_time=300'
  trigger:
    include: .covfuzz-ci.yml
    strategy: depend
  rules:
    - if: $CI_COMMIT_BRANCH != 'continuous_fuzzing' && $CI_PIPELINE_SOURCE != 'merge_request_event'

async_fuzzing:
  variables:
    COVFUZZ_ADDITIONAL_ARGS: '-max_total_time=3600'
  trigger:
    include: .covfuzz-ci.yml
  rules:
    - if: $CI_COMMIT_BRANCH == 'continuous_fuzzing' && $CI_PIPELINE_SOURCE != 'merge_request_event'

이는 다음 두 작업을 생성합니다:

  1. sync_fuzzing: 블로킹 구성에서 짧은 기간 동안 모든 퍼징 대상을 실행합니다. 이는 간단한 버그를 찾으며 MR이 새로운 버그를 도입하거나 이전 버그가 다시 나타나는지 확인하는 데 도움이 됩니다.
  2. async_fuzzing: 브랜치에서 실행되며 개발 주기 및 MR을 차단하지 않고 코드의 심층적인 버그를 찾습니다.

covfuzz-ci.yml원래 동기식 예제와 동일합니다.

FIPS 활성화된 이진 파일

GitLab 15.0부터 커버리지 퍼징 이진 파일은 Linux x86에서 golang-fips로 컴파일되며 암호화 백엔드로 OpenSSL을 사용합니다. 자세한 내용은 GitLab에서 Go를 사용한 FIPS 규정 준수를 참조하세요.

오프라인 환경

오프라인 환경에서 커버리지 퍼징을 사용하려면 다음을 수행합니다:

  1. 오프라인 GitLab 인스턴스에서 액세스할 수 있는 비공개 리포지터리에 gitlab-cov-fuzz를 복제합니다.

  2. 각 퍼징 단계에 대해 COVFUZZ_URL_PREFIX${NEW_URL_GITLAB_COV_FUZ}/-/raw로 설정합니다. 여기서 NEW_URL_GITLAB_COV_FUZ는 첫 번째 단계에서 설정한 gitlab-cov-fuzz 복제본의 URL입니다.

취약점과 상호 작용

취약점을 발견한 후 해당 취약점을 해결할 수 있습니다. Merge Request 위젯에는 취약점이 표시되며 퍼징 아티팩트를 다운로드할 수 있는 버튼이 포함되어 있습니다. 감지된 취약점 중 하나를 선택하면 해당 취약점의 세부 정보를 확인할 수 있습니다.

커버리지 퍼징 보안 보고서

보안 대시보드에서도 취약점을 볼 수 있으며 여기에서 그룹, 프로젝트 및 파이프라인의 모든 보안 취약점에 대한 개요가 표시됩니다.

취약점을 선택하면 취약점에 대한 모달이 열리며 추가 정보를 제공합니다:

  • 상태: 취약점의 상태. 모든 유형의 취약점과 마찬가지로 커버리지 퍼징 취약점은 감지됨, 확인됨, 해제됨 또는 해결됨일 수 있습니다.
  • 프로젝트: 취약점이 존재하는 프로젝트.
  • 충돌 유형: 코드의 충돌 또는 약점의 유형입니다. 전형적으로 CWE에 매핑됩니다.
  • 충돌 상태: 충돌의 정규화된 버전으로, 충돌의 마지막 세 가지 함수(임의의 주소 없음)를 포함합니다.
  • 스택 추적 스니펫: 스택 추적의 마지막 몇 줄로, 충돌에 대한 세부 정보를 보여줍니다.
  • 식별자: 취약점의 식별자. 이는 CVE 또는 CWE 중 하나에 매핑됩니다.
  • 심각도: 취약점의 심각도. Critical, High, Medium, Low, Info 또는 Unknown일 수 있습니다.
  • 스캐너: 취약점을 감지한 스캐너(예: 커버리지 퍼징)입니다.
  • 스캐너 제공자: 스캔을 수행한 엔진입니다. 커버리지 퍼징의 경우 지원되는 퍼징 엔진 및 언어에 나열된 엔진 중 하나일 수 있습니다.

문제 해결

artifacts zip file에서 corpus folder를 추출할 수 없음이라는 오류

이 오류 메시지가 표시되고 COVFUZZ_USE_REGISTRYtrue로 설정된 경우, 업로드된 코퍼스 파일이 corpus라는 이름의 폴더로 추출되도록 확인합니다.

400 Bad request - Duplicate package is not allowed 오류

COVFUZZ_USE_REGISTRYtrue로 설정하여 퍼징 작업을 실행할 때 이 오류 메시지를 보면 중복이 허용되는지 확인합니다. 자세한 내용은 중복 제네릭 패키지를 참조하세요.