{"id":"CVE-2026-53500","aliases":["DEBIAN-CVE-2026-53500","EUVD-2026-51597","GHSA-6x26-6r6f-m537","PYSEC-2026-3617","UBUNTU-CVE-2026-53500"],"status":"published","title":"Thumbor treats ALLOWED_SOURCES string patterns as unescaped regex, allowing hostname bypass via wildcard dot","description":"Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, the ALLOWED_SOURCES configuration passes plain strings to re.match() without escaping dots, so a hostname differing at dot positions can match the allowlist. This issue is fixed in 7.8.0.","severity":{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00498,"percentile":0.4303,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-1333","CWE-918"],"published":"2026-07-31T18:35:54Z","modified":"2026-10-28T02:30:44Z","fixes":[{"ecosystem":"PyPI","package":"thumbor","fixed_in":["7.8.0"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"thumbor","fixed_in":["7.8.0-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"thumbor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"7.7.7"},{"fixed":"7.8.0"}]}]},{"source":"osv","ecosystem":"Debian:14","package":"thumbor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.8.0-1"}]}],"versions":["6.3.2-3","6.3.2-4","6.5.1-1","6.5.1-2","6.5.1-3","6.5.1-4","6.5.1-5","6.5.1-6","6.5.1-7","6.7.0-1","7.7.7-1","7.7.7-2","7.7.7-3","7.7.7-4","7.7.7-5","7.7.7-6"]},{"source":"osv","ecosystem":"PyPI","package":"thumbor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.8.0"}]}],"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"]},{"source":"osv","ecosystem":"PyPI","package":"thumbor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.8.0"}]}],"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"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"thumbor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["7.7.7-4","7.7.7-5"]}],"products":[{"vendor":"thumbor","product":"thumbor"}],"references":[{"url":"https://github.com/thumbor/thumbor/security/advisories/GHSA-6x26-6r6f-m537","tags":["x_refsource_CONFIRM","exploit","advisory","web","report"]},{"url":"https://github.com/thumbor/thumbor/commit/68876715350c6c8f49c324e5515e64908830aed7","tags":["x_refsource_MISC","fix","web","report"]},{"url":"https://github.com/thumbor/thumbor/releases/tag/7.8.0","tags":["x_refsource_MISC","web","report"]},{"url":"https://github.com/advisories/GHSA-6x26-6r6f-m537","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/53xxx/CVE-2026-53500.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-53500","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-53500","tags":["advisory"]},{"url":"https://github.com/thumbor/thumbor","tags":["package"]},{"url":"https://pypi.org/project/thumbor","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-53500","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-53500","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-53500","title":"Thumbor treats ALLOWED_SOURCES string patterns as unescaped regex, allowing hostname bypass via wildcard dot","description":"Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, the ALLOWED_SOURCES configuration passes plain strings to re.match() without escaping dots, so a hostname differing at dot positions can match the allowlist. This issue is fixed in 7.8.0.","status":"published","published":"2026-07-31T18:35:54.234Z","modified":"2026-07-31T23:30:44.046Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high","assessor":"cna"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T14:19:59.761005Z"}],"euvd":[{"id":"EUVD-2026-51597","description":"Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, the ALLOWED_SOURCES configuration passes plain strings to re.match() without escaping dots, so a hostname differing at dot positions can match the allowlist. This issue is fixed in 7.8.0.","status":"published","published":"2026-07-31T18:35:54Z","modified":"2026-07-31T23:30:44Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high"}],"updated_at":"2026-11-02T18:02:25.159179Z"}],"ghsa":[{"id":"GHSA-6x26-6r6f-m537","title":"Thumbor treats ALLOWED_SOURCES string patterns as unescaped regex, allowing hostname bypass via wildcard dot","description":"## Summary\n\nThe `ALLOWED_SOURCES` configuration is meant to restrict which hosts Thumbor's HTTP loader may fetch images from. Plain-string entries in that list (the overwhelming majority of real-world and documented configurations) are passed directly to `re.match()` without escaping. Because `.` is a regex wildcard, every dot in a domain name becomes a bypass vector: `s.glbimg.com` silently matches `sXglbimgYcom`, `sAglbimg.com`, and any other hostname that differs only at a dot position. This undermines the primary SSRF defence that `ALLOWED_SOURCES` is intended to provide.\n\n## Affected component\n\n`thumbor/loaders/http_loader.py` — `validate()`\n\n## Proof of concept\n\n```python\nimport re\nfrom thumbor.config import Config\nfrom thumbor.context import Context\nfrom thumbor.loaders import http_loader as loader\n\nconfig = Config()\nconfig.ALLOWED_SOURCES = [\"s.glbimg.com\"]   # typical user config\nctx = Context(None, config, None)\n\n# These should be blocked — both return True due to the unescaped dot\nprint(loader.validate(ctx, \"http://sXglbimgYcom/secret.jpg\"))  # True ← bypass\nprint(loader.validate(ctx, \"http://sAglbimg.com/secret.jpg\"))  # True ← bypass\n\n# Legitimate origin — correctly allowed\nprint(loader.validate(ctx, \"http://s.glbimg.com/logo.jpg\"))    # True ← correct\n```\n\n## Root cause\n\n`thumbor/loaders/http_loader.py` (before fix):\n\n```python\nfor pattern in context.config.ALLOWED_SOURCES:\n    if isinstance(pattern, Pattern):\n        match = url\n    else:\n        pattern = f\"^{pattern}$\"   # \u003c-- dots not escaped, act as regex wildcard\n        match = res.hostname\n\n    if re.match(pattern, match):\n        return True\n```\n\n## Impact\n\nAn attacker who can influence the image source URL passed to Thumbor can fetch images from arbitrary hosts, bypassing the `ALLOWED_SOURCES` allowlist.\n\n**Preconditions:**\n\n- `ALLOWED_SOURCES` contains at least one plain-string entry (the common case; all official documentation examples use plain strings).\n- The attacker can supply or influence the image URL — true whenever  `ALLOW_UNSAFE_URL = True` (the default), or when the application forwards user input to a signed URL endpoint.\n\n## Fix\n\nApply `re.escape()` to plain-string patterns before compiling them, so every\ncharacter is matched literally:\n\n```python\nelse:\n    pattern = f\"^{re.escape(pattern)}$\"   # dots and other metacharacters are now literal\n    match = res.hostname\n```\n\nThis is a one-call addition with no breaking change for correctly written configurations. Users who need real regular-expression behaviour should supply a compiled pattern (`re.compile(r\"s\\.glbimg\\.com\")`), which is already handled by the existing `isinstance(pattern, Pattern)` branch and is unaffected by this change.\n\nThe `ALLOWED_SOURCES` docstring in `config.py` was also updated to document the two-mode behaviour explicitly.","status":"published","published":"2026-07-31T18:36:03Z","modified":"2026-09-30T18:44:16Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high"}],"cwes":["CWE-1333"],"updated_at":"2026-11-02T14:41:23.783773Z"}],"nvd":[{"id":"CVE-2026-53500","description":"Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, the ALLOWED_SOURCES configuration passes plain strings to re.match() without escaping dots, so a hostname differing at dot positions can match the allowlist. This issue is fixed in 7.8.0.","status":"published","published":"2026-07-31T19:17:09.247Z","modified":"2026-09-08T20:51:43.49Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high","assessor":"secondary"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T14:12:34.290716Z"}],"osv":[{"id":"CVE-2026-53500","title":"Thumbor treats ALLOWED_SOURCES string patterns as unescaped regex, allowing hostname bypass via wildcard dot","description":"Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, the ALLOWED_SOURCES configuration passes plain strings to re.match() without escaping dots, so a hostname differing at dot positions can match the allowlist. This issue is fixed in 7.8.0.","status":"published","published":"2026-07-31T18:35:54.234Z","modified":"2026-10-28T02:30:44.335059274Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T15:45:08.132336Z"},{"id":"DEBIAN-CVE-2026-53500","description":"Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, the ALLOWED_SOURCES configuration passes plain strings to re.match() without escaping dots, so a hostname differing at dot positions can match the allowlist. This issue is fixed in 7.8.0.","status":"published","published":"2026-07-31T19:17:09.247Z","modified":"2026-10-01T03:03:18.91775922Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high"}],"updated_at":"2026-11-02T15:45:57.236551Z"},{"id":"GHSA-6x26-6r6f-m537","title":"Thumbor treats ALLOWED_SOURCES string patterns as unescaped regex, allowing hostname bypass via wildcard dot","description":"## Summary\n\nThe `ALLOWED_SOURCES` configuration is meant to restrict which hosts Thumbor's HTTP loader may fetch images from. Plain-string entries in that list (the overwhelming majority of real-world and documented configurations) are passed directly to `re.match()` without escaping. Because `.` is a regex wildcard, every dot in a domain name becomes a bypass vector: `s.glbimg.com` silently matches `sXglbimgYcom`, `sAglbimg.com`, and any other hostname that differs only at a dot position. This undermines the primary SSRF defence that `ALLOWED_SOURCES` is intended to provide.\n\n## Affected component\n\n`thumbor/loaders/http_loader.py` — `validate()`\n\n## Proof of concept\n\n```python\nimport re\nfrom thumbor.config import Config\nfrom thumbor.context import Context\nfrom thumbor.loaders import http_loader as loader\n\nconfig = Config()\nconfig.ALLOWED_SOURCES = [\"s.glbimg.com\"]   # typical user config\nctx = Context(None, config, None)\n\n# These should be blocked — both return True due to the unescaped dot\nprint(loader.validate(ctx, \"http://sXglbimgYcom/secret.jpg\"))  # True ← bypass\nprint(loader.validate(ctx, \"http://sAglbimg.com/secret.jpg\"))  # True ← bypass\n\n# Legitimate origin — correctly allowed\nprint(loader.validate(ctx, \"http://s.glbimg.com/logo.jpg\"))    # True ← correct\n```\n\n## Root cause\n\n`thumbor/loaders/http_loader.py` (before fix):\n\n```python\nfor pattern in context.config.ALLOWED_SOURCES:\n    if isinstance(pattern, Pattern):\n        match = url\n    else:\n        pattern = f\"^{pattern}$\"   # \u003c-- dots not escaped, act as regex wildcard\n        match = res.hostname\n\n    if re.match(pattern, match):\n        return True\n```\n\n## Impact\n\nAn attacker who can influence the image source URL passed to Thumbor can fetch images from arbitrary hosts, bypassing the `ALLOWED_SOURCES` allowlist.\n\n**Preconditions:**\n\n- `ALLOWED_SOURCES` contains at least one plain-string entry (the common case; all official documentation examples use plain strings).\n- The attacker can supply or influence the image URL — true whenever  `ALLOW_UNSAFE_URL = True` (the default), or when the application forwards user input to a signed URL endpoint.\n\n## Fix\n\nApply `re.escape()` to plain-string patterns before compiling them, so every\ncharacter is matched literally:\n\n```python\nelse:\n    pattern = f\"^{re.escape(pattern)}$\"   # dots and other metacharacters are now literal\n    match = res.hostname\n```\n\nThis is a one-call addition with no breaking change for correctly written configurations. Users who need real regular-expression behaviour should supply a compiled pattern (`re.compile(r\"s\\.glbimg\\.com\")`), which is already handled by the existing `isinstance(pattern, Pattern)` branch and is unaffected by this change.\n\nThe `ALLOWED_SOURCES` docstring in `config.py` was also updated to document the two-mode behaviour explicitly.","status":"published","published":"2026-07-31T18:36:03Z","modified":"2026-08-04T14:40:34.274877182Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high"}],"cwes":["CWE-1333"],"updated_at":"2026-11-02T15:46:40.308826Z"},{"id":"PYSEC-2026-3617","title":"Thumbor treats ALLOWED_SOURCES string patterns as unescaped regex, allowing hostname bypass via wildcard dot","description":"## Summary\n\nThe `ALLOWED_SOURCES` configuration is meant to restrict which hosts Thumbor's HTTP loader may fetch images from. Plain-string entries in that list (the overwhelming majority of real-world and documented configurations) are passed directly to `re.match()` without escaping. Because `.` is a regex wildcard, every dot in a domain name becomes a bypass vector: `s.glbimg.com` silently matches `sXglbimgYcom`, `sAglbimg.com`, and any other hostname that differs only at a dot position. This undermines the primary SSRF defence that `ALLOWED_SOURCES` is intended to provide.\n\n## Affected component\n\n`thumbor/loaders/http_loader.py` — `validate()`\n\n## Proof of concept\n\n```python\nimport re\nfrom thumbor.config import Config\nfrom thumbor.context import Context\nfrom thumbor.loaders import http_loader as loader\n\nconfig = Config()\nconfig.ALLOWED_SOURCES = [\"s.glbimg.com\"]   # typical user config\nctx = Context(None, config, None)\n\n# These should be blocked — both return True due to the unescaped dot\nprint(loader.validate(ctx, \"http://sXglbimgYcom/secret.jpg\"))  # True ← bypass\nprint(loader.validate(ctx, \"http://sAglbimg.com/secret.jpg\"))  # True ← bypass\n\n# Legitimate origin — correctly allowed\nprint(loader.validate(ctx, \"http://s.glbimg.com/logo.jpg\"))    # True ← correct\n```\n\n## Root cause\n\n`thumbor/loaders/http_loader.py` (before fix):\n\n```python\nfor pattern in context.config.ALLOWED_SOURCES:\n    if isinstance(pattern, Pattern):\n        match = url\n    else:\n        pattern = f\"^{pattern}$\"   # \u003c-- dots not escaped, act as regex wildcard\n        match = res.hostname\n\n    if re.match(pattern, match):\n        return True\n```\n\n## Impact\n\nAn attacker who can influence the image source URL passed to Thumbor can fetch images from arbitrary hosts, bypassing the `ALLOWED_SOURCES` allowlist.\n\n**Preconditions:**\n\n- `ALLOWED_SOURCES` contains at least one plain-string entry (the common case; all official documentation examples use plain strings).\n- The attacker can supply or influence the image URL — true whenever  `ALLOW_UNSAFE_URL = True` (the default), or when the application forwards user input to a signed URL endpoint.\n\n## Fix\n\nApply `re.escape()` to plain-string patterns before compiling them, so every\ncharacter is matched literally:\n\n```python\nelse:\n    pattern = f\"^{re.escape(pattern)}$\"   # dots and other metacharacters are now literal\n    match = res.hostname\n```\n\nThis is a one-call addition with no breaking change for correctly written configurations. Users who need real regular-expression behaviour should supply a compiled pattern (`re.compile(r\"s\\.glbimg\\.com\")`), which is already handled by the existing `isinstance(pattern, Pattern)` branch and is unaffected by this change.\n\nThe `ALLOWED_SOURCES` docstring in `config.py` was also updated to document the two-mode behaviour explicitly.","status":"published","published":"2026-08-04T11:34:46.220271Z","modified":"2026-08-04T14:30:30.167925503Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high"}],"updated_at":"2026-11-02T15:52:56.847083Z"},{"id":"UBUNTU-CVE-2026-53500","description":"Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, the ALLOWED_SOURCES configuration passes plain strings to re.match() without escaping dots, so a hostname differing at dot positions can match the allowlist. This issue is fixed in 7.8.0.","status":"published","published":"2026-07-31T19:17:00Z","modified":"2026-08-06T00:20:53Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L","level":"high"}],"updated_at":"2026-11-02T16:56:41.382716Z"}]}}
