Polyglot Files: A Hacker’s Best Friend
And how to hide executable PHP code in JPEG files.
Hello! Thanks for clicking on this blog post. Today, we’re gonna talk about a topic that has always fascinated me in the cybersecurity world: polyglot files! We’re going to take a look at what they are, how they confuse applications, how they are used in an attack scenario.
What are polyglots?
Polyglots, in a security context, are files that are a valid form of multiple different file types. For example, a GIFAR is both a GIF and a RAR file. There are also files out there that can be both GIF and JS, both PPT and JS, etc.
Polyglot files are often used to bypass protection based on file types. Many applications that allow users to upload files only allow uploads of certain types, such as JPEG, GIF, DOC, so as to prevent users from uploading potentially dangerous files like JS files, PHP files or Phar files.
One example of a polyglot file is a Phar-JPEG file. Phar files are used to carry out PHP object injection attacks.
This type of attack can lead to RCE. However, it requires the attacker to be able to upload a readable Phar file, and Phar files are not often allowed by file upload functionalities. So a Phar-JPEG file allows the malicious upload to look like a harmless JPEG file to bypass upload restrictions, but still retain the capabilities of a Phar file.
File format specifications
First, it is important to understand that different file types are simply chunks of bytes that follow a predefined structure.
Phar archive format
For example, let’s look at the PHAR (PHp ARchive) format. For a Phar file to be valid, it needs to follow a certain structure: it contains a stub section, a manifest section and finally, a file content section.
- Stub: the stub is a chunk of PHP code which is executed when the file is accessed in an executable context. At a minimum, the stub must contain __HALT_COMPILER(); at its conclusion. Otherwise, there are no restrictions on the contents of a Phar stub.
- Manifest: this section contains metadata about the archive and its contents.
- File contents: this section contains the actual files in the archive.
- Signature (optional): for verifying archive integrity.
JPEG format
On the other hand, JPEGs are structured very differently. (The complete specifications of JPEG images are beyond the scope of this article, but you can read about it here.) But for the purposes of this article, keep in mind that:
- A header of a JPEG image looks like this:
typedef struct _JFIFHeader
{
BYTE SOI[2]; /* 00h Start of Image Marker */
BYTE APP0[2]; /* 02h Application Use Marker */
BYTE Length[2]; /* 04h Length of APP0 Field */
BYTE Identifier[5]; /* 06h "JFIF" (zero terminated) Id */
BYTE Version[2]; /* 07h JFIF Format Revision */
BYTE Units; /* 09h Units used for Resolution */
BYTE Xdensity[2]; /* 0Ah Horizontal Resolution */
BYTE Ydensity[2]; /* 0Ch Vertical Resolution */
BYTE XThumbnail; /* 0Eh Horizontal Pixel Count */
BYTE YThumbnail; /* 0Fh Vertical Pixel Count */
} JFIFHEAD;
- JPEG files start with a “Start of Image (SOI)” marker which contains the bytes
FF
D8
. - The application marker APP0 contains the bytes
FF
E0
. - The length field is the size of the image.
- The identifier field contains “JFIF” with a trailing NULL byte.
- The version field specifies the JFIF specification version.
- And finally, JPEG files end with an “End of Image (EOI)” marker,
FF
D9
.
So… How do polyglots work?
So, how exactly do polyglots fool the applications that process them? How does a Phar-JPEG appear as an image to an image processor and file upload security checker, yet still be able to be used as a Phar file?
To understand why polyglots are possible, we have to look at how applications detect file types.
How do applications determine file type?
An application can utilize several ways of checking the file type of a file: by looking at the file extension, by looking for specific magic bytes, and by validating certain file signatures.
- Extension based detection
The simplest way to detect a file’s type is to look at its file extension, for example, puppies.jpeg is a JPEG file. However, this method of detection is not always reliable. Files can be renamed and files could come without an extension. So this method is often only one of many methods that applications use to validate file types.
- Magic bytes detection
For some file types, there are magic bytes at the start of the file that indicates that the file is of a certain format. Some forms of file type detection make use of this information. Some of these examples are:
- JPEG files begin with
FF
D8
and end withFF
D9
. - PDF files start with “%PDF” (hex
25
50
44
46
).
- File signature validation
Still, some more sophisticated file validators will perform checks based on specific signatures of the file. For example, they might check if a JPEG file contains all the necessary header fields.
How polyglots become two file types
Polyglots become polyglots when they pass all the validity checks for more than one file type. To an application that is looking for image files, it bears all the signatures of an image file. To another application that is looking for different indicators, it becomes something else and checks all the boxes as well.
For a Phar-JPEG file, it has to pass all the JPEG validity checks when it is uploaded, then still be used as a PHP archive when the attacker calls it to start a PHP object injection attack.
Constructing a Phar-JPEG
A Phar-JPEG file is possible due to the flexibility in the Phar format’s specifications. Specifically, there are no restrictions to the stub section of a Phar besides that it must end with __HALT_COMPILER();.
This means that the stub section of the Phar can be used to inject dummy data in order to disguise the file as a JPEG. Let’s say we inject a valid JPEG image in the stub section of the Phar before __HALT_COMPILER();. Now the file looks something like this:
\xFF\xD8......................JPEG DATA.....................\xFF\xD8
__HALT_COMPILER(); ............PHAR DATA............................
To an image processor, the file is a JPEG image because it contains all the signatures of a valid JPEG file: a Start of Image marker, headers, and an End of Image marker. Most image processors will stop processing the file at this point and will not look beyond the JPEG.
But when used with a Phar stream wrapper phar://, the file seems like something entirely different: it looks like a file with a valid stub section, a valid manifest and some files in the contents section. Therefore, when operating on the file, PHP still performs an unserialize() operation on the Phar file’s metadata. The attacker thus achieves RCE through a successful PHP object injection.
Isn’t that super cool? :)