GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

PYSEC-2026-3619

Vulnerability from pysec - Published: 2026-08-04 11:34 - Updated: 2026-08-04 13:36
VLAI
Details

Summary

Thumbor's filters:convolution(<matrix>, <columns>, <should_normalize>) filter passes the user-controlled <columns> value to a C extension (thumbor/ext/filters/_convolution.c) where it is used as a divisor (for % and /) without validating columns > 0. When columns=0, the C code triggers undefined behavior; on x86_64 this reliably results in a fatal divide-by-zero trap (SIGFPE) and crashes the Thumbor process (confirmed on Linux x86_64 and macOS Intel x86_64), causing a remote denial of service.

Details

Root cause

The Python filter accepts columns=0, and the native C extension uses columns_count as a divisor without validating it.

1) Python filter entry point allows columns=0 (thumbor/filters/convolution.py):

@filter_method(
    r"(?:[-]?[\d]+\.?[\d]*[;])*(?:[-]?[\d]+\.?[\d]*)",
    BaseFilter.PositiveNumber,  # accepts 0
    BaseFilter.Boolean,
)
async def convolution(self, matrix, columns, should_normalize=True):
    ...
    imgdata = _convolution.apply(..., matrix, columns, should_normalize)

BaseFilter.PositiveNumber matches "0" (thumbor/filters/__init__.py):

class BaseFilter:
    PositiveNumber = {"regex": r"[\d]+", "parse": int}  # matches "0"
    PositiveNonZeroNumber = {"regex": r"[\d]*[1-9][\d]*", "parse": int}

2) C extension divides/modulos by columns_count without a zero check (thumbor/ext/filters/_convolution.c):

kernel_size = PyTuple_Size(kernel_tuple);
if ((kernel_size % columns_count != 0) ||
    (kernel_size % 2 == 0) ||
    ((kernel_size / columns_count) % 2) == 0) {
    // TODO: error, not a valid kernel
    return NULL;
}

PoC

Test environment

  • Linux x86_64

Preconditions

  • The convolution filter is enabled (it is enabled by default via BUILTIN_FILTERS).
  • Either:
  • /unsafe/ URLs are allowed (ALLOW_UNSAFE_URL=True), OR
  • /unsafe/ is disabled, and the attacker has a valid signed URL (i.e., the attacker is an authorized user/partner, or can obtain signed URLs from a trusted signing service).

Example request (signed URL)

http://<host>:<port>/<url-sign>/400x400/filters:convolution(1;2;1;2;4;2;1;2;1,0,true)/example.jpg

Example request (/unsafe/)

http://<host>:<port>/unsafe/400x400/filters:convolution(1;2;1;2;4;2;1;2;1,0,true)/example.jpg

Impact

  • Remote Denial of Service via process crash (SIGFPE) on x86_64 (confirmed on Linux x86_64 and macOS Intel x86_64).
  • Exploitability depends on deployment:
  • If /unsafe/ is enabled: unauthenticated remote DoS.
  • If /unsafe/ is disabled: the attacker needs a valid signed URL.(i.e., the attacker is an authorized user/partner, or can obtain signed URLs from a trusted signing service)

Suggested remediation

  • In the C extension (thumbor/ext/filters/_convolution.c):
  • Reject columns_count <= 0 before any % or /.
  • In the Python filter (thumbor/filters/convolution.py):
  • Require columns to be non-zero (e.g., use BaseFilter.PositiveNonZeroNumber).
Impacted products
Name purl
thumbor pkg:pypi/thumbor

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "thumbor",
        "purl": "pkg:pypi/thumbor"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "7.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "4.1.3",
        "4.10.0",
        "4.10.1",
        "4.10.2",
        "4.10.3",
        "4.11.0",
        "4.11.1",
        "4.12.0",
        "4.12.1",
        "4.12.2",
        "4.4.1",
        "4.5.3",
        "4.5.4",
        "4.6.0",
        "4.7.0",
        "4.7.1",
        "4.8.0",
        "4.8.1",
        "4.8.2",
        "4.8.3",
        "4.8.4",
        "4.8.5",
        "4.8.6",
        "4.9.0",
        "4.9.1",
        "5.0.0",
        "5.0.0rc1",
        "5.0.0rc2",
        "5.0.1",
        "5.0.2",
        "5.0.3",
        "5.0.4",
        "5.0.5",
        "5.0.6",
        "5.1.0",
        "5.2.0",
        "5.2.1",
        "6.0.0",
        "6.0.0b1",
        "6.0.0b2",
        "6.0.0b3",
        "6.0.0b4",
        "6.0.0b5",
        "6.0.1",
        "6.0.2",
        "6.1.0",
        "6.1.1",
        "6.1.2",
        "6.1.3",
        "6.1.4",
        "6.1.5",
        "6.2.0",
        "6.2.1",
        "6.3.0",
        "6.3.1",
        "6.3.2",
        "6.4.0",
        "6.4.1",
        "6.4.2",
        "6.4.3",
        "6.5.0",
        "6.5.1",
        "6.5.2",
        "6.6.0",
        "6.6.1",
        "6.7.0",
        "6.7.1",
        "6.7.2",
        "6.7.3",
        "6.7.4",
        "6.7.5",
        "6.7.6",
        "7.0.0",
        "7.0.0a1",
        "7.0.0a2",
        "7.0.0a3",
        "7.0.0a4",
        "7.0.0a5",
        "7.0.0b1",
        "7.0.1",
        "7.0.10",
        "7.0.11",
        "7.0.12",
        "7.0.2",
        "7.0.3",
        "7.0.5",
        "7.0.6",
        "7.0.7",
        "7.0.8",
        "7.0.9",
        "7.1.0",
        "7.1.1",
        "7.1.2",
        "7.2.0",
        "7.2.1",
        "7.3.0",
        "7.3.1",
        "7.3.2",
        "7.4.0",
        "7.4.1",
        "7.4.2",
        "7.4.3",
        "7.4.4",
        "7.4.5",
        "7.4.6",
        "7.4.7",
        "7.5.0",
        "7.5.1",
        "7.5.2",
        "7.6.0",
        "7.7.0",
        "7.7.1",
        "7.7.2",
        "7.7.3",
        "7.7.4",
        "7.7.5",
        "7.7.6",
        "7.7.7"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-53503",
    "GHSA-cqjp-jf4r-h5q9"
  ],
  "details": "### Summary\nThumbor\u0027s `filters:convolution(\u003cmatrix\u003e, \u003ccolumns\u003e, \u003cshould_normalize\u003e)` filter passes the user-controlled `\u003ccolumns\u003e` value to a C extension (`thumbor/ext/filters/_convolution.c`) where it is used as a divisor (for `%` and `/`) without validating `columns \u003e 0`. When `columns=0`, the C code triggers undefined behavior; on x86_64 this reliably results in a fatal divide-by-zero trap (SIGFPE) and crashes the Thumbor process (confirmed on Linux x86_64 and macOS Intel x86_64), causing a remote denial of service.\n\n### Details\n#### Root cause\nThe Python filter accepts `columns=0`, and the native C extension uses `columns_count` as a divisor without validating it.\n\n1) Python filter entry point allows `columns=0` (`thumbor/filters/convolution.py`):\n```python\n@filter_method(\n    r\"(?:[-]?[\\d]+\\.?[\\d]*[;])*(?:[-]?[\\d]+\\.?[\\d]*)\",\n    BaseFilter.PositiveNumber,  # accepts 0\n    BaseFilter.Boolean,\n)\nasync def convolution(self, matrix, columns, should_normalize=True):\n    ...\n    imgdata = _convolution.apply(..., matrix, columns, should_normalize)\n```\n`BaseFilter.PositiveNumber` matches `\"0\"` (`thumbor/filters/__init__.py`):\n```python\nclass BaseFilter:\n    PositiveNumber = {\"regex\": r\"[\\d]+\", \"parse\": int}  # matches \"0\"\n    PositiveNonZeroNumber = {\"regex\": r\"[\\d]*[1-9][\\d]*\", \"parse\": int}\n```\n\n2) C extension divides/modulos by `columns_count` without a zero check (`thumbor/ext/filters/_convolution.c`):\n```c\nkernel_size = PyTuple_Size(kernel_tuple);\nif ((kernel_size % columns_count != 0) ||\n    (kernel_size % 2 == 0) ||\n    ((kernel_size / columns_count) % 2) == 0) {\n    // TODO: error, not a valid kernel\n    return NULL;\n}\n```\n\n\n### PoC\n#### Test environment\n- Linux x86_64\n\n#### Preconditions\n- The `convolution` filter is enabled (it is enabled by default via `BUILTIN_FILTERS`).\n- Either:\n  - `/unsafe/` URLs are allowed (`ALLOW_UNSAFE_URL=True`), OR\n  - `/unsafe/` is disabled, and the attacker has a valid signed URL (i.e., the attacker is an authorized user/partner, or can obtain signed URLs from a trusted signing service).\n\n#### Example request (signed URL)\n`http://\u003chost\u003e:\u003cport\u003e/\u003curl-sign\u003e/400x400/filters:convolution(1;2;1;2;4;2;1;2;1,0,true)/example.jpg`\n\n#### Example request (/unsafe/)\n`http://\u003chost\u003e:\u003cport\u003e/unsafe/400x400/filters:convolution(1;2;1;2;4;2;1;2;1,0,true)/example.jpg`\n\n\n### Impact\n- Remote Denial of Service via process crash (SIGFPE) on x86_64 (confirmed on Linux x86_64 and macOS Intel x86_64).\n- Exploitability depends on deployment:\n  - If `/unsafe/` is enabled: unauthenticated remote DoS.\n  - If `/unsafe/` is disabled: the attacker needs a valid signed URL.(i.e., the attacker is an authorized user/partner, or can obtain signed URLs from a trusted signing service)\n\n\n### Suggested remediation\n- In the C extension (`thumbor/ext/filters/_convolution.c`):\n  - Reject `columns_count \u003c= 0` before any `%` or `/`.\n- In the Python filter (`thumbor/filters/convolution.py`):\n  - Require `columns` to be non-zero (e.g., use `BaseFilter.PositiveNonZeroNumber`).",
  "id": "PYSEC-2026-3619",
  "modified": "2026-08-04T13:36:38.176268Z",
  "published": "2026-08-04T11:34:46.338544Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/thumbor/thumbor/security/advisories/GHSA-cqjp-jf4r-h5q9"
    },
    {
      "type": "WEB",
      "url": "https://github.com/thumbor/thumbor/commit/447e192e3fb92e64c12e5354a56a4a7133f69d73"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/thumbor/thumbor"
    },
    {
      "type": "WEB",
      "url": "https://github.com/thumbor/thumbor/releases/tag/7.8.0"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/thumbor"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-cqjp-jf4r-h5q9"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-53503"
    }
  ],
  "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": "Thumbor convolution filter allows divide-by-zero in C extension leading to remote DoS"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…