SQL Injection Attack Notifier (ready-to-deploy MU-plugin)

SQL Injection Attack Notifier (ready-to-deploy MU-plugin)

Below is a production-grade lightweight SQLi Attack Notifier. Save it in wp-content/mu-plugins/sqli-attack-notifier.php (MU plugins run automatically) or wp-content/plugins/ and activate.

It inspects $_GET, $_POST, $_COOKIE, and REQUEST_URI for common SQLi patterns, logs the events into a DB table ({prefix}sqli_events), writes to error_log, emails the admin, and optionally sends alerts to a Slack webhook. Tune patterns to reduce false positives.

   <?php
/**
 * Plugin Name: SQLi Attack Notifier (Lightweight)
 * Description: Detects suspicious SQL injection payload patterns, logs the event, sends email to admin, and posts to Slack (optional).
 * Version: 1.1
 * Author: Your Name
 * NOTE: Place in wp-content/mu-plugins/ for always-on protection.
 */

if ( ! defined( 'ABSPATH' ) ) exit;

class WP_SQLi_Attack_Notifier {
    private $admin_email;
    private $slack_webhook = ''; // set to your Slack incoming webhook URL to enable Slack notifications
    private $notify_threshold = 1; // number of matched suspicious inputs before action triggers

    // Basic heuristic patterns (tune them)
    private $patterns = [
        "/\bUNION\b/i",
        "/\bSELECT\b.*\bFROM\b/i",
        "/\bDROP\b\s+\bTABLE\b/i",
        "/\bINSERT\b\s+\bINTO\b/i",
        "/\bUPDATE\b\s+\bSET\b/i",
        "/\bDELETE\b\s+\bFROM\b/i",
        "/\bOR\b\s+['\"]?1['\"]?\s*=\s*['\"]?1['\"]?/i",
        "/--\s*$/", "/#\s*$/",
        "/;--|;|\bCHAR\(|\bCAST\(|\bCONCAT\(/i",
        "/sleep\(\s*\d+\s*\)/i",
        "/benchmark\(/i",
        "/\bEXEC\b/i",
        "/\bINFORMATION_SCHEMA\b/i",
        "/0x[0-9a-f]{2,}/i",
    ];

    public function __construct() {
        $this->admin_email = get_option( 'admin_email' );
        add_action( 'init', [ $this, 'inspect_request' ], 0 );
        register_activation_hook( __FILE__, [ $this, 'maybe_create_table' ] );
    }

    public function inspect_request() {
        // Optionally skip trusted contexts to reduce false positives
        if ( is_user_logged_in() && current_user_can( 'manage_options' ) ) {
            return;
        }

        $sources = [
            '_GET'    => isset($_GET) ? wp_unslash($_GET) : [],
            '_POST'   => isset($_POST) ? wp_unslash($_POST) : [],
            '_COOKIE' => isset($_COOKIE) ? wp_unslash($_COOKIE) : [],
            'URI'     => isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '',
        ];

        $matches = [];
        $count = 0;

        foreach ( $sources as $key => $payload ) {
            if ( is_array( $payload ) ) {
                foreach ( $payload as $name => $value ) {
                    $value = (string) $value;
                    if ( $value === '' ) continue;
                    $hit = $this->match_patterns( $value );
                    if ( $hit ) {
                        $count++;
                        $matches[] = [ 'source' => $key, 'field' => $name, 'value' => $this->shorten($value), 'pattern' => $hit ];
                    }
                }
            } else {
                $value = (string) $payload;
                if ( $value !== '' ) {
                    $hit = $this->match_patterns( $value );
                    if ( $hit ) {
                        $count++;
                        $matches[] = [ 'source' => $key, 'field' => '', 'value' => $this->shorten($value), 'pattern' => $hit ];
                    }
                }
            }
        }

        if ( $count >= $this->notify_threshold ) {
            $event = $this->build_event( $matches );
            $this->maybe_create_table();
            $this->log_event_to_db( $event );
            error_log( '[SQLi-Notifier] ' . wp_json_encode( $event ) );
            $this->send_email( $event );
            if ( ! empty( $this->slack_webhook ) ) $this->send_slack( $event );
        }
    }

    private function match_patterns( $value ) {
        foreach ( $this->patterns as $pattern ) {
            if ( preg_match( $pattern, $value ) ) return $pattern;
        }
        return false;
    }

    private function build_event( $matches ) {
        $ip = $this->get_ip();
        $ua = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        $uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
        return [
            'time' => current_time( 'mysql' ),
            'ip'   => $ip,
            'ua'   => $ua,
            'uri'  => $uri,
            'matches' => $matches,
        ];
    }

    private function log_event_to_db( $event ) {
        global $wpdb;
        $table = $wpdb->prefix . 'sqli_events';
        $this->maybe_create_table();
        $wpdb->insert(
            $table,
            [
                'event_time' => $event['time'],
                'ip'         => $event['ip'],
                'user_agent' => mb_substr($event['ua'], 0, 255),
                'request_uri'=> mb_substr($event['uri'], 0, 255),
                'payload'    => wp_json_encode($event['matches']),
            ],
            [ '%s','%s','%s','%s','%s' ]
        );
    }

    private function send_email( $event ) {
        $subject = '[ALERT] Possible SQLi attempt on ' . get_bloginfo( 'name' );
        $body = "Time: {$event['time']}\nIP: {$event['ip']}\nURI: {$event['uri']}\nUA: {$event['ua']}\n\nMatches:\n";
        foreach ( $event['matches'] as $m ) {
            $body .= "- {$m['source']} [{$m['field']}] => {$m['value']} (pattern: {$m['pattern']})\n";
        }
        $body .= "\nInvestigate or block the IP if needed.";
        wp_mail( $this->admin_email, $subject, $body );
    }

    private function send_slack( $event ) {
        if ( empty($this->slack_webhook) ) return;
        $text = "*SQLi Alert* on *" . get_bloginfo('name') . "*\nTime: {$event['time']}\nIP: {$event['ip']}\nURI: {$event['uri']}\nMatches: " . count($event['matches']);
        $payload = [ 'text' => $text ];
        wp_remote_post( $this->slack_webhook, [
            'headers' => [ 'Content-Type' => 'application/json' ],
            'body'    => wp_json_encode( $payload ),
            'timeout' => 3,
        ] );
    }

    public function maybe_create_table() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'sqli_events';
        $charset_collate = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            event_time datetime NOT NULL,
            ip varchar(45) NOT NULL,
            user_agent varchar(255) NOT NULL,
            request_uri varchar(255) NOT NULL,
            payload longtext NOT NULL,
            PRIMARY KEY (id),
            KEY ip (ip),
            KEY event_time (event_time)
        ) $charset_collate;";
        require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
        dbDelta( $sql );
    }

    private function get_ip() {
        if ( ! empty($_SERVER['HTTP_X_FORWARDED_FOR']) ) {
            $ips = explode(',', wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR']));
            return trim($ips[0]);
        } elseif ( ! empty($_SERVER['REMOTE_ADDR']) ) {
            return sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
        }
        return '0.0.0.0';
    }

    private function shorten( $val, $len = 200 ) {
        $val = trim($val);
        return (mb_strlen($val) > $len) ? mb_substr($val,0,$len).'...' : $val;
    }
}

new WP_SQLi_Attack_Notifier();

 

How to configure & deploy

  1. Place file in wp-content/mu-plugins/ (always-on) or as a normal plugin.
  2. Edit the file to set $slack_webhook if you want Slack alerts.
  3. Monitor the DB table wp_sqli_events and error_log for events.
  4. Tune $patterns and $notify_threshold to reduce false positives.
  5. Optionally add automatic IP blocking logic (careful; can block legit clients).

Caveat: This plugin is heuristic-based — it detects common payloads but can generate false positives. Use a WAF (Cloudflare / ModSecurity / Sucuri) for stronger protection.

 

How to triage and respond when your notifier fires

  1. Review event details: timestamp, IP, user agent, request URI, matched payloads.
  2. Check whether the IP is legitimate: lookups, GeoIP, or known scanner lists.
  3. Block abusive IPs temporarily via firewall or .htaccess. For high confidence, block via WAF.
  4. Scan the site with WPScan, Wordfence, and Patchstack for known vulnerable plugins/themes.
  5. Patch and update affected plugins/themes/core immediately. CVE advisories often list remediation steps (example: CVE-2022-21661 required users to upgrade WordPress core).
  6. Rotate secrets (DB password, API keys) if you suspect a breach.
  7. Restore from backups if data integrity is compromised and investigate root cause.
Previous Article

Unsafe SQL Calls, Vulnerable Examples, and an Automatic SQLi Attack Notifier

Next Article

How to Generate a Personalized “I’m Attending” Event Banner Dynamically Using PHP and GD Library

Write a Comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.
Pure inspiration, zero spam ✨