Class: ShieldPressTracker::SecurityCollector
- Inherits:
-
Object
- Object
- ShieldPressTracker::SecurityCollector
- Defined in:
- lib/shieldpress_tracker/collectors.rb
Constant Summary collapse
- RULES =
[ [:sql_injection, :critical, /\b(union|select|drop|delete)\b.*\b(from|table|where)\b|--|\/\*/i], [:xss_attempt, :high, /<script|javascript:|onerror\s*=/i], [:command_injection, :critical, /[;&|`]\s*(rm|curl|wget|bash|sh|python)\b|\$\(/i], [:suspicious_path, :high, /\.\.\/|%2e%2e|\/\.env|\/\.git/i], [:bot_detected, :medium, /sqlmap|nikto|nmap|burp|nuclei|wpscan/i] ].freeze
Instance Method Summary collapse
- #analyze(url:, method: "GET", headers: {}, body: nil, ip: nil, query: nil) ⇒ Object
- #flush ⇒ Object
-
#initialize(maximum) ⇒ SecurityCollector
constructor
A new instance of SecurityCollector.
- #record(type:, severity:, message:, **data) ⇒ Object
Constructor Details
#initialize(maximum) ⇒ SecurityCollector
Returns a new instance of SecurityCollector.
67 |
# File 'lib/shieldpress_tracker/collectors.rb', line 67 def initialize(maximum); @maximum, @events, @ips, @mutex = maximum, [], {}, Mutex.new end |
Instance Method Details
#analyze(url:, method: "GET", headers: {}, body: nil, ip: nil, query: nil) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/shieldpress_tracker/collectors.rb', line 75 def analyze(url:, method: "GET", headers: {}, body: nil, ip: nil, query: nil) headers = headers.transform_keys { |k| k.to_s.downcase }; text = "#{url} #{body} #{query.to_json}"; ua = headers["user-agent"].to_s RULES.each do |type, severity, pattern| target = type == :bot_detected ? ua : text record(type: type, severity: severity, message: "#{type.to_s.tr('_', ' ')} detected in request", ip: ip, url: url, method: method, userAgent: ua, payload: %i[sql_injection xss_attempt command_injection].include?(type) ? text[0, 500] : nil) if pattern.match?(target) end record(type: :oversized_payload, severity: :medium, message: "Request payload exceeds 10MB", ip: ip, url: url, method: method) if headers["content-length"].to_i > 10 * 1024 * 1024 rescue StandardError nil end |
#flush ⇒ Object
86 87 88 89 90 91 92 93 94 95 |
# File 'lib/shieldpress_tracker/collectors.rb', line 86 def flush events = @mutex.synchronize { rows = @events; @events = []; rows } return nil if events.empty? && @ips.empty? severity = %i[critical high medium low info].to_h { |x| [x, 0] }; types = Hash.new(0) events.each { |e| severity[e[:severity]] += 1; types[e[:type]] += 1 } ips = @ips.map { |ip, row| { ip: ip, count: row[:count], types: row[:types] } }.sort_by { |x| -x[:count] }.first(10) { totalEvents: events.length, bySeverity: severity, byType: types, topOffendingIps: ips, events: events.last(50) } rescue StandardError nil end |
#record(type:, severity:, message:, **data) ⇒ Object
68 69 70 71 72 73 74 |
# File 'lib/shieldpress_tracker/collectors.rb', line 68 def record(type:, severity:, message:, **data) event = { type: type, severity: severity, message: .to_s[0, 4096], timestamp: Time.now.utc.iso8601 }.merge(data.compact) @mutex.synchronize do @events << event; @events = @events.last(@maximum) if data[:ip]; row = (@ips[data[:ip]] ||= { count: 0, types: [] }); row[:count] += 1; row[:types] |= [type]; end end end |