module HackerOne module Client class Weakness class << self def validate_cwe!(cwe) fail NotAnOwaspWeaknessError if cwe.upcase.start_with?('CAPEC-') fail StandardError::ArgumentError unless cwe.upcase.start_with?('CWE-') end def extract_cwe_number(cwe) return if cwe.nil? validate_cwe!(cwe) cwe.split('CWE-').last.to_i end end class NotAnOwaspWeaknessError < StandardError def message "CAPEC labels do not describe OWASP weaknesses" end end CLASSIFICATION_MAPPING = { "None Applicable" => "A0-Other", "Denial of Service" => "A0-Other", "Memory Corruption" => "A0-Other", "Cryptographic Issue" => "A0-Other", "Privilege Escalation" => "A0-Other", "UI Redressing (Clickjacking)" => "A0-Other", "Command Injection" => "A1-Injection", "Remote Code Execution" => "A1-Injection", "SQL Injection" => "A1-Injection", "Authentication" => "A2-AuthSession", "Cross-Site Scripting (XSS)" => "A3-XSS", "Information Disclosure" => "A6-DataExposure", "Cross-Site Request Forgery (CSRF)" => "A8-CSRF", "Unvalidated / Open Redirect" => "A10-Redirects" } OWASP_TOP_10_2013_TO_CWE = { 'A1-Injection' => [77, 78, 88, 89, 90, 91, 564], 'A2-AuthSession' => [287, 613, 522, 256, 384, 472, 346, 441, 523, 620, 640, 319, 311], 'A3-XSS' => [79], 'A4-DirectObjRef' => [639, 99, 22], 'A5-Misconfig' => [16, 2, 215, 548, 209], 'A6-DataExposure' => [312, 319, 310, 326, 320, 311, 325, 328, 327], 'A7-MissingACL' => [285, 287], 'A8-CSRF' => [352, 642, 613, 346, 441], 'A9-KnownVuln' => [], 'A10-Redirects' => [601], }.freeze OWASP_DEFAULT = 'A0-Other'.freeze def initialize(weakness) @attributes = weakness end def to_owasp from_cwe = OWASP_TOP_10_2013_TO_CWE.map do |owasp, cwes| owasp if cwes.include?(self.class.extract_cwe_number(to_cwe)) end.compact.first from_cwe || CLASSIFICATION_MAPPING[@attributes[:name]] || OWASP_DEFAULT end def to_cwe @attributes[:external_id] end end end end