Automatic WordPress security: local firewall, malware and file-integrity scanning, vulnerability protection, quarantine, scheduled backups, an optional page cache, and automatic global threat intelligence. See README.md for installation, update, and uninstall instructions.
123 lines
4.8 KiB
PHP
123 lines
4.8 KiB
PHP
<?php
|
|
|
|
if ( ! class_exists( 'Argus_WAF_Rules' ) ) {
|
|
|
|
class Argus_WAF_Rules {
|
|
|
|
public static function corpus() {
|
|
static $rules = null;
|
|
if ( null !== $rules ) {
|
|
return $rules;
|
|
}
|
|
|
|
$rules = array(
|
|
|
|
array( 'id' => 'sqli-union-select', 'category' => 'sql_injection', 'severity' => 'critical',
|
|
'pattern' => '/\bunion\b[^\w]{1,20}\bselect\b/i' ),
|
|
array( 'id' => 'sqli-classic-tautology', 'category' => 'sql_injection', 'severity' => 'critical',
|
|
|
|
'pattern' => '/(\%27|\'|\%22|")\s*(or|and)\s*(\%27|\'|\%22|")?[\d\w]+(\%27|\'|\%22|")?\s*=\s*(\%27|\'|\%22|")?[\d\w]+(\%27|\'|\%22|")?/i' ),
|
|
array( 'id' => 'sqli-information-schema', 'category' => 'sql_injection', 'severity' => 'critical',
|
|
'pattern' => '/information_schema|sysobjects|sysdatabases/i' ),
|
|
array( 'id' => 'sqli-time-based', 'category' => 'sql_injection', 'severity' => 'high',
|
|
'pattern' => '/\b(sleep|benchmark|pg_sleep|waitfor\s+delay)\s*\(/i' ),
|
|
array( 'id' => 'sqli-stacked-comment', 'category' => 'sql_injection', 'severity' => 'high',
|
|
'pattern' => '/;\s*(drop|delete|update|insert)\s+(table|from|into)/i' ),
|
|
|
|
array( 'id' => 'xss-script-tag', 'category' => 'xss', 'severity' => 'high',
|
|
'pattern' => '/<\s*script[\s>\/]/i' ),
|
|
|
|
array( 'id' => 'xss-event-handler', 'category' => 'xss', 'severity' => 'high',
|
|
'pattern' => '/\bon(error|load|mouseover|click|focus)\s*=\s*["\']?[^"\'>]*[\(\{]/i' ),
|
|
array( 'id' => 'xss-javascript-uri', 'category' => 'xss', 'severity' => 'high',
|
|
'pattern' => '/javascript\s*:\s*[^\s]/i' ),
|
|
array( 'id' => 'xss-svg-onload', 'category' => 'xss', 'severity' => 'high',
|
|
'pattern' => '/<\s*svg[^>]*onload/i' ),
|
|
|
|
array( 'id' => 'cmdi-shell-metachars', 'category' => 'rce', 'severity' => 'critical',
|
|
'pattern' => '/;\s*(cat|ls|whoami|id|uname|wget|curl)\s/i' ),
|
|
array( 'id' => 'cmdi-backtick-subshell', 'category' => 'rce', 'severity' => 'critical',
|
|
'pattern' => '/`[^`]{1,80}`|\$\([^\)]{1,80}\)/' ),
|
|
array( 'id' => 'phpi-eval-base64', 'category' => 'rce', 'severity' => 'critical',
|
|
'pattern' => '/\beval\s*\(\s*(base64_decode|gzinflate|str_rot13)\s*\(/i' ),
|
|
array( 'id' => 'phpi-dangerous-function', 'category' => 'rce', 'severity' => 'critical',
|
|
'pattern' => '/\b(system|exec|shell_exec|passthru|proc_open|popen)\s*\(/i' ),
|
|
array( 'id' => 'phpi-tag-in-input', 'category' => 'rce', 'severity' => 'high',
|
|
'pattern' => '/<\?php|<\?=/i' ),
|
|
|
|
array( 'id' => 'lfi-dot-dot-slash', 'category' => 'file_access', 'severity' => 'high',
|
|
'pattern' => '/(\.\.\/|\.\.\\\\|%2e%2e%2f|%252e%252e%252f)/i' ),
|
|
array( 'id' => 'lfi-sensitive-file', 'category' => 'file_access', 'severity' => 'high',
|
|
'pattern' => '/\/etc\/(passwd|shadow|hosts)\b|wp-config\.php/i' ),
|
|
array( 'id' => 'lfi-php-wrapper', 'category' => 'file_access', 'severity' => 'high',
|
|
'pattern' => '/php:\/\/(filter|input|data)/i' ),
|
|
array( 'id' => 'rfi-remote-scheme', 'category' => 'file_access', 'severity' => 'high',
|
|
'pattern' => '/^(https?|ftp):\/\/.+\.(php|txt)(\?|$)/i' ),
|
|
|
|
array( 'id' => 'proto-null-byte', 'category' => 'protocol_anomaly', 'severity' => 'medium',
|
|
'pattern' => '/%00/' ),
|
|
array( 'id' => 'proto-double-encoding', 'category' => 'protocol_anomaly', 'severity' => 'low',
|
|
'pattern' => '/%25(2e|2f|5c)/i' ),
|
|
);
|
|
|
|
return $rules;
|
|
}
|
|
|
|
public static function scan( array $inputs ) {
|
|
$hits = array();
|
|
|
|
foreach ( $inputs as $source => $value ) {
|
|
if ( ! is_string( $value ) || '' === $value ) {
|
|
continue;
|
|
}
|
|
|
|
$decoded = rawurldecode( $value );
|
|
|
|
foreach ( self::corpus() as $rule ) {
|
|
$matched = preg_match( $rule['pattern'], $decoded, $m ) ? $m : ( preg_match( $rule['pattern'], $value, $m ) ? $m : null );
|
|
if ( null === $matched ) {
|
|
continue;
|
|
}
|
|
if ( 'rfi-remote-scheme' === $rule['id'] && ! self::is_cross_origin( $matched[0] ) ) {
|
|
|
|
continue;
|
|
}
|
|
$hits[] = array_merge(
|
|
$rule,
|
|
array(
|
|
'source' => $source,
|
|
'matched_value' => mb_substr( $value, 0, 200 ),
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
return $hits;
|
|
}
|
|
|
|
protected static function is_cross_origin( $url ) {
|
|
$target_host = strtolower( (string) parse_url( $url, PHP_URL_HOST ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
|
$own_host = strtolower( (string) ( $_SERVER['HTTP_HOST'] ?? '' ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
|
|
|
if ( '' === $target_host || '' === $own_host ) {
|
|
return true;
|
|
}
|
|
|
|
$own_host = preg_replace( '/:\d+$/', '', $own_host );
|
|
return $target_host !== $own_host;
|
|
}
|
|
|
|
public static function highest_severity( array $hits ) {
|
|
$order = array( 'critical' => 4, 'high' => 3, 'medium' => 2, 'low' => 1, 'info' => 0 );
|
|
$best = 'info';
|
|
foreach ( $hits as $hit ) {
|
|
if ( ( $order[ $hit['severity'] ] ?? 0 ) > ( $order[ $best ] ?? 0 ) ) {
|
|
$best = $hit['severity'];
|
|
}
|
|
}
|
|
return $best;
|
|
}
|
|
}
|
|
|
|
}
|