PHP: Magic Bytes check for image files
Published: 2023-03-05 20:04:08

This is based on my previous post about writing boolean functions that do a byte signature check depending on the file extension.

The Byte Signature of a file can be manipulated too and also it may be redundant to use it for validation, because the URL to a corrupt image file on your website will simply not be displayed by the browser. (And in your upload script you should whitelist file extensions anyway!)

But for the sake of completion I want to share all the 5 functions I wrote, to validate jpg, png, gif, bmp and webp image files.

```
<?php
// function from my previous post
$ext = getFileExtension($_FILES["myFile"]["name"]);


// Magic Bytes validation callback functions:
$imgCallBackFuncs = array("jpg" => "magicBytesJPG",
"jpeg" => "magicBytesJPG",
"gif" => "magicBytesGIF",
"bmp" => "magicBytesBMP",
"png" => "magicBytesPNG",
"webp" => "magicBytesWEBP");


function checkMagicBytes($ext, $file):bool
{
    if(!isset($GLOBALS["imgCallBackFuncs"][$ext])
    OR !function_exists($GLOBALS["imgCallBackFuncs"][$ext])) {
        return FALSE;
    }
    return call_user_func($GLOBALS["imgCallBackFuncs"][$ext], $file);
}

// The 5 different image types:

function magicBytesBMP($file):bool
{
    if(!$handle = fopen($file, 'r')) return FALSE;
    if(!$readBytes = fread($handle, 2)) return FALSE;

    // Byte Signature for BMP:
    // "42 4D" (2 Bytes)
    if(mb_strtoupper(bin2hex($readBytes)) == "424D") {
        return TRUE;
    }
    return FALSE;
}

function magicBytesPNG($file):bool
{
    if(!$handle = fopen($file, 'r')) return FALSE;
    if(!$readBytes = fread($handle, 8)) return FALSE;
    
    // Byte Signature for PNG:
    // "89 50 4E 47 0D 0A 1A 0A" (8 bytes)
    if(mb_strtoupper(bin2hex($readBytes)) == "89504E470D0A1A0A") {
        return TRUE;
    }
    return FALSE;
}

function magicBytesGIF($file):bool
{
    if(!$handle = fopen($file, 'r')) return FALSE;
    if(!$readBytes = fread($handle, 6)) return FALSE;
    
    // Byte Signature for GIF:
    // GIF87a "47 49 46 38 37 61"
    // GIF89a "47 49 46 38 39 61"
    $readBytes = mb_strtoupper(bin2hex($readBytes));
    if($readBytes === "474946383761"
    OR $readBytes === "474946383961") {
        return TRUE;
    }
    return FALSE;
}

function magicBytesWEBP($file):bool
{
    if(!$handle = fopen($file, 'r')) return FALSE;
    if(!$readBytes = fread($handle, 12)) return FALSE;

    // Byte Signature for WEBP:
    // "52 49 46 46 ?? ?? ?? ?? 57 45 42 50" (12 Bytes)
    $readBytes = mb_strtoupper(bin2hex($readBytes));
    if(preg_match("/52494646[A-F0-9]{8}57454250/", $readBytes)) {
        return TRUE;
    }
    return FALSE;
}

function magicBytesJPG($file):bool
{
    if(!$handle = fopen($file, 'r')) return FALSE;
    if(!$readBytes12 = fread($handle, 12)
    OR !$readBytes4 = fread($handle, 4)) return FALSE;
    fclose($handle);

    /*
    // Byte Signature for JPG:
    1 - FF D8 FF DB (4 bytes)
    2 - FF D8 FF E0 00 10 4A 46 49 46 00 01 (12 bytes)
    3 - FF D8 FF EE (4 bytes)
    4 - FF D8 FF E1 ?? ?? 45 78 69 66 00 00 (12 bytes)
    5 - FF D8 FF E0 (4 bytes)
    */
    $readBytes12 = mb_strtoupper(bin2hex($readBytes12));
    $readBytes4 = mb_strtoupper(bin2hex($readBytes4));

    if($readBytes4 == "FFD8FFDB" OR $readBytes4 == "FFD8FFEE"
    OR $readBytes4 == "FFD8FFE0" OR $readBytes12 == "FFD8FFE000104A4649460001"
    OR preg_match("/FFD8FFE1[A-F0-9]{4}457869660000/", $readBytes12)) {
        return TRUE;
    }
    return FALSE;
}
```