Skip to content

Commit

Permalink
feat: wordpress plugins checksum verifier
Browse files Browse the repository at this point in the history
  • Loading branch information
marcocesarato committed Oct 24, 2020
1 parent fd11825 commit e420a58
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 16 deletions.
169 changes: 158 additions & 11 deletions src/Module/Wordpress.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

namespace marcocesarato\amwscan\Module;

use GlobIterator;
use marcocesarato\amwscan\Console;
use marcocesarato\amwscan\VerifierInterface;

class Wordpress implements VerifierInterface
{
protected static $checksums = array();
protected static $pluginsChecksums = array();
protected static $roots = array();
protected static $DS = DIRECTORY_SEPARATOR;

Expand All @@ -21,12 +23,16 @@ public static function init($path)
if (self::isRoot($path)) {
$version = self::getVersion($path);
if ($version && !empty($version) && !isset(self::$roots[$path])) {
Console::writeLine('Found WordPress ' . $version . ' at "' . $path . '"', 1, 'green');
$locale = self::getLocale($path);
Console::writeLine('Found WordPress ' . $version . ' (' . $locale . ') at "' . $path . '"', 1, 'green');

$plugins = self::getPlugins($path);
self::$roots[$path] = array(
'path' => $path,
'version' => $version,
'locale' => $locale,
'plugins' => $plugins,
);
self::getChecksums($version);
}
}
}
Expand All @@ -45,7 +51,6 @@ public static function isRoot($path)
is_dir($path . self::$DS . 'wp-admin') &&
is_dir($path . self::$DS . 'wp-content') &&
is_dir($path . self::$DS . 'wp-includes') &&
is_file($path . self::$DS . 'wp-config.php') &&
is_file($path . self::$DS . 'wp-includes' . self::$DS . 'version.php')
;
}
Expand All @@ -62,7 +67,7 @@ public static function getVersion($root)
$versionFile = $root . self::$DS . 'wp-includes' . self::$DS . 'version.php';
if (is_file($versionFile)) {
$versionContent = file_get_contents($versionFile);
preg_match('/\$wp_version[\s]*=[\s]*[\'"]([0-9.]+)[\'"]/', $versionContent, $match);
preg_match('/\$wp_version[\s]*=[\s]*[\'"]([0-9.]+)[\'"]/m', $versionContent, $match);
$version = trim($match[1]);
if (!empty($version)) {
return $version;
Expand All @@ -72,19 +77,97 @@ public static function getVersion($root)
return null;
}

/**
* Get locale.
*
* @param $root
*
* @return string
*/
public static function getLocale($root)
{
$versionFile = $root . self::$DS . 'wp-includes' . self::$DS . 'version.php';
if (is_file($versionFile)) {
$versionContent = file_get_contents($versionFile);
preg_match('/\$wp_local_package[\s]*=[\s]*[\'"]([A-Za-z_-]+)[\'"]/m', $versionContent, $match);
$locale = trim($match[1]);
if (!empty($locale)) {
return $locale;
}
}

return 'en_US';
}

/**
* Get plugins.
*
* @param $root
*
* @return string[]
*/
public static function getPlugins($root)
{
$plugins = array();
$files = new GlobIterator($root . self::$DS . 'wp-content' . self::$DS . 'plugins' . self::$DS . '*' . self::$DS . '*.php');
foreach ($files as $cur) {
if ($cur->isFile()) {
$headers = self::getPluginHeaders($cur->getPathname());
if (!empty($headers['domain']) && !empty($headers['version'])) {
if (empty($headers['domain'])) {
$headers['domain'] = $cur->getBasename('.' . $cur->getExtension());
}
$headers['path'] = $cur->getPath();
$plugins[$cur->getPath()] = $headers;
Console::writeLine('Found WordPress Plugin ' . $headers['name'] . ' ' . $headers['version'], 1, 'green');
}
}
}

return $plugins;
}

/**
* Get file headers.
*
* @param $file
*
* @return string[]
*/
public static function getPluginHeaders($file)
{
$headers = array('name' => 'Plugin Name', 'version' => 'Version', 'domain' => 'Text Domain');
$file_data = file_get_contents($file);
$file_data = str_replace("\r", "\n", $file_data);
foreach ($headers as $field => $regex) {
if (preg_match('/^[ \t\/*#@]*' . preg_quote($regex, '/') . ':(.*)$/mi', $file_data, $match) && $match[1]) {
$headers[$field] = trim(preg_replace('/\s*(?:\*\/|\?>).*/', '', $match[1]));
} else {
$headers[$field] = '';
}
}

return $headers;
}

/**
* Get checksums.
*
* @param $version
* @param string $locale
* @param array $plugins
*
* @return array
* @return array|false
*/
public static function getChecksums($version)
public static function getChecksums($version, $locale = 'en_US')
{
if (empty(self::$checksums[$version])) {
$checksums = file_get_contents('https://api.wordpress.org/core/checksums/1.0/?version=' . $version);
$checksums = json_decode($checksums, true);
$versionChecksums = $checksums['checksums'][$version];
if (!isset(self::$checksums[$version])) {
Console::writeLine('Retrieving checksums of Wordpress ' . $version, 1, 'grey');
$checksums = self::getData('https://api.wordpress.org/core/checksums/1.0/?version=' . $version . '&locale=' . $locale);
if (!$checksums) {
return false;
}
$versionChecksums = $checksums['checksums'];
self::$checksums[$version] = array();
// Sanitize paths and checksum
foreach ($versionChecksums as $filePath => $checksum) {
Expand All @@ -96,6 +179,39 @@ public static function getChecksums($version)
return self::$checksums[$version];
}

/**
* Get checksums.
*
* @param $version
* @param string $locale
* @param array $plugins
*
* @return array|false
*/
public static function getPluginsChecksums($plugins = array())
{
foreach ($plugins as $plugin) {
if (!isset(self::$pluginsChecksums[$plugin['domain']][$plugin['version']])) {
Console::writeLine('Retrieving checksums of Wordpress Plugin ' . $plugin['name'] . ' ' . $plugin['version'], 1, 'grey');
$checksums = self::getData('https://downloads.wordpress.org/plugin-checksums/' . $plugin['domain'] . '/' . $plugin['version'] . '.json');
if (!$checksums) {
self::$pluginsChecksums[$plugin['domain']][$plugin['version']] = array();
continue;
}
$pluginChecksums = $checksums['files'];
foreach ($pluginChecksums as $filePath => $checksum) {
$path = $plugin['path'] . self::$DS . $filePath;
$root = self::getRoot($path);
$sanitizePath = str_replace($root, '', $path);
$sanitizePath = self::sanitizePath($sanitizePath);
self::$pluginsChecksums[$plugin['domain']][$plugin['version']][$sanitizePath] = strtolower($checksum['md5']);
}
}
}

return self::$pluginsChecksums;
}

/**
* Is verified file.
*
Expand All @@ -113,16 +229,28 @@ public static function isVerified($path)
if (!empty($root)) {
$comparePath = str_replace($root['path'], '', $path);
$comparePath = self::sanitizePath($comparePath);
$checksums = self::getChecksums($root['version']);
$checksums = self::getChecksums($root['version'], $root['locale']);
$pluginsChecksums = self::getPluginsChecksums($root['plugins']);
if (!$checksums) {
return false;
}
// Core
if (!empty($checksums[$comparePath])) {
$checksum = md5_file($path);
$checksum = strtolower($checksum);

return $checksums[$comparePath] === $checksum;
}
// Plugins
foreach ($root['plugins'] as $plugin) {
$checksums = $pluginsChecksums[$plugin['domain']][$plugin['version']];
if (!empty($pluginsChecksums[$plugin['domain']][$plugin['version']]) && !empty($checksums[$comparePath])) {
$checksum = md5_file($path);
$checksum = strtolower($checksum);

return $checksums[$comparePath] === $checksum;
}
}
}

return false;
Expand Down Expand Up @@ -162,4 +290,23 @@ public static function sanitizePath($path)

return $sanitized;
}

/**
* HTTP request get data.
*
* @param $url
*
* @return mixed|null
*/
protected static function getData($url)
{
$headers = get_headers($url);
if (substr($headers[0], 9, 3) != '200') {
return null;
}

$content = @file_get_contents($url);

return @json_decode($content, true);
}
}
5 changes: 1 addition & 4 deletions src/Modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ public static function init($path)
*/
public static function isVerified($path)
{
$result = false;
$result = (Wordpress::isVerified($path) || $result);

return $result;
return Wordpress::isVerified($path);
}
}
3 changes: 2 additions & 1 deletion src/Scanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ public function run($args = null)
Console::writeLine('Scanning ' . self::$pathScan, 2);

// Mapping files
Console::writeLine('Mapping and verifying files. It may take a while, please wait...');
Console::writeLine('Mapping, retrieving checksums and verifying files');
Console::displayLine('It may take a while, please wait...');
$iterator = $this->mapping();

// Counting files
Expand Down

0 comments on commit e420a58

Please sign in to comment.