Skip to content
1 change: 1 addition & 0 deletions lib/CleantalkSP/Common/Helpers/CSV.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static function sanitizeFromEmptyLines(&$buffer)
* @param string $buffer
*
* @return string[]
* @psalm-suppress PossiblyUnusedMethod
*/
public static function parseCSVLite($buffer)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@ class BinaryCheckModule
*/
private $statuses = array();

/**
* @var string[]
*/
private static $executable_mime_types = array(
'application/x-executable'
);

/**
* Runs the BinaryCheckModule to find malicious binary files in a single batch.
*
Expand Down Expand Up @@ -186,47 +179,7 @@ private function incrementStatus($status)

private function analyzeBinaryFile($binary_file_path)
{
$first_bytes = $this->getBinaryBytes($binary_file_path);
if (false === $first_bytes) {
return false;
}

// Method 1: Check using ord() to get byte values
$byte1 = ord($first_bytes[0]);
$byte2 = ord($first_bytes[1]);
$byte3 = ord($first_bytes[2]);
$byte4 = ord($first_bytes[3]);

// Check for ELF (0x7F followed by "ELF" = 0x45 0x4C 0x46)
if ($byte1 === 0x7F && $byte2 === 0x45 && $byte3 === 0x4C && $byte4 === 0x46) {
return true; // ELF file detected
}

// Check for PE/MZ (Windows executable: 0x4D 0x5A = "MZ")
if ($byte1 === 0x4D && $byte2 === 0x5A) {
return true; // PE file detected
}

// Check for MACHO (macOS/iOS executables)
// 32-bit big-endian: 0xFE 0xED 0xFA 0xCE
// 32-bit little-endian: 0xCE 0xFA 0xED 0xFE
// 64-bit big-endian: 0xFE 0xED 0xFA 0xCF
// 64-bit little-endian: 0xCF 0xFA 0xED 0xFE
$macho_signatures = array(
array(0xFE, 0xED, 0xFA, 0xCE), // 32-bit big-endian
array(0xCE, 0xFA, 0xED, 0xFE), // 32-bit little-endian
array(0xFE, 0xED, 0xFA, 0xCF), // 64-bit big-endian
array(0xCF, 0xFA, 0xED, 0xFE), // 64-bit little-endian
);

foreach ($macho_signatures as $signature) {
if ($byte1 === $signature[0] && $byte2 === $signature[1] &&
$byte3 === $signature[2] && $byte4 === $signature[3]) {
return true; // MACHO file detected
}
}

return false;
return self::hasExecutableSignature($binary_file_path);
}

private function markAsCritical($binary_file_path)
Expand All @@ -246,13 +199,9 @@ private function markAsCritical($binary_file_path)
* @param $filename
* @return false|string String of bytes on success, false on failure.
*/
private function getBinaryBytes($filename)
private static function getBinaryBytes($filename)
{
if (!file_exists($filename)) {
return false;
}

$handle = fopen($filename, 'rb');
$handle = @fopen($filename, 'rb');
if (!$handle) {
return false;
}
Expand All @@ -268,39 +217,74 @@ private function getBinaryBytes($filename)
}

/**
* Read MIME type and make decision if MIME is in expected static array.
* Check the file signature for the known executable formats.
*
* Reading 4 bytes is used instead of finfo_file() intentionally: finfo reads a large part of
* every file and makes the file system passes of the scanner dozens of times slower.
*
* @param string $filename
* @param string $extension
*
* @return bool
*/
public static function probablyBinaryExecutableFile($filename, $extension)
public static function hasExecutableSignature($filename)
{
if (!is_string($filename) || !file_exists($filename)) {
$first_bytes = self::getBinaryBytes($filename);
if (false === $first_bytes) {
return false;
}

$extension = strtolower((string) $extension);
$byte1 = ord($first_bytes[0]);
$byte2 = ord($first_bytes[1]);
$byte3 = ord($first_bytes[2]);
$byte4 = ord($first_bytes[3]);

// only empty or elf extension
if (!in_array($extension, ['', 'elf'], true)) {
return false;
// ELF (0x7F followed by "ELF" = 0x45 0x4C 0x46)
if ($byte1 === 0x7F && $byte2 === 0x45 && $byte3 === 0x4C && $byte4 === 0x46) {
return true;
}

if (function_exists('finfo_open') && function_exists('finfo_file')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if (!$finfo) {
return false;
}
$mime = finfo_file($finfo, $filename);
if (PHP_VERSION_ID < 80000) {
finfo_close($finfo);
}
// PE/MZ (Windows executable: 0x4D 0x5A = "MZ")
if ($byte1 === 0x4D && $byte2 === 0x5A) {
return true;
}

// MACHO (macOS/iOS executables)
$macho_signatures = array(
array(0xFE, 0xED, 0xFA, 0xCE), // 32-bit big-endian
array(0xCE, 0xFA, 0xED, 0xFE), // 32-bit little-endian
array(0xFE, 0xED, 0xFA, 0xCF), // 64-bit big-endian
array(0xCF, 0xFA, 0xED, 0xFE), // 64-bit little-endian
);

if (in_array($mime, self::$executable_mime_types, true)) {
foreach ($macho_signatures as $signature) {
if ($byte1 === $signature[0] && $byte2 === $signature[1] &&
$byte3 === $signature[2] && $byte4 === $signature[3]) {
return true;
}
}

return false;
}

/**
* Make decision if the file is an executable binary one.
* @param string $filename
* @param string $extension
* @return bool
*/
public static function probablyBinaryExecutableFile($filename, $extension)
{
$extension = strtolower((string) $extension);

// only empty or elf extension, checked before any filesystem call
if (!in_array($extension, ['', 'elf'], true)) {
return false;
}

if (!is_string($filename) || $filename === '') {
return false;
}

return self::hasExecutableSignature($filename);
}
}
39 changes: 0 additions & 39 deletions lib/CleantalkSP/SpbctWP/Scanner/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,45 +121,6 @@ public static function getHashesForModules($cms, $type, $plugin, $version)
return array('error' => 'REMOTE_FILE_NOT_FOUND_OR_VERSION_IS_NOT_SUPPORTED__PLUG');
}

/**
* Getting real hashs of approved files
*
* @param string $cms CMS name
* @param string $type Type - approved/rejected
* @param string $version
*
* @return false|string[]|non-empty-array<array-key, string> Array with all files hashes or Error Array
*/
public static function getHashesForFiles($cms, $type, $version)
{
$file_path = 'https://cleantalk-security.s3-us-west-2.amazonaws.com/extensions_checksums/' . $cms . '/' . $type . '/' . $version . '.csv.gz';
if ( HTTP::getResponseCode($file_path) !== 200 ) {
return array('error' => 'REMOTE_FILE_NOT_FOUND');
}

$gz_data = HTTP::getContentFromURL($file_path, false);
if ( !empty($gz_data['error']) ) {
return (array) $gz_data;
}

if ( !function_exists('gzdecode') ) {
return array('error' => 'Function gzdecode not exists. Please update your PHP to version 5.4');
}

$data = gzdecode($gz_data);
if ( $data === false ) {
return array('error' => 'COULDNT_UNPACK');
}

/** @psalm-suppress UndefinedMethod */
$lines = CSV::parseCSVLite($data);
if ( count($lines) < 1 ) {
return array('error' => 'Empty hashes file');
}

return $lines;
}

/**
* Getting real hashes of CMS language (translation) files for all available translations
*
Expand Down
Loading
Loading