PCRE pattern. Patterns run on a normalised, lower-case copy of the value. * * @return array */ public static function patterns() { return array( 'null-byte' => '/\x00/', 'path-traversal' => '#(?:^|[\\\\/])\.\.(?:[\\\\/]|$)#', 'sensitive-file' => '#(?:/etc/(?:passwd|shadow|group)\b|/proc/self/|(?:^|[\\\\/])wp-config\.php\b|php://(?:filter|input)|(?:file|expect|phar)://)#', 'sqli-union' => '/\bunion\b(?:\s+(?:all|distinct))?\s+select\b/', 'sqli-tautology' => '/[\'"`]\s*(?:or|and|\|\||&&)\s*[\'"`]?(\w+)[\'"`]?\s*(?:=|like)\s*[\'"`]?\1\b/', 'sqli-numeric' => '/\b(?:or|and)\s+(\d+)\s*=\s*\1\s*(?:--|#|$)/', 'sqli-time' => '/\b(?:sleep|benchmark|pg_sleep)\s*\(\s*\d|\bwaitfor\s+delay\s+[\'"]/', 'sqli-schema' => '/\binformation_schema\b|\bload_file\s*\(|\binto\s+(?:out|dump)file\b/', 'sqli-stacked' => '/;\s*(?:drop|truncate|alter)\s+table\b/', 'xss-script' => '/<\s*\/?\s*script\b/', 'xss-tag' => '/<\s*(?:iframe|object|embed|svg|base|meta)\b/', 'xss-handler' => '/<[^>]*[\s\/"\']on[a-z]{3,}\s*=/', 'xss-protocol' => '/(?:^|[\s"\'`=(<])(?:javascript|vbscript)\s*:|data\s*:\s*text\/html/', ); } /** * Normalise a value: decode one extra level of URL encoding, remove SQL inline comments and * collapse whitespace, lower-case. * * @param string $value Raw value. * @return string */ public static function normalise( $value ) { $value = substr( (string) $value, 0, self::MAX_LENGTH ); if ( false !== strpos( $value, '%' ) ) { $value = rawurldecode( $value ); } $value = preg_replace( '#/\*.*?\*/#s', ' ', $value ); $value = preg_replace( '/\s+/', ' ', (string) $value ); return strtolower( (string) $value ); } /** * First rule matching a single value. * * @param string $value Value. * @return string Rule id, or '' when nothing matches. */ public static function match( $value ) { $value = (string) $value; if ( '' === $value ) { return ''; } $normal = self::normalise( $value ); foreach ( self::patterns() as $id => $pattern ) { if ( preg_match( $pattern, $normal ) ) { return $id; } } return ''; } /** * Inspect an array of input (keys and, optionally, values), recursively. * * @param array $data Input array. * @param bool $with_values Also inspect values, not just keys. * @param string $prefix Field name prefix (used in recursion). * @param int $depth Current depth. * @return array|null array( 'rule' => id, 'field' => name ) for the first hit, or null. */ public static function inspect( array $data, $with_values = true, $prefix = '', $depth = 0 ) { if ( $depth > self::MAX_DEPTH ) { return null; } foreach ( $data as $key => $value ) { $field = '' === $prefix ? (string) $key : $prefix . '[' . $key . ']'; $rule = self::match( (string) $key ); if ( '' !== $rule ) { return array( 'rule' => $rule, 'field' => $field, ); } if ( is_array( $value ) ) { $hit = self::inspect( $value, $with_values, $field, $depth + 1 ); if ( $hit ) { return $hit; } } elseif ( $with_values && is_scalar( $value ) ) { $rule = self::match( (string) $value ); if ( '' !== $rule ) { return array( 'rule' => $rule, 'field' => $field, ); } } } return null; } }