mirror of
https://gh.wpcy.net/https://github.com/mainwp/Code-Snippets-Functions.git
synced 2026-04-25 10:52:20 +08:00
138 lines
5.2 KiB
PHP
138 lines
5.2 KiB
PHP
<?php
|
|
/**
|
|
* MainWP Code Snippets - Detect suspicious wp-config.php append after wp-settings.php require
|
|
* Targets the wpos-analytics / Essential Plugin supply chain attack (August 2025)
|
|
*/
|
|
|
|
function mainwp_find_wp_config_path() {
|
|
$paths = array(
|
|
ABSPATH . 'wp-config.php',
|
|
dirname( ABSPATH ) . '/wp-config.php',
|
|
);
|
|
foreach ( $paths as $path ) {
|
|
if ( file_exists( $path ) && is_readable( $path ) ) {
|
|
return $path;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
$config_path = mainwp_find_wp_config_path();
|
|
if ( ! $config_path ) {
|
|
echo 'ERROR: Could not locate a readable wp-config.php file.';
|
|
return;
|
|
}
|
|
|
|
if ( ! is_readable( $config_path ) ) {
|
|
echo 'ERROR: wp-config.php exists but cannot be read. Check file permissions.';
|
|
return;
|
|
}
|
|
|
|
$content = file_get_contents( $config_path );
|
|
if ( false === $content ) {
|
|
echo 'ERROR: Could not read wp-config.php. The file may be restricted by server permissions (open_basedir).';
|
|
return;
|
|
}
|
|
|
|
$line_endings_normalized = str_replace( array( "\r\n", "\r" ), "\n", $content );
|
|
$lines = explode( "\n", $line_endings_normalized );
|
|
$alerts = array();
|
|
$matched_line = '';
|
|
$matched_line_number = 0;
|
|
|
|
// 1) Look for code appended after the wp-settings.php require line.
|
|
// Catches both require and require_once variants.
|
|
foreach ( $lines as $index => $line ) {
|
|
if ( strpos( $line, 'wp-settings.php' ) !== false &&
|
|
( strpos( $line, 'require_once' ) !== false || strpos( $line, 'require' ) !== false ) ) {
|
|
|
|
$matched_line = $line;
|
|
$matched_line_number = $index + 1;
|
|
|
|
// Flag trailing content after the semicolon that is not a comment or a closing PHP tag.
|
|
// Safe trailing content: // comment, # comment, /* comment,
|
|
if ( preg_match( '/wp-settings\.php[\'"]?\s*\)?\s*;\s*(.+)$/i', $line, $m ) ) {
|
|
$trailing = trim( $m[1] );
|
|
|
|
if ( $trailing !== '' && ! preg_match( '/^(\/\/|#|\/\*|\?>\s*$)/', $trailing ) ) {
|
|
$alerts[] = 'Suspicious content found on the same line after the wp-settings.php require statement.';
|
|
}
|
|
}
|
|
|
|
// Flag eval() or base64_decode() anywhere on the wp-settings.php line.
|
|
if ( preg_match( '/\beval\s*\(|\bbase64_decode\s*\(/i', $line ) ) {
|
|
$alerts[] = 'eval() or base64_decode() detected on the wp-settings.php require line — high confidence indicator.';
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2) Check for known indicators specific to this attack.
|
|
$indicators = array(
|
|
// Network / C2 indicators
|
|
'analytics.essentialplugin.com' => 'C2 domain (analytics.essentialplugin.com) found — phone-home address used by the malicious plugin.',
|
|
// Dropper file name (designed to look like core wp-comments-post.php)
|
|
'wp-comments-posts.php' => 'Backdoor dropper filename (wp-comments-posts.php) referenced in wp-config.php.',
|
|
// Code markers left by the injector
|
|
'wpos-analytics' => 'Plugin module identifier (wpos-analytics) found in wp-config.php.',
|
|
'wpos_analytics_anl' => 'Malware function marker (wpos_analytics_anl) found — strong indicator of active injection.',
|
|
'Plugin Wpos Analytics Data Starts' => 'Malware block comment marker found — confirms injected payload is present.',
|
|
// Blockchain C2 resolution
|
|
'ethereum' => 'Ethereum reference found in wp-config.php — used by this malware to resolve its C2 server via smart contract.',
|
|
'web3' => 'Web3 reference found in wp-config.php — no legitimate reason for this in a config file.',
|
|
);
|
|
|
|
foreach ( $indicators as $indicator => $message ) {
|
|
if ( stripos( $content, $indicator ) !== false ) {
|
|
$alerts[] = $message;
|
|
}
|
|
}
|
|
|
|
// 3) Check for the backdoor dropper file in the webroot.
|
|
$dropper_path = ABSPATH . 'wp-comments-posts.php';
|
|
if ( file_exists( $dropper_path ) ) {
|
|
$alerts[] = 'CRITICAL: Backdoor dropper file wp-comments-posts.php found in webroot (' . $dropper_path . '). This file should not exist.';
|
|
}
|
|
|
|
// 4) Size sanity check — injected payload adds ~6KB.
|
|
$file_size = strlen( $content );
|
|
$size_warning = '';
|
|
if ( $file_size > 9000 ) {
|
|
$size_warning = 'wp-config.php is unusually large (' . $file_size . ' bytes). Clean configs are typically under 5KB. This alone is not proof of compromise, but warrants inspection.';
|
|
}
|
|
|
|
// --- Output ---
|
|
|
|
if ( empty( $alerts ) ) {
|
|
echo 'OK: No compromise indicators detected in wp-config.php.' . "\n";
|
|
echo 'Checked file: ' . $config_path . "\n";
|
|
if ( $matched_line_number ) {
|
|
echo 'wp-settings.php require located on line ' . $matched_line_number . '.' . "\n";
|
|
}
|
|
echo 'File size: ' . $file_size . ' bytes.' . "\n";
|
|
if ( $size_warning ) {
|
|
echo "\nNOTE: " . $size_warning . "\n";
|
|
}
|
|
return;
|
|
}
|
|
|
|
echo '*** CRITICAL: Potential compromise indicators detected ***' . "\n\n";
|
|
echo 'File: ' . $config_path . "\n";
|
|
echo 'File size: ' . $file_size . ' bytes.' . "\n";
|
|
|
|
if ( $matched_line_number ) {
|
|
echo "\nwp-settings.php require line: " . $matched_line_number . "\n";
|
|
echo 'Line content:' . "\n";
|
|
echo $matched_line . "\n";
|
|
}
|
|
|
|
echo "\nAlerts:\n";
|
|
foreach ( $alerts as $alert ) {
|
|
echo ' - ' . $alert . "\n";
|
|
}
|
|
|
|
if ( $size_warning ) {
|
|
echo "\nNOTE: " . $size_warning . "\n";
|
|
}
|
|
|
|
echo "\nDo not delete or modify wp-config.php without taking a backup first.";
|
|
echo "\nIf compromise is confirmed, initiate a full site cleanup — patching the plugin alone is not sufficient.";
|