
# CVE-2026-55255 재현용 로컬 Docker 랩 Langflow의 Responses API에서 발견된 IDOR 취약점인 CVE-2026-55255를 재현하기 위한 로컬 Docker 랩입니다. 취약한 버전과 패치된 버전에서 요청 기반 PoC를 통해 교차 사용자 플로우 실행을 검증합니다.
/api/v1/responses IDOR 취약점이 저장소는 Langflow의 OpenAI 호환 Responses API에 영향을 미치는 안전하지 않은 직접 객체 참조(IDOR) 취약점인 CVE-2026-55255를 재현하고 검증하기 위한 로컬 Docker 랩을 포함합니다.
Langflow는 AI 기반 에이전트와 워크플로우를 구축하고 배포하기 위한 오픈소스 플랫폼입니다. 취약한 동작은 /api/v1/responses 엔드포인트에 영향을 미치며, 인증된 공격자가 다른 사용자의 flow UUID를 model 값으로 제공하여 Langflow가 해당 피해자 소유의 flow를 실행하도록 만들 수 있습니다.
이 랩은 두 가지 Langflow 버전을 비교합니다:
| 서비스 | Langflow 버전 | 용도 | URL |
|---|---|---|---|
| vuln | 1.9.0 | 취약한 비교 대상 | http://localhost:7860 |
| patched | 1.9.1 | 패치된 비교 대상 | http://localhost:7861 |
이 로컬 랩에서 시연된 HTTP 검증 경로는 다음과 같습니다:```text Authenticated attacker API key → POST /api/v1/responses → request body sets model to victim-owned flow UUID → vulnerable target executes the victim-owned flow → patched target returns flow_not_found and does not execute the victim-owned flow
취약한 대상에서 공격자가 소유한 API 키는 피해자가 소유한 플로우를 실행할 수 있으며, 응답에는 피해자 전용 마커가 포함됩니다:```text
VICTIM_ONLY_CONTEXT_55255_VULN
패치된 대상에서는 동일한 사용자 간 요청이 피해자 마커를 반환하지 않고 OpenAI 스타일의 오류 본문을 반환합니다:```json {"error":{"message":"Flow with id '' not found","type":"invalid_request_error","code":"flow_not_found"}}
This lab validates the vulnerable-versus-patched HTTP behavior using Langflow 1.9.0 and Langflow 1.9.1.
The lab is intentionally scoped to local Docker services. It does not target external systems and does not include credential theft, database dumping, destructive payloads, external callbacks, malware, persistence, or post-exploitation activity.
## Verified Facts
| Claim | Evidence | How to verify in this lab |
| ----- | -------- | ------------------------- |
| CVE-2026-55255 affects Langflow's `/api/v1/responses` endpoint. | GitHub Advisory GHSA-qrpv-q767-xqq2 describes an IDOR in `/api/v1/responses`. | Review the References section and run the PoC against both local targets. |
| GitHub Advisory lists affected versions as `< 1.9.1` and patched version as `1.9.1`. | GitHub Advisory GHSA-qrpv-q767-xqq2. | Compare the vulnerable and patched target versions in `docker-compose.yml`. |
| Some downstream vulnerability sources disagree on the exact fixed version. | GitHub/GitLab list `1.9.1` as fixed; some downstream intelligence pages mention `1.9.2` or contain mixed wording. | Review the References section and rely on the lab validation for the tested 1.9.1 behavior. |
| This lab uses Langflow 1.9.0 as the vulnerable comparison target. | The `vuln` service uses `langflowai/langflow:1.9.0`. | Inspect `docker-compose.yml` and run `docker compose ps`. |
| This lab uses Langflow 1.9.1 as the patched comparison target. | The `patched` service uses `langflowai/langflow:1.9.1`. | Inspect `docker-compose.yml` and run `docker compose ps`. |
| Langflow's Responses API uses `POST /api/v1/responses`. | Langflow documentation describes the OpenAI-compatible Responses API endpoint. | Run the PoC or manual curl request against `/api/v1/responses`. |
| Langflow's Responses API accepts a flow ID as the `model` value. | Langflow documentation states that the `model` value is replaced with a `flow_id`. | Inspect the PoC request body. |
| Langflow API requests require an API key through `x-api-key`. | Langflow API documentation describes API key authentication with the `x-api-key` header. | Inspect the PoC request headers. |
| The PoC is request-based. | `poc/validate_idor.py` sends HTTP requests and does not call Docker, Docker Compose, shell commands, or container APIs. | Inspect `poc/validate_idor.py`. |
| The vulnerable target executes a victim-owned flow with an attacker-owned API key. | The vulnerable response returns `VICTIM_ONLY_CONTEXT_55255_VULN`. | Run the vulnerable PoC command with the victim flow ID and attacker API key. |
| The patched target blocks the same cross-user execution path. | The patched response returns `error.code = flow_not_found` and does not return the victim marker. | Run the patched PoC command with the victim flow ID and attacker API key. |
## Assumptions and Unknowns
This lab uses Langflow 1.9.0 as the vulnerable comparison target because GitHub Advisory GHSA-qrpv-q767-xqq2 identifies versions before 1.9.1 as affected, and local testing confirmed the vulnerable behavior in 1.9.0.
This lab uses Langflow 1.9.1 as the patched comparison target because GitHub Advisory GHSA-qrpv-q767-xqq2 lists 1.9.1 as the patched version, and local testing confirmed that 1.9.1 blocks the tested cross-user `/api/v1/responses` execution path with `flow_not_found`.
There is a version discrepancy between sources. GitHub and GitLab advisories list versions before 1.9.1 as affected and 1.9.1 as fixed. Some downstream vulnerability intelligence pages mention 1.9.2 or contain mixed wording around the fixed version. This repository documents that discrepancy and validates the tested behavior directly:```text
Langflow 1.9.0
→ attacker API key + victim flow UUID
→ victim marker returned
→ vulnerable behavior observed
Langflow 1.9.1
→ attacker API key + victim flow UUID
→ flow_not_found
→ victim marker not returned
→ blocked behavior observed
이 랩은 공격자가 이미 피해자의 flow UUID를 알고 있다고 가정합니다. PoC는 flow ID를 무차별 대입하지 않으며, flow를 열거하거나 피해자의 flow ID를 발견하려고 시도하지 않습니다.
이 랩은 다음의 관찰 가능한 HTTP 동작에 초점을 맞춥니다:```text POST /api/v1/responses
이 요청 형태로:```json
{
"model": "<victim-flow-id>",
"input": "cross-user CVE-2026-55255 validation request",
"stream": false
}
이 랩은 취약한 대상에서 비인가 교차 사용자 플로우 실행을 시연하고, 패치된 대상에서는 차단된 동작을 시연합니다.
이 랩은 다음을 시연하지 않습니다:
CVE-2026-55255의 근본 원인은 Langflow의 플로우 조회 로직에 존재하는 인가 갭입니다.
/api/v1/responses 엔드포인트는 model 필드를 통해 플로우 UUID를 수신합니다. 취약한 버전에서는 get_flow_by_id_or_endpoint_name() 내부의 UUID 조회 경로가 기본 키(PK)로 Flow 객체를 직접 로드할 수 있었으며, 조회된 Flow.user_id가 인증된 API 키 사용자와 일치하는지 검증하지 않았습니다.
취약한 동작은 다음과 같이 요약할 수 있습니다:```text Attacker owns API key → attacker sends POST /api/v1/responses → model contains victim-owned flow UUID → flow resolver loads Flow by UUID → resolver does not enforce Flow.user_id == attacker_user.id → response endpoint executes the victim-owned flow → attacker receives victim flow output
패치된 동작은 다음과 같이 요약할 수 있습니다:```text
Attacker owns API key
→ attacker sends POST /api/v1/responses
→ model contains victim-owned flow UUID
→ flow resolver loads candidate Flow
→ resolver compares Flow.user_id with authenticated API-key user id
→ cross-user lookup is treated as not found
→ response endpoint returns flow_not_found
→ victim-owned flow is not executed
보안 문제는 공격자가 자신의 flow를 사용하여 /api/v1/responses를 호출할 수 있다는 점이 아닙니다. 이는 예상된 동작입니다. 문제는 낮은 권한의 인증된 사용자가 피해자 flow UUID를 제공할 때 엔드포인트가 다른 사용자가 소유한 flow를 실행하도록 만들 수 있다는 것입니다.
보안 교훈은 다음과 같습니다.```text Object lookup by UUID is not authorization. Every object lookup used by an authenticated API route must be scoped to the authenticated principal or followed by a strict ownership check before the object is used.
## 소스 코드 분석
소스 레벨 문제는 Langflow `v1.9.0`과 `v1.9.1`을 비교하여 확인했습니다.
검토에 사용된 소스 태그는 다음과 같습니다:
| 버전 | Git 커밋 |
| ------- | ---------- |
| v1.9.0 | `a47f2ad17eb662e940c550cfccb64a87dddd7e0b` |
| v1.9.1 | `dc26d19c1ed5b2779a3a759f78a747f47089c534` |
관련 헬퍼는 다음과 같습니다:```python
async def get_flow_by_id_or_endpoint_name(flow_id_or_name: str, user_id: str | UUID | None = None) -> FlowRead:
취약한 UUID 분기에서 흐름이 ID로 로드되었습니다:```python flow_id = UUID(flow_id_or_name) flow = await session.get(Flow, flow_id)
보안 관련 누락된 검사는 다음과 같았습니다:```python
flow.user_id == authenticated_user.id
소유자 확인이 없으면 유효한 flow UUID만으로도 다른 사용자에게 속한 Flow 객체를 확인할 수 있었습니다.