커버리지 유도 퍼즈 테스트

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_REGISTRY를 true로 설정한다면) 코퍼스 레지스트리로부터 코퍼스를 다운로드합니다.
  5. 이전 파이프라인에서 충돌 이벤트를 다운로드합니다.
  6. 파싱된 충돌 이벤트 및 데이터를 gl-coverage-fuzzing-report.json 파일에 출력합니다.
  7. 코퍼스를 업데이트합니다.
    • 작업의 파이프라인 내에서
    • COVFUZZ_USE_REGISTRY를 true로 설정한다면 코퍼스 레지스트리에서

커버리지 유도 퍼즈 테스트의 결과는 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 only)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 지원은 issue 409764에서 계획 중입니다.

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

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

  1. 왼쪽 사이드바에서 검색 또는 이동을 선택하고 프로젝트를 찾습니다.
  2. Secure > Security configuration을 선택합니다.
  3. Coverage Fuzzing 섹션에서 상태를 확인합니다:
    • 구성되지 않음
    • 활성화됨
    • GitLab Ultimate로 업그레이드하라는 프롬프트

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

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

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

  2. 응용 프로그램이 Go로 작성되지 않은 경우 일치하는 퍼징 엔진을 사용하는 도커 이미지를 제공합니다. 예시:

    image: python:latest
    
  3. 가져오는 것으로 Coverage-Fuzzing.gitlab-ci.yml 템플릿 을 포함합니다.

  4. my_fuzz_target 작업을 요구 사항에 맞게 사용자 정의합니다.

Coverage-Guided Fuzzing 구성 예시 추출

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 -- <퍼징 대상>

Coverage-Fuzzing 템플릿은 숨겨진 작업.fuzz_base을 포함하며, 각각의 퍼징 대상에 대해 extends해야 합니다. 각 퍼징 대상은 반드시 별도의 작업을 가져야 합니다. 예를 들어, go-fuzzing-example 프로젝트는 단일 퍼징 대상에 .fuzz_base를 확장하는 작업을 포함하고 있습니다.

숨겨진 작업 .fuzz_base은 여러 YAML 키를 사용하는데, 여러분이 본인의 작업에서 이러한 키를 덮어쓰면 안됩니다. 만일 여러분의 작업에 이러한 키를 포함해야 한다면, 그들의 원본 내용을 복사해야 합니다:

  • before_script
  • artifacts
  • rules

사용 가능한 CI/CD 변수

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

caution
GitLab 보안 스캔 도구의 모든 사용자 정의는 기본 브랜치에 변경 사항을 Merge하기 전에 MR에서 테스트해야 합니다. 이를 하지 않을 경우 예상치 못한 결과가 발생할 수 있으며, 거짓 긍정이 많아질 수 있습니다.
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. GitLab 14.8에서 도입되었습니다.
COVFUZZ_CORPUS_NAME 작업에 사용될 코퍼스의 이름. GitLab 14.8에서 도입되었습니다.
COVFUZZ_GITLAB_TOKEN API 읽기/쓰기 액세스권을 갖는 개인 액세스 토큰 또는 프로젝트 액세스 토큰으로 구성된 환경 변수. GitLab 14.8에서 도입되었습니다.

시드 코퍼스

시드 코퍼스의 파일은 매뉴얼으로 업데이트해야 합니다. 커버리지 지도 퍼징 테스트 작업에서는 이러한 파일들이 업데이트되거나 덮어써지지 않습니다.

출력

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

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

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

코퍼스 레지스트리

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

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

코퍼스를 다운로드하면, 파일명은 초기 업로드될 때 사용된 파일명과 관계없이 artifacts.zip입니다. 이 파일은 CI/CD 파이프라인에서 다운로드할 수 있는 아티팩트 파일과 다릅니다. 또한 Reporter 이상 권한을 가진 프로젝트 멤버는 직접 다운로드 링크를 이용해 코퍼스를 다운로드할 수 있습니다.

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

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

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

코퍼스 레지스트리에 코퍼스 만들기

코퍼스 레지스트리에 코퍼스를 만들기 위해 다음 중 하나를 수행하세요:

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

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

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

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

#### 코퍼스 파일 업로드

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

1. 왼쪽 사이드바에서 **검색 또는 이동하여**를 선택하고 프로젝트를 찾습니다.
2. **Secure > Security configuration**을 선택합니다.
3. **Coverage Fuzzing** 섹션에서 **Manage corpus**를 선택합니다.
4. **New corpus**를 선택합니다.
5. 필드를 완성합니다.
6. **Upload file**을 선택합니다.
7. **Add**을 선택합니다.

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

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

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

전제 조건:

- 프로젝트에서 [Coverage-guided Fuzz 테스트를 활성화](#enable-coverage-guided-fuzz-testing)합니다.

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

## Coverage-guided Fuzz 테스트 보고서

> - [GitLab 13.3에서 도입](https://gitlab.com/gitlab-org/gitlab/-/issues/220062)되었으며 [실험](../../../policy/experiment-beta-support.md#experiment)으로 분류됩니다.

`gl-coverage-fuzzing-report.json` 파일의 형식에 대한 자세한 정보는 [스키마](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/coverage-fuzzing-report-format.json)를 읽어보세요.

커버리지 가이드 퍼징 보고서의 예시:

```json-doc
{
  "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` 값을 설정하십시오.

커버리지 지속적인 지도된 퍼즈 테스트

또한 주된 파이프라인을 차단하지 않고 커버리지 지도된 퍼즈 테스트 작업을 길게 실행하는 것이 가능합니다. 이 구성은 GitLab의 [상위-하위 파이프라인](../../../ci/pipelines/downstream_pipelines.md#parent-child-pipelines)을 사용합니다.

이 시나리오에서 제안되는 워크플로우는 주로 커밋 파이프라인을 빠르게 완료하는 것과 동시에 퍼저가 앱을 완전히 탐색하고 테스트할 수 있는 많은 시간을 제공하는 것입니다. 커버리지 지도된 퍼저가 코드베이스에서 더 깊은 버그를 찾기 위해 일반적으로 장기간 실행되는 퍼징 작업이 필요합니다.

다음은 이 워크플로우에 대한 `.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을 사용합니다. 자세한 내용은 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에 매핑됩니다.
  • 심각도: 취약점의 심각도. 중요, 높음, 중간, 낮음, 정보, 또는 알 수 없음일 수 있습니다.
  • 스캐너: 취약점을 감지한 스캐너(예: 커버리지 퍼징).
  • 스캐너 공급자: 스캔을 실행한 엔진. 커버리지 퍼징의 경우 지원되는 퍼징 엔진 및 언어 중 하나일 수 있습니다.

문제 해결

Unable to extract corpus folder from artifacts zip file 오류

이 오류 메시지가 표시되고 COVFUZZ_USE_REGISTRYtrue로 설정된 경우, 업로드된 코퍼스 파일이 corpus라는 폴더로 압축 해제되도록 확인하십시오.

400 Bad request - Duplicate package is not allowed 오류

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