PHP Hidden Inside Image and Icon Files
By Glenn Lyvers · Updated · 4 min read
You have checked every PHP file on the site and found nothing. Meanwhile something is still executing. One of the more elegant tricks in the toolkit is a file that is genuinely a valid image — it opens in an image viewer, it passes an image check, it has the right extension — and also contains working PHP that runs when the server is persuaded to treat it as code.
How a file can be two things at once
Image formats tolerate extra data. A JPEG has a defined structure with metadata fields and comment blocks, and a decoder reads the parts it cares about and ignores the rest. So you can append or embed arbitrary bytes into a valid image and it still displays perfectly, because the extra content sits somewhere the decoder does not look.
PHP, for its part, ignores everything outside its opening and closing tags. Give the PHP interpreter a file full of binary image data with a block of PHP somewhere inside it, and it will output the binary as-is and execute the PHP. The same bytes are a picture to one program and a script to another, which is where the term polyglot comes from.
Why this defeats upload filters
Most upload validation asks one of two questions: does the extension look like an image, and does the file content look like an image. A polyglot answers yes to both. The extension really is .jpg, the magic bytes at the start really are a JPEG header, and a library asked to read the dimensions will read them successfully.
So the file gets stored in your uploads directory as a legitimate image. It is not executable on its own at that point — requesting it just serves a picture. What makes it dangerous is the second half of the technique, which is arranging for the server to run it as PHP.
How it gets executed
Several routes, all of which require something else to be wrong. A local file inclusion flaw in a plugin lets an attacker point an include at the uploaded image, and PHP executes the embedded code. An .htaccess dropped into the uploads directory can tell the server to treat image extensions as PHP, which is why an unexpected .htaccess anywhere in your tree deserves attention — my guide on hacked .htaccess files covers that.
Server misconfiguration can do it too, where a permissive rule causes files with double extensions to be handed to the PHP interpreter. The point is that the image alone is a stored payload; there is always a second component that pulls the trigger, and finding both matters.
Finding them
Scan your uploads directory for PHP tags inside image files. A recursive search for the opening PHP tag across image extensions is crude but extremely effective, because a legitimate photograph essentially never contains that sequence. Anything that matches is worth examining immediately.
Check file sizes against expectations — an icon that is unexpectedly large, or a thumbnail bigger than its original, is a reasonable hint. Check for .htaccess files anywhere inside wp-content/uploads, since a legitimate WordPress install has no reason to put one there. And review modification timestamps, which my guide on timestamp forensics covers in detail.
Cleaning up
Delete the polyglot files and any .htaccess that was added to the uploads tree. Then find the execution route, because the stored image was harmless until something ran it — look for the vulnerable plugin providing file inclusion, and check for other web shells and backdoors that the same access would have allowed.
Do the ordinary full pass as well: replace core, plugins and theme from clean sources, clean the database, audit administrator accounts, and rotate credentials. A polyglot in your uploads folder means an attacker had a working upload path into your site, and that path is the thing that actually needs closing.
Stopping it happening again
The single most effective measure is to stop PHP executing in your uploads directory at all. Your media folder exists to serve images and documents; there is no legitimate reason for it to run code, and blocking execution there removes the value of every upload trick at once, polyglot or otherwise.
Beyond that: keep plugins current so file inclusion flaws get patched, remove upload-handling plugins you do not use, and consider whether your site genuinely needs to accept uploads from anyone who is not an administrator. My guides on PHP files in uploads and hardening cover the configuration, and if you would rather have the whole site swept properly, that is my malware removal service.
Common questions
Can an image file really contain working code?
Yes. Image formats ignore data they do not recognize and PHP ignores everything outside its tags, so a single file can be a completely valid image and a completely valid script at the same time. It will display correctly in any image viewer while still executing when handed to the PHP interpreter.
Does opening the image infect my computer?
No. The embedded code is PHP, which runs on a web server, not on your desktop. Viewing the file locally just shows you a picture. The risk is entirely server-side, and it only materializes when something causes your server to interpret the file as code.
How do I search for these?
Recursively search your uploads directory for the PHP opening tag across image file extensions. Legitimate photographs effectively never contain that byte sequence, so matches are nearly always worth investigating. Also look for any .htaccess file inside the uploads tree, which WordPress never puts there.
Why did my scanner miss it?
Many scanners only examine files with executable extensions, and a polyglot is deliberately named as an image. Some also validate files by checking whether they parse as images, which a polyglot passes by design. It is an evasion technique aimed squarely at that kind of check.
What is the single best prevention?
Disabling PHP execution in wp-content/uploads. That directory exists to serve media, and code should never run from it. Once execution is blocked there, an uploaded polyglot is just a slightly odd image file rather than a route into your server.
More than malware
Most people meet me in an emergency. It isn’t all I do.
I’ve been building and repairing systems since 1995. Whatever brought you here, there’s a good chance I can help with the rest of it too — and you’ll be dealing with the same person either way.
Hacked, but not WordPress?
Joomla, Drupal, Magento, Shopify, PrestaShop, Laravel, Node, IIS and plain HTML — cleaned the same way, priced the same way.
Take a look →Custom builds & AI systems
Plugins, custom applications, website chatbots and automation — built to do exactly what you need, maintained by the person who wrote them.
Take a look →Servers, speed, SEO & accessibility
Migrations, faster load times, technical SEO and accessibility fixes. Measured improvements, with the numbers to show you.
Take a look →Better web hosting
Fast, secure hosting with SSL and backups included at no extra charge. Clear pricing, no long-term contracts, no surprises.
Take a look →Classes & free tools
Rather learn to handle it yourself? I teach this, and I give away the tools I built for my own cleanups.
Take a look →Something else broken?
Half my work is untangling what someone else started, gave up on, or broke. Describe it in plain words and I’ll tell you honestly.
Take a look →Tell me what’s wrong. I’ll tell you what it takes.
No queue, no call centre, no sales pitch — one person who answers, quotes honestly, and does the work.