module Capra::SnortRuleParser

Public Class Methods

convert(rule) click to toggle source
# File lib/capra/snort_rule_parser.rb, line 31
def self.convert(rule)
  parsed_rule = self.parse(rule)
  puts "rule '#{parsed_rule[:protocol].upcase}' do |packet|"
  unless parsed_rule[:source_ip] == "any"
    if parsed_rule[:source_ip] == "$EXTERNAL_NET" # might want to check direction too?
      puts "\tnext unless packet.ip.external_source?"
    elsif parsed_rule[:source_ip][0] == "["
      puts "\tnext unless packet.ip.from_subnets?(#{parsed_rule[:source_ip].sub("[","").sub("]","").split(",").inspect})"
    else
      puts "\tnext unless packet.ip.src == '#{parsed_rule[:source_ip]}'"
    end
  end
  unless parsed_rule[:source_port] == "any"
    puts "\tnext unless packet.#{parsed_rule[:protocol]}.sport == #{parsed_rule[:source_port]}"
  end
  unless parsed_rule[:destination_ip] == "any"
    if parsed_rule[:destination_ip] == "$HOME_NET"
      puts "\tnext unless packet.ip.internal_destination?"
    elsif parsed_rule[:source_ip][0] == "["
      parsed_rule[:source_ip].sub("[","").sub("]","").split(",").each do |cidr|
        puts "\tnext unless packet.ip.to_subnets?(#{cidr})"
      end
    else
      puts "\tnext unless packet.ip.dst == '#{parsed_rule[:destination_ip]}'"
    end
  end
  unless parsed_rule[:destination_port] == "any"
    puts "\tnext unless packet.#{parsed_rule[:protocol]}.dport == #{parsed_rule[:destination_port]}"
  end
  # TODO: need to support mixed string and byte matching, even though it's insane
  if parsed_rule[:options]["content"]
    parsed_rule[:options]["content"].each do |content|
      if content[0] == "|" and content[-1] == "|"
        puts "\tnext packet.body.unpack('H*').first.include?(\"#{content.gsub("|","").split.join}\")"
      else
        puts "\tnext unless packet.body.include?(\"#{content}\")"
      end  
    end
    #puts "\tnext unless packet.body.include?(\"#{parsed_rule[:options]["content"]}\")"
  end
  if parsed_rule[:options]["pcre"]
    parsed_rule[:options]["pcre"].each do |pcre|
      regex, regex_ops = pcre.split("/")[1..-1]
      regex_ops_value = regex_ops.split('').map do |str|
        case str
        when "i"
          Regexp::IGNORECASE
        when "m"
          Regexp::MULTILINE
        when "x"
          Regexp::EXTENDED
        else
          0
        end
      end.sum
      puts "\tnext unless packet.body.match(Regexp.new('#{regex}', #{regex_ops_value}))"  
    end
  end
  if parsed_rule[:options]['flags']
    parsed_rule[:options]['flags'].each do |flag_opt|
      flag_opt.split('').each do |flag|
        case flag
        when 'F' #fin
          puts "\tnext unless packet.tcp.flag_fin?"
        when 'S' #syn
          puts "\tnext unless packet.tcp.flag_syn?"
        when 'R' #rst
          puts "\tnext unless packet.tcp.flag_rst?"
        when 'P' #psh
          puts "\tnext unless packet.tcp.flag_psh?"
        when 'A' #ack
          puts "\tnext unless packet.tcp.flag_ack?"
        when 'U' #urg
          puts "\tnext unless packet.tcp.flag_urg?"
        end
      end
    end
  end
  # alert probably needs to be the last thing in the rule for it to work properly in this case
  if parsed_rule[:options]["msg"]
    puts "\talert \"#{parsed_rule[:options]["msg"].first}\""
  end
  puts "end"
end
parse(rule) click to toggle source
# File lib/capra/snort_rule_parser.rb, line 3
def self.parse(rule)
  # alert tcp $EXTERNAL_NET any -> $HOME_NET 21 (msg:"FTP MDTM overflow attempt"; flow:to_server,established; content:"MDTM"; nocase; isdataat:100,relative; pcre:"/^MDTM\s[^\n]{100}/smi"; reference:bugtraq,9751; reference:cve,2001-1021; reference:cve,2004-0330; reference:nessus,12080; classtype:attempted-admin; sid:2546; rev:5;)
  rule_parts = rule.split
  
  rule_options = {} 

  rule_parts[7..-1].join(" ").sub("(",'').sub(")",'').split(";").map { |opt| opt.split(":").map { |val| val.gsub('"', '') }}.each do |k, v|
    k = k.strip
  
    if rule_options[k]
      rule_options[k] << v
    else
      rule_options[k] = [v]
    end
  end
  
  {
    action: rule_parts[0],
    protocol: rule_parts[1],
    source_ip: rule_parts[2],
    source_port: rule_parts[3],
    direction: rule_parts[4], # almost always -> unless you're crazy?
    destination_ip: rule_parts[5],
    destination_port: rule_parts[6],     
    options: rule_options
  }
end