2018-01-12 17:37:36 +01:00
|
|
|
<?php
|
|
|
|
|
2018-01-12 18:22:58 +01:00
|
|
|
# based on @jasonish idstools regexp
|
|
|
|
# https://github.com/jasonish/py-idstools/blob/master/idstools/rule.py
|
|
|
|
|
2018-07-19 11:48:22 +02:00
|
|
|
class SuricataRuleFormat
|
|
|
|
{
|
2018-01-12 17:37:36 +01:00
|
|
|
private $actions = array("alert", "log", "pass", "activate", "dynamic", "drop", "reject", "sdrop");
|
|
|
|
private $rule_pattern = '/(?P<action>%s)\s*'
|
|
|
|
. '(?P<protocol>[^\s]*)\s*'
|
|
|
|
. '(?P<src_ip>[^\s]*)\s*'
|
|
|
|
. '(?P<src_port>[^\s]*)\s*'
|
|
|
|
. '(?P<direction>->|<>)\s*'
|
|
|
|
. '(?P<dst_ip>[^\s]*)\s*'
|
|
|
|
. '(?P<dst_port>[^\s]*)\s*'
|
|
|
|
. '\((?P<options>.*)\)\s*'
|
|
|
|
. '/';
|
2018-03-02 19:08:59 +01:00
|
|
|
private $http_req_modifiers = array('http_uri', 'http_raw_uri', 'http_method', 'http_client_body', 'http_header', 'http_raw_header', 'http_cookie', 'http_user_agent', 'http_host', 'http_raw_host');
|
|
|
|
private $http_req_sticky = array('http_request_line', 'http_accept', 'http_accept_lang', 'http_accept_enc', 'http_referer', 'http_connection', 'http_content_type', 'http_content_len', 'http_start', 'http_protocol', 'http_header_names');
|
|
|
|
private $http_res_modifiers = array('http_stat_msg', 'http_stat_code', 'http_header', 'http_raw_header', 'http_cookie', 'http_server_body');
|
|
|
|
private $http_res_sticky = array('http_response_line', 'file_data', 'http_content_type', 'http_content_len', 'http_start', 'http_protocol', 'http_header_names');
|
2018-01-12 17:37:36 +01:00
|
|
|
|
2018-07-19 11:48:22 +02:00
|
|
|
private function findOptionEnd($options)
|
|
|
|
{
|
2018-01-12 18:22:58 +01:00
|
|
|
$offset = 0;
|
|
|
|
while (true) {
|
2018-01-19 18:31:30 +01:00
|
|
|
$i = strpos($options, ';', $offset);
|
2018-01-12 18:22:58 +01:00
|
|
|
if ($i === false) {
|
|
|
|
return -1;
|
|
|
|
}
|
2018-01-19 18:31:30 +01:00
|
|
|
if ($options[$offset + $i - 1] == '\\') {
|
2018-01-12 18:22:58 +01:00
|
|
|
$offset += 2;
|
2018-07-19 11:48:22 +02:00
|
|
|
} else {
|
2018-01-19 18:31:30 +01:00
|
|
|
return $offset + $i;
|
2018-01-12 18:22:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-19 11:48:22 +02:00
|
|
|
private function getOptions($options)
|
|
|
|
{
|
2018-01-19 18:31:30 +01:00
|
|
|
$opt_list = array();
|
|
|
|
|
|
|
|
if ($options == false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
while (true) {
|
|
|
|
if ($options == false) {
|
|
|
|
return $opt_list;
|
|
|
|
}
|
|
|
|
$index = $this->findOptionEnd($options);
|
|
|
|
if ($index < 0) {
|
|
|
|
throw new LogicException(
|
|
|
|
'SuricataRule - could not find end of options'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$option = substr($options, 0, $index);
|
|
|
|
$options = substr($options, $index + 1);
|
|
|
|
$delim = strpos($option, ':');
|
2018-03-02 19:08:59 +01:00
|
|
|
if ($delim === false) {
|
2018-01-19 18:31:30 +01:00
|
|
|
$name = $option;
|
|
|
|
$value = None;
|
2018-07-19 11:48:22 +02:00
|
|
|
} else {
|
2018-01-19 18:31:30 +01:00
|
|
|
$vals = explode(':', $option);
|
|
|
|
$name = $vals[0];
|
|
|
|
$value = $vals[1];
|
|
|
|
}
|
|
|
|
$name = str_replace(' ', '', $name);
|
|
|
|
$opt_list[$name] = $value;
|
|
|
|
}
|
|
|
|
return $opt_list;
|
|
|
|
}
|
|
|
|
|
2018-07-19 11:48:22 +02:00
|
|
|
private function parseRule($rule)
|
|
|
|
{
|
2018-01-12 17:37:36 +01:00
|
|
|
$regexp = sprintf($this->rule_pattern, join('|', $this->actions));
|
|
|
|
preg_match($regexp, $rule, $matches);
|
|
|
|
return $matches;
|
|
|
|
}
|
2018-01-12 18:22:58 +01:00
|
|
|
|
|
|
|
# function to validate the global syntax of a suricata rule
|
2018-07-19 11:48:22 +02:00
|
|
|
private function validateRuleSyntax($rule)
|
|
|
|
{
|
2018-01-12 18:22:58 +01:00
|
|
|
$matches = $this->parseRule($rule);
|
2018-03-02 19:08:59 +01:00
|
|
|
if (($matches === false) or ($matches['src_ip'] === false) or ($matches['dst_ip'] === false)) {
|
2018-01-12 18:22:58 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-01-19 18:45:18 +01:00
|
|
|
#function to validate http rule keywords order (sticky vs modifiers)
|
2018-07-19 11:48:22 +02:00
|
|
|
private function validateRuleHTTP($rule)
|
|
|
|
{
|
2018-03-02 19:08:59 +01:00
|
|
|
$matches = $this->parseRule($rule);
|
|
|
|
if ($matches['protocol'] != 'http') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
$options = $this->getOptions($matches['options']);
|
|
|
|
$keys = array_keys($options);
|
2018-07-19 11:48:22 +02:00
|
|
|
foreach ($keys as $k) {
|
|
|
|
if (in_array($k, $this->http_req_modifiers) or in_array($k, $this->http_res_modifiers)) {
|
2018-03-02 19:08:59 +01:00
|
|
|
$mod = array_search($k, $keys);
|
|
|
|
if (($mod != 0) and ($keys[$mod - 1] != 'content')) {
|
|
|
|
return false;
|
|
|
|
}
|
2018-07-19 11:48:22 +02:00
|
|
|
} elseif (in_array($k, $this->http_req_sticky) or in_array($k, $this->http_res_sticky)) {
|
2018-03-02 19:08:59 +01:00
|
|
|
$mod = array_search($k, $keys);
|
|
|
|
if (($mod != (count($keys) - 1)) and ($keys[$mod + 1] != 'content')) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-12 18:22:58 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-01-19 18:45:18 +01:00
|
|
|
# function to validate dns rule keywords order
|
2018-07-19 11:48:22 +02:00
|
|
|
private function validateRuleDNS($rule)
|
|
|
|
{
|
2018-01-19 18:45:18 +01:00
|
|
|
$matches = $this->parseRule($rule);
|
|
|
|
if ($matches['protocol'] != 'dns') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
$options = $this->getOptions($matches['options']);
|
|
|
|
$keys = array_keys($options);
|
|
|
|
$dns_query = array_search('dns_query', $keys);
|
|
|
|
if ($dns_query == false) {
|
|
|
|
return true;
|
|
|
|
}
|
2018-03-02 19:08:59 +01:00
|
|
|
if (($dns_query != (count($keys) - 1)) and ($keys[$dns_query + 1] != 'content')) {
|
2018-01-19 18:45:18 +01:00
|
|
|
return false;
|
|
|
|
}
|
2018-01-12 18:22:58 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
# function to validate the complete syntax of a suricata rule
|
2018-07-19 11:48:22 +02:00
|
|
|
# idea is to
|
|
|
|
public function validateRule($rule)
|
|
|
|
{
|
2018-01-19 18:31:30 +01:00
|
|
|
return $this->validateRuleSyntax($rule) and $this->validateRuleHTTP($rule) and $this->validateRuleDNS($rule);
|
2018-01-12 18:22:58 +01:00
|
|
|
}
|
2018-07-19 11:48:22 +02:00
|
|
|
}
|