포스트

[ProjectDiscovery] nuclei-templates CVE 템플릿 기여하기_CVE-2024-4322

LoLLMS WebUI CVE-2024-4322 템플릿을 nuclei-templates에 기여하며 배운 검증과 PR 작성 방식

[ProjectDiscovery] nuclei-templates CVE 템플릿 기여하기_CVE-2024-4322

1. 목표

이번에는 ProjectDiscovery의 nuclei-templates 저장소에 CVE 템플릿을 기여했다.

대상은 LoLLMS WebUI의 CVE-2024-4322.

취약점은 /list_personalities 엔드포인트의 category 파라미터에서 발생하는 path traversal / directory listing exposure이다.

최종 PR은 머지되었고, 머지 직전에 maintainer가 refine commit을 하나 추가했다. pr_image

이 글에서는 다음 내용을 정리한다.

  1. nuclei 템플릿을 작성할 때 어떤 정보를 넣어야 하는지
  2. 로컬 재현 환경을 어떻게 구성했는지
  3. 취약 버전과 패치 버전을 어떻게 검증했는지
  4. maintainer가 최종 템플릿을 어떤 스타일로 다듬었는지
  5. 다음에 nuclei-templates에 기여할 때 맞춰야 할 스타일

이 글의 모든 검증은 로컬 Docker 랩에서 수행했다. 공개 타겟을 대상으로 테스트하지 않았다.

2. 취약점 요약

LoLLMS WebUI v9.6의 /list_personalities 엔드포인트는 category 값을 이용해 personality category 경로를 만든다.

문제는 사용자 입력인 category가 충분히 제한되지 않으면, 원래 의도한 personalities 디렉터리 밖으로 벗어날 수 있다는 점이다.

취약 버전에서 확인한 최소 요청은 다음과 같다.

1
2
/list_personalities?category=
/list_personalities?category=..

정상 baseline 요청은 빈 배열을 반환한다.

1
[]

반면 traversal 요청은 상위 디렉터리의 zoo 디렉터리들을 보여준다.

1
["personalities_zoo","extensions_zoo","bindings_zoo","models_zoo"]

핵심은 민감 파일을 읽는 것이 아니다.

이 템플릿의 목적은 directory listing behavior만 확인하는 것이다.

3. 로컬 재현 환경 구성

검증을 위해 별도의 PoC 랩 저장소를 만들었다.

POC_CVE-2024-4322

환경은 총 4개로 나눴다.

환경버전서비스명포트기대 결과
Linux vulnerablev9.6linux-vuln-server9600match
Linux patchedv9.8linux-patched-server9602no match
Windows vulnerablev9.6windows-vuln-server9601match
Windows patchedv9.8windows-patched-server9603no match

취약 버전은 LoLLMS WebUI v9.6을 사용했다.

패치 버전은 LoLLMS WebUI v9.8을 사용했다.

Dockerfile에서는 lollms-webui 태그와 lollms_core submodule commit을 고정해서, 나중에 다시 빌드해도 같은 버전으로 재현되도록 했다.

확인한 commit은 다음과 같다.

1
2
3
4
5
lollms-webui v9.6 -> e2f2d313cd6ea3fe81dfc4496f985f5b650853b9
lollms-webui v9.8 -> 8a8e3a1c386321f641a014bf8f7029512ccad411

lollms_core for v9.6 -> cfb18f6f1091addf440347322441ab28005a2d3b
lollms_core for v9.8 -> 6f45b1ca828e9f75ca3ed38aaf0d04d0f72a0a49

lollms_core는 submodule이기 때문에 lollms-webui의 태그 commit과 별도로 확인해야 한다.

확인 명령은 다음과 같다.

1
2
git ls-remote --tags https://github.com/ParisNeo/lollms-webui.git refs/tags/v9.6 refs/tags/v9.8
git ls-tree HEAD lollms_core

참고로 LoLLMS WebUI 취약버전/패치버전 릴리즈는 고정된 바이너리 이미지가 아니라 설치 스크립트 형태로 제공되기 때문에, 재현성을 위해 PoC Dockerfile에서 installer를 빌드 시점에 패치했다. 또한 취약/패치 버전의 lollms_core 서브모듈이 lollms_legacy로 이전되어, 과거 커밋 히스토리는 현재 ParisNeo/lollms_legacy.git에서 fetch되도록 PoC 환경에 반영했다.

4. 취약 버전 검증

Linux 취약 서버 실행:

1
2
docker compose up --build -d linux-vuln-server
docker compose logs -f linux-vuln-server

서버가 뜨면 다음 로그가 보인다.

1
Uvicorn running on http://0.0.0.0:9600

control 요청:

1
curl -sS 'http://127.0.0.1:9600/list_personalities?category='

결과:

1
[]

traversal 요청:

1
curl -sS 'http://127.0.0.1:9600/list_personalities?category=..'

결과:

1
["personalities_zoo","extensions_zoo","bindings_zoo","models_zoo"]

Nuclei 실행:

1
nuclei -duc -u http://127.0.0.1:9600 -t templates/CVE-2024-4322.yaml

결과:

1
2
[CVE-2024-4322] [http] [high] http://127.0.0.1:9600/list_personalities?category=..
[INF] Scan completed ... 1 matches found.

Windows 취약 환경에서도 같은 control/traversal behavior와 nuclei match를 확인했다.

5. 패치 버전 검증

패치 버전 검증은 false positive를 줄이기 위한 과정이다.

Linux patched 서버 실행:

1
2
docker compose --profile patched up --build -d linux-patched-server
docker compose --profile patched logs -f linux-patched-server

patched target은 http://127.0.0.1:9602로 접근한다.

control 요청:

1
curl -i 'http://127.0.0.1:9602/list_personalities?category='

결과:

1
HTTP 200 []

traversal 요청:

1
curl -i 'http://127.0.0.1:9602/list_personalities?category=..'

결과:

1
HTTP 400 {"detail":"Detected an attempt of path traversal or command injection. Are you kidding me?"}

이 문구는 임의로 만든 메시지가 아니라, LoLLMS WebUI v9.8의 lollms_core/lollms/security.py에 있는 sanitize_path() 기본 예외 메시지다.

패치 버전에 대해 nuclei를 실행한다.

1
nuclei -duc -u http://127.0.0.1:9602 -t templates/CVE-2024-4322.yaml

결과:

1
[INF] Scan completed ... 0 matches found.

Windows patched 환경에서도 http://127.0.0.1:9603에 대해 0 matches found를 확인했다.

6. 처음 작성한 템플릿 로직

처음 작성한 템플릿은 다음 흐름이었다.

  1. / 요청으로 LoLLMS WebUI인지 확인
  2. /list_personalities?category= 요청
  3. /list_personalities?category=.. 요청
  4. 두 응답이 다르고, traversal 응답에만 "personalities_zoo"가 있는지 확인

의도는 오픈소스가 커스텀되어 발생할수 있는 false positive를 줄이는 것이었다.

대략적인 matcher 조건은 다음과 같았다.

1
2
3
4
5
6
7
8
9
10
11
flow: http(1) && http(2)

matchers:
  - type: dsl
    dsl:
      - 'status_code_1 == 200 && status_code_2 == 200'
      - 'contains(content_type_1, "application/json") && contains(content_type_2, "application/json")'
      - 'body_1 != body_2'
      - '!contains(body_1, "\"personalities_zoo\"")'
      - 'contains(body_2, "\"personalities_zoo\"")'
    condition: and

이 방식은 보수적이지만 요청이 많고, baseline 비교가 들어간다.

7. Maintainer refine에서 바뀐 점

머지 전에 maintainer가 refine commit을 추가했다.

1
https://github.com/projectdiscovery/nuclei-templates/commit/1800ea8c570fb05e5f11568c9e9bb593b0fa4cfa

핵심 변경은 다음과 같다.

  1. info.name에 영향 버전 범위가 들어갔다.

    1
    
     name: LoLLMS WebUI < 9.8 - Path Traversal
    
  2. baseline 비교가 제거됐다.

    기존에는 category=와 category=..를 비교했다.

    최종 템플릿은 취약 요청 하나에서 더 강한 증거를 직접 확인한다.

  3. traversal payload가 바뀌었다.

    1
    
     /list_personalities?category=../..
    
  4. matcher evidence가 바뀌었다.

    기존에는 "personalities_zoo"를 봤다.

    최종 템플릿은 "lollms_core"와 "personal_data"를 함께 본다.

    즉, 단순한 zoo 디렉터리명보다 LoLLMS WebUI의 실제 디렉터리 구조에 가까운 증거를 선택한 것이다.

  5. metadata가 정리됐다.

    1
    2
    3
    4
    
     metadata:
       verified: true
       max-request: 2
       fofa-query: body="LoLLMS WebUI - Welcome"
    

    max-request가 추가됐고, fofa-query도 nuclei-templates 스타일에 맞게 정리됐다.

  6. reference가 줄었다.

    최종 템플릿에는 핵심 reference만 남았다.

    1
    2
    
     huntr
     NVD
    

8. 배운 스타일

이번 PR을 통해 배운 nuclei-templates 스타일은 다음과 같다.

8.1. 이름에는 영향 버전을 넣는다

좋은 예:

1
name: LoLLMS WebUI < 9.8 - Path Traversal

단순히 제품명과 취약점 종류만 쓰는 것보다, reviewer가 한눈에 scope를 이해하기 쉽다.

8.2. product check를 먼저 한다

취약 endpoint만 바로 때리지 않고, 먼저 제품을 확인한다.

1
flow: http(1) && http(2)

첫 번째 요청은 /에서 제품 marker를 확인한다.

1
contains(body, "LoLLMS WebUI - Welcome")

8.3. 가능하면 요청 수를 줄인다

내가 처음 작성한 방식은 baseline과 traversal을 비교했다.

maintainer는 최종적으로 단일 traversal 요청에서 강한 증거를 찾는 방식으로 줄였다.

즉, 좋은 matcher는 다음 방향에 가깝다.

1
2
3
적은 요청 수
+ 제품 고유 marker
+ 취약 동작에서만 나오는 응답

8.4. generic evidence보다 product-specific evidence를 쓴다

"personalities_zoo"도 나쁘지는 않지만, maintainer는 "lollms_core"와 "personal_data"를 선택했다.

이유는 더 구체적인 directory listing signal이기 때문이다.

좋은 evidence는 다음 조건을 만족해야 한다.

  1. 제품과 관련이 깊다.
  2. 우연히 나올 가능성이 낮다.
  3. 민감 파일을 직접 읽지 않는다.
  4. 취약 동작을 충분히 설명한다.

8.5. PR 본문은 짧게 쓴다

PR 본문에는 긴 취약점 분석보다 다음 정보가 중요하다.

  1. 무엇을 탐지하는지
  2. 어떤 endpoint와 parameter인지
  3. 어떤 버전에서 검증했는지
  4. nuclei validation 결과가 무엇인지
  5. patched version에서 false positive가 없는지

Dockerfile 내부 수정이나 설치 우회 세부사항은 PR 본문에 길게 넣지 않는 편이 낫다.

9. PR 메시지에 넣은 검증 정보

PR에는 로컬 Docker 랩에서 검증했다고 적었다.

대상은 다음과 같이 정리했다.

1
2
3
4
Linux vulnerable target, LoLLMS WebUI v9.6: http://127.0.0.1:9600
Linux patched target, LoLLMS WebUI v9.8: http://127.0.0.1:9602
Windows vulnerable target, LoLLMS WebUI v9.6: http://127.0.0.1:9601
Windows patched target, LoLLMS WebUI v9.8: http://127.0.0.1:9603

중요한 점은 public target 정보가 아니라 local target 정보라는 것이다.

그리고 취약 버전과 패치 버전 모두 체크했다.

1
2
- [x] Validated with a host running a vulnerable version and/or configuration
- [x] Validated with a host running a patched version and/or configuration

10. 다음 기여를 위한 체크리스트

다음에 nuclei-templates에 기여한다면 아래 순서로 진행할 것이다.

  1. CVE reference 확인
  2. 취약 endpoint와 parameter 확인
  3. local vulnerable target 구성
  4. local patched target 구성
  5. 제품 확인 request 작성
  6. 최소 취약 request 작성
  7. product-specific matcher 작성
  8. nuclei -validate 수행
  9. vulnerable target에서 match 확인
  10. patched target에서 no match 확인
  11. PR body에 검증 결과만 짧게 정리

11. 마무리

이번 기여에서 가장 크게 배운 점은, 좋은 nuclei template은 단순히 “취약점을 재현하는 요청”이 아니라는 것이다.

좋은 템플릿은 reviewer와 사용자가 빠르게 신뢰할 수 있어야 한다.

그래서 다음 조건이 중요하다.

1
2
3
4
5
작은 요청 수
+ 명확한 제품 확인
+ 고유한 취약 응답 증거
+ local vulnerable/patched 검증
+ 짧고 정확한 PR 설명

처음에는 baseline 비교 방식으로 false positive를 줄이려고 했지만, maintainer는 더 간결한 방식으로 refine했다.

결과적으로 최종 템플릿은 더 짧고, 더 nuclei-templates다운 형태가 되었다.

References:

https://huntr.com/bounties/5116d858-ce00-418c-a5a5-851c5608c209

https://nvd.nist.gov/vuln/detail/CVE-2024-4322

https://osv.dev/vulnerability/CVE-2024-4322

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.