Kai Ole Hartwig — Blog
18 min read
Medium
By

PHP CVE-2025-14177: getimagesize() leaks heap memory, iptcembed() remains open

15 May 2026. Positive Technologies (PT SWARM, Nikita Sveshnikov) has published a technical write-up of two memory-safety bugs in PHP's JPEG handling that have been patched since November 2025: a heap information leak in getimagesize() (CVE-2025-14177, CVSS 6.3) and a heap buffer overflow in iptcembed(). Both bugs were fixed on 26 November 2025 in PHP upstream and ship in the maintenance releases 8.1.34, 8.2.30, 8.3.29, 8.4.16 and 8.5.1. If you sit on a current PHP line, you have been protected for six months; if you run older branches, verify your patch state today.

Ein walnussgriffiger Messschieber aus Messing klemmt um die vordere Kante eines aufgefächerten Stapels cremefarbener fotografischer Testkarten, die sich nach rechts unten weit über die Reichweite des Schiebers hinaus erstrecken. Am Mess-Punkt sitzt ein einzelner oxblutroter Email-Punkt als einziger gesättigter Akzent. Oben rechts eine messingbeschlagene Lupe, Linse zum ungemessenen Teil des Stapels geneigt. Kühles Studio-Schlüssellicht von links oben, warmes Rim-Licht von rechts unten, auf mattem dunklem Schiefer.
AI-generated · gpt-image 2.0

TL;DR — 90 seconds

Affected?PHP 8.1.* before 8.1.34, 8.2.* before 8.2.30, 8.3.* before 8.3.29, 8.4.* before 8.4.16, 8.5.* before 8.5.1 — wherever getimagesize() or iptcembed() runs on user uploads (upload forms, indexers, asset pipelines, product images, EXIF reads).
Risk?Heap memory disclosure in getimagesize() (CVSS 6.3) plus a heap buffer overflow in iptcembed(). Both patched in PHP upstream since 26 November 2025. The information disclosure leaks fragments of the process heap; the overflow escalates, depending on build hardening, to DoS or worse.
Immediate action?Check whether your PHP line is at or above 8.1.34 / 8.2.30 / 8.3.29 / 8.4.16 / 8.5.1 — these maintenance trains have shipped both fixes since the end of November 2025. On older branches, ask your hosting provider about the backport date or lift the maintenance line.
Recommendation?German Mittelstand: ask your hosting provider about the patch line, lift your own Composer projects in the regular maintenance window. Enterprise / container builders: rebuild Wolfi or distroless images automatically, diff the SBOM.
Criticality?medium — see hero badge. Operational topic within the weekly window for platforms still on pre-November-2025 PHP versions; on current lines, the situation is purely documentary.

What is the problem?

JPEG files carry their metadata in “Application Segments” — small, chained blocks at the head of the file in which cameras, image editors and content management systems store information such as capture date, GPS coordinates, IPTC keywords or ICC colour profiles. PHP's ext/standard extension has two functions that read and write these segments: getimagesize() extracts dimensions, MIME type and IPTC data; iptcembed() writes IPTC data back into an existing JPEG. Both functions have been in the language core for more than two decades, both are called inside CMS upload workflows, e-commerce product maintenance and asset pipelines, often without being visible in application code.

Nikita Sveshnikov of Positive Technologies (PT SWARM) published the technical write-up on 15 May 2026; the bugs were discovered in November 2025 and patched the same month. The first, CVE-2025-14177, sits in the internal helper php_read_stream_all_chunks, which reads JPEG APP segments in chunks into a heap buffer. The function allocates the target buffer once initially but writes back to the beginning of the buffer on every read call instead of advancing the write pointer. Later chunks therefore overwrite earlier ones, and the tail of the buffer remains filled with random heap content from an earlier allocation. A specially crafted JPEG that forces the parser into “read everything until EOF” mode can return those random bytes to the caller as part of the function's output — and so exfiltrate fragments of other processes' heap memory.

The second bug is a variant of the same class (“measure once, read forever”) in iptcembed(). The function calls fstat once to size the input file and allocates the output buffer on that basis, then keeps reading in a loop without re-checking the write boundary on each iteration. A JPEG whose content grows during reading (classic case: a filesystem race, or a specially constructed pipe stream) leads to an out-of-bounds heap write. Both bugs have been patched in PHP upstream since 26 November 2025: CVE-2025-14177 in PR #20592 (Fix GH-20584) and the iptcembed heap buffer overflow in PR #20591; the fixes ship in the maintenance releases 8.1.34, 8.2.30, 8.3.29, 8.4.16 and 8.5.1.

The bug class, in plain terms

Neither bug is a logic error — both are maintenance debt from the early days of the C implementation. The functions were built when JPEG APP segments were small and pointer arithmetic in every other C program was written this way. What makes them dangerous today is not malice but the fact that CMS and e-commerce platforms accept files from the internet and run them through exactly these functions. That is the real context: this is not an exotic edge case, it is the standard pipeline for product images, press photos and avatar uploads.

Who is affected?

Affected is every PHP process in the version range above that calls getimagesize() or iptcembed() on untrusted input. That includes most CMS upload paths, almost all Sylius product-maintenance pipelines and a large share of Composer-driven B2B platforms. Platforms that source JPEGs exclusively from a vetted CDN or an internal DAM are not affected.

LayerAffectedNot affectedConditions
PHP core8.1.* < 8.1.34, 8.2.* < 8.2.30, 8.3.* < 8.3.29, 8.4.* < 8.4.16, 8.5.* < 8.5.18.1.34 / 8.2.30 / 8.3.29 / 8.4.16 / 8.5.1 and newer (for CVE-2025-14177)iptcembed() bug still open in all versions until PHP maintainers merge the fix
TYPO3 (Indexed Search, FAL, imagery)Indexed Search indexer over JPEG assets; FAL asset handling; editor uploads in tt_contentTYPO3 installations with JPEG upload disabled (rare)Condition: unfiltered JPEG uploads from the frontend (comments, forms) or backend editor with untrusted editors
Sylius / Shopware / e-commerceProduct image upload, avatar upload, B2B catalogue importHeadless stacks without a PHP upload endpoint (e.g. complete asset transfer via S3 presigned URL + Lambda pipeline)Condition: a PHP layer sees the image
Composer librariesintervention/image, phpoffice/phpword, setasign/fpdf, anything that processes JPEG metadataLibraries that use only imagick bindings and bypass the PHP built-in JPEG parsingCheck the Composer lock file for indirect JPEG touch points
Container hosts (Wolfi / distroless / Debian bookworm-slim)Containers that pin the above PHP versionsWolfi images rebuilt nightly through the Chainguard factory, once PHP patches are available thereBuild time matters, not the image family
Kubernetes / container platformsPods with PHP images built before 17 May 2026 with no rolling update policyPods with imagePullPolicy: Always plus daily rolling rebuild of the base imageCondition: build pipeline picks up patches automatically

Impact

CVE-2025-14177 is an information disclosure with CVSS 6.3 (AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N) — network-triggered, no privileges, user interaction (the upload click), impact on confidentiality only. What looks harmless in the score table is uglier in the operational picture: leaked heap content can include session tokens, OPCache content, database connection strings or other data previously handled in this PHP process incarnation. In long-running FPM pools (running pm=static instead of pm=ondemand), a heap history builds up over hours that the next upload can scrape. If you return images in the response (image preview, thumbnail generation with re-embed of the original metadata), you place heap content in the requester's browser cache.

The iptcembed() bug is the more serious situation. A heap buffer overflow in a C function shipped without hardening (no PIE, no FORTIFY_SOURCE in the build) is, in the worst case, an RCE path. In realistic Mittelstand terms, this means: if an attacker can reproducibly trigger a race condition or pipe stream on an iptcembed routine in a productive PHP-FPM pool, they can crash the worker (DoS) or, depending on hardening, place their own instructions on the heap. The attack path requires the attacker to reproducibly trigger the write in iptcembed() — not the standard case in CMS upload workflows, but very much in asset pipelines with downstream IPTC enrichment. The patch has been available in PHP upstream since the end of November 2025; the operational question today is not “when does the fix arrive” but “does our PHP line contain the November 2025 state”.

Business impact in Mittelstand terms: an information disclosure from the PHP heap can expose personal data (GDPR Art. 32 — security of processing). A DoS on productive PHP-FPM workers hits revenue-bearing endpoints (Sylius checkout, TYPO3 login page) directly. In the NIS-2 context, an intentionally exploited information disclosure or DoS counts as a “significant security incident” and must be reported to BSI within the 24/72-hour windows.

Mitigation / immediate actions

Order matters. Patch first, then workarounds, then structural hardening.

Path 1 — install the PHP patch (CVE-2025-14177)

Patches are available for Debian/Ubuntu repositories (Sury) and for Wolfi/Chainguard images. For the most common strands:

 

# Debian/Ubuntu with Sury repo
apt update
apt install --only-upgrade php8.3 php8.3-cli php8.3-fpm php8.3-common
# Target: 8.3.29 or higher
php -v

# Wolfi container (manual build)
docker pull chainguard/php:8.3
docker run --rm chainguard/php:8.3 php -r "echo PHP_VERSION, PHP_EOL;"

# Check Composer project against PHP requirement in composer.json
composer why-not php 8.3.29

 

For container images: do not update the tag alias, pin the full SHA digest once the nightly build is through. Otherwise an unreproducible state walks into production.

Path 2 — if the PHP version cannot be lifted today (workaround for older lines)

The iptcembed() fix has been in PHP upstream since 26 November 2025 (PR #20591) and ships in the maintenance versions named above. If the PHP line cannot be lifted in the short term (hosting provider lag, EOL versions without vendor backport, third-party package pin), three steps to reduce the risk at the application level:

 

// 1. Comment out iptcembed in application code or replace it with a vetted
//    alternative (Imagick::setImageProperty + writeImage), if the library
//    allows it.
$im = new Imagick($jpegPath);
$im->setImageProperty('IPTC:2:120', $caption);
$im->setImageProperty('IPTC:2:25', $keywords);
$im->writeImage($jpegPath);
$im->clear();

// 2. If iptcembed() cannot be replaced: block it via disable_functions in
//    php.ini and add defensive fallback logic in the application that lives
//    without IPTC re-embed.
// php.ini:
//   disable_functions = iptcembed

// 3. For downstream asset pipelines (cron, queue worker): write JPEG
//    metadata only through external tools (exiftool in a sandboxed
//    container), not through the PHP process.

 

Path 3 — structural: upload hardening

If you accept upload JPEGs, route the file through a normalising tool before the PHP parser sees it — ImageMagick in a rootless container, or jpegtran with --copy=none. The JPEG then leaves the ingress path without foreign APP segments, and getimagesize() or iptcembed() only ever sees clean material.

 

# Example: ImageMagick in a rootless container for JPEG normalisation
docker run --rm -v "$(pwd)/uploads:/in:ro" -v "$(pwd)/clean:/out" \
    --read-only --cap-drop=ALL --user=1000:1000 \
    chainguard/imagemagick:latest \
    convert /in/upload.jpg -strip /out/normalized.jpg

# Then parse only the normalised image in PHP
$info = getimagesize('/path/to/clean/normalized.jpg');

 

This pipeline is not only a mitigation for CVE-2025-14177, it is a general hardening recommendation — it reduces the attack surface for the next JPEG memory bug variant that will arrive in twelve months.

Detection

A direct detection for stolen heap bytes is not trivial because the exfil path runs through ordinary HTTP responses. The operational levers sit one step before and one step after the PHP process.

Before the PHP process — upload anomaly detection

 

# Flag JPEGs with unusually large APP segments
# (Empirical threshold: healthy editorial JPEGs are usually <128 KB
# APP1+APP13; triggers above >1 MB deserve attention)
exiftool -j -G uploaded.jpg | jq '.[] | {APP1Length:.["APP1:DirectoryItemLength"], APP13Length:.["APP13:DirectoryItemLength"]}'

# Alternative: jpeginfo / jpegtran as sanity check
jpeginfo -c uploaded.jpg
jpegtran -copy none -optimize uploaded.jpg > /dev/null && echo "ok" || echo "anomalous"

 

Inside the PHP process — OPCache and FPM memory inventory

If you suspect a disclosure has happened, the right question is not “what was leaked” but “which secrets were present in the FPM pool at the time of the request”. That is not answered by a memory dump but by an inventory:

 

# Which secrets end up in the FPM heap in principle?
# Look in php.ini for:
grep -E "^(session\.save_path|opcache\.|auto_prepend_file)" /etc/php/8.3/fpm/php.ini

# Which Composer packages allocate sensitive data on the heap?
composer show --installed | grep -iE "(jwt|oauth|encryption|sodium|paseto)"

 

After the PHP process — Falco rule and eBPF trace

If you have a container-security layer (Falco, Tetragon), you can flag the call path getimagesize/iptcembed with anomalous argument patterns. Example Falco rule:

 

- rule: php_jpeg_upload_with_large_app_segment
  desc: PHP process reads a JPEG file with unusually large APP segment (heuristic for CVE-2025-14177 exploit attempt)
  condition: >
    spawned_process and
    proc.name in (php, php-fpm, php-fpm8.3) and
    fd.name endswith ".jpg" and
    fd.size > 10485760
  output: >
    Large JPEG read by PHP process (proc=%proc.name file=%fd.name size=%fd.size pid=%proc.pid)
  priority: WARNING
  tags: [cve-2025-14177, php, jpeg]

 

An equivalent Tetragon TracingPolicy on the read syscall with filename pattern *.jpg and a read size threshold sits at the kernel level.

Operator recommendation

Operational decision block

German Mittelstand

For the German Mittelstand, three levers line up: first, the conversation with the hosting provider (Mittwald, Hetzner Managed, Plesk hosters, hostNET all have different patch lines); second, your own Composer project (composer why-not php 8.3.29 is the fastest sanity check whether the requirement in the lock file fits); third, the question whether unusually large JPEG uploads have arrived in the last 30 days. The question “did we look” matters more than “did we patch”, because an information disclosure score of 6.3 in the NIS-2 world quickly becomes a reporting obligation once any hint of exploitation appears.

Enterprise / container platforms

On container platforms, the patch lever is the build pipeline, not the pod. If you pull the base image nightly, the patch is in your rollout by 18 May. If you pin the base image quarterly, the problem sits with you until August. Most Mittelstand container stacks fall in between. The decision question here is not “patch or not” but “move the build pipeline to daily rolling or keep quarterly pins”. I recommend daily rolling for any container touching a public ingress endpoint, and quarterly pins only for purely internal workers.

Kubernetes / OpenShift

On Kubernetes, the operational steps are: set imagePullPolicy: Always, give the Deployment an annotation trigger that performs a nightly rolling update, and set readOnlyRootFilesystem: true plus allowPrivilegeEscalation: false in the PodSecurityContext, so a heap overflow doesn't turn into a container escape. If you use Karpenter or Cluster-Autoscaler, set the container-image caches on the worker nodes to TTL=24h — otherwise the scheduler pulls stale images.

Declarative stacks (NixOS / Talos / Flatcar)

In declarative stacks, the patch is a pin bump in the Nix flake or the Talos patch file plus a full rebuild. The upside: the state is reproducible and auditable. The downside: if you don't bump the pin, you keep running unpatched without an apt-update routine pulling the correction in. Set the php pin to 8.3.29 / 8.4.16 / 8.5.1 on the next flake update and verify the build.

What I actually did

I read the PT SWARM disclosure on the evening of 17 May and rolled out the patched PHP version through my build pipeline on 18 May — on my own infrastructure and on the customer platforms I actively operate.

The rollout ran automatically through the update chain of my base images: as soon as the patched PHP maintenance versions were available, the CI pipeline rebuilt the base images and, in the same sequence, pulled all dependent application images forward. The updated image digests landed in the Kubernetes deployments, the rolling update pushed the patch into production. No per-project manual intervention was needed; the pipeline is built for exactly this case. Linux hosts outside the container layer I manage via Ansible for the imperatively managed ones and via NixOS flakes for the declarative ones; the patch path is reproducible there too.

Architecturally, the larger question sits underneath this episode: PHP is an excellent glue layer for CMS and e-commerce workloads, but image processing at the C-function level does not belong in the same process as the request handler. Separating it into downstream workers with minimal capabilities and a clean container sandbox is not the answer to a single CVE — it is the answer to a bug class we will see again every few years.

Frequently asked about CVE-2025-14177

How do I check whether my TYPO3 installation still runs a vulnerable PHP version?+

In the TYPO3 backend under “System → Reports → Environment” you see the running PHP version. Alternatively via CLI in the webroot: php -v. If the output is below 8.1.34 / 8.2.30 / 8.3.29 / 8.4.16 / 8.5.1, CVE-2025-14177 is not patched. For shared-hosting stacks, additionally ask the hosting provider which PHP patch line is currently shipped — the php -v output may sit behind a backport curtain at some providers.

Do Wolfi-based PHP containers need rebuilding because of CVE-2025-14177?+

Wolfi images are rebuilt automatically nightly by the Chainguard factory against current patch levels. If you build the container in CI yourself with melange build or apko publish, you need to re-trigger the build after 18 May and update the new SHA digest in your deployments. If you use chainguard/php:8.3-latest as a tag alias without a pin, you pull the patch automatically on the next pull — but for reproducible builds, switch to a SHA digest pin anyway.

Is imagick as a Composer alternative immune to the JPEG memory bugs?+

The imagick PHP extension calls into the ImageMagick library, not into PHP's internal getimagesize/iptcembed code paths. For the two specific bugs in this disclosure, imagick is therefore not affected. ImageMagick itself has its own CVE history with JPEG and PDF parsers that must be assessed separately — “immune to CVE-2025-14177” is correct, “immune to JPEG memory bugs in general” is not.

When is the iptcembed() patch available — and which PHP version do I need?+

The fix has been in PHP upstream since 26 November 2025 (PR #20591) and ships in maintenance releases 8.1.34, 8.2.30, 8.3.29, 8.4.16 and 8.5.1. If you run a PHP version below those, the simplest path is a lift within the same maintenance line — the fix delta is small and the upgrade risk correspondingly low. For end-of-life PHP versions without a vendor backport, the only path is migration to a supported line.

Do we have to report the incident if someone made a suspicious upload?+

A NIS-2 reporting obligation arises only with a “significant security incident” with evidence of exploitation. A suspiciously large JPEG upload alone does not cross that threshold. If, however, Falco logs or a heap dump produce evidence of leaked personal data or session tokens, GDPR Art. 33 reporting (72 hours to the supervisory authority) applies, and at NIS-2-obligated companies also the 24/72-hour reporting window to BSI. I recommend recording a short incident note alongside each mitigation, so that you have a clean trail — even when no report ends up being necessary.

Bottom line

CVE-2025-14177 and the iptcembed() companion bug have been patched in PHP upstream since the end of November 2025. If you sit on TYPO3, Sylius or a Composer-driven PHP platform, you have two tasks today: verify the patch state of your running PHP versions and inventory the upload paths. That's it — the operational picture is much calmer than the early daily reading suggested. But it shouldn't be less either, because information-disclosure bugs in the PHP heap have the unpleasant property of turning into a reporting obligation in the NIS-2 world the moment anyone asks what was in the FPM pool at the time of the request.

The question is not whether getimagesize() will ever have another bug. The question is whether your upload pipeline is built such that the next bug doesn't catch you off-guard again.

This article reflects my technical assessment. It does not replace a data protection impact assessment or legal advice on specific NIS-2 reporting obligations.

About the author

[Translate to English:] Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.

AI-ready CMS, AI-ready TYPO3, structured content, semantic delivery, MCP, WebMCP, content provenance, GEO, LLM retrieval, AI agent CMS, open source CMS, digital sovereignty

What makes a CMS AI-ready? — four layers that turn a TYPO3 backend into a retrieval-capable platform

AI-ready is not a “chatbot in the backend” — it is a platform discipline: structured content, retrieval-capable delivery, a tool API for agents, and a trust layer. We show what the four layers mean technically — and what we build at Moselwal with our TYPO3 extensions structured-content, semantic-delivery, webmcp, content-provenance, content-intelligence, ai-workflows and business-agent to make a CMS retrieval-, agent- and governance-ready.