diff --git a/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/ScannerDatabaseNames.java b/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/ScannerDatabaseNames.java index 88c27c826a..2c08e9c74a 100644 --- a/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/ScannerDatabaseNames.java +++ b/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/ScannerDatabaseNames.java @@ -61,5 +61,6 @@ public final class ScannerDatabaseNames { CPPCHECK_DB_NAME = "Cppcheck", CONTRAST_DB_NAME = "Contrast", SONATYPE_DB_NAME = "Manual", - SCARF_DB_NAME = "SWAMP SCARF"; + SCARF_DB_NAME = "SWAMP SCARF", + BARRACUDA_BVM_DB_NAME = "Barracuda Vulnerability Manager"; } diff --git a/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/ScannerType.java b/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/ScannerType.java index b7db7abc2e..ff415ea72b 100644 --- a/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/ScannerType.java +++ b/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/ScannerType.java @@ -64,7 +64,8 @@ public enum ScannerType { MANUAL("manual", "Manual", "Manual", MANUAL_DB_NAME), CONTRAST("contrast", "Contrast", "Contrast", CONTRAST_DB_NAME), SONATYPE("sonatype", "Sonatype", "Sonatype", SONATYPE_DB_NAME), - SCARF("scarf", SCARF_DB_NAME, SCARF_DB_NAME, SCARF_DB_NAME); + SCARF("scarf", SCARF_DB_NAME, SCARF_DB_NAME, SCARF_DB_NAME), + BARRACUDA_BVM("bvm", "Barracuda Vulnerability Manager", "Barracuda Vulnerability Manager", BARRACUDA_BVM_DB_NAME); public String displayName; private String shortName; @@ -176,4 +177,4 @@ public static List getScanAgentSupportedList() { public static String getEnumUpdatedDate(){ return "2015-07-29 10:56"; } -} \ No newline at end of file +} diff --git a/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/WafType.java b/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/WafType.java index 9dcd816f7c..9da5c0cdf7 100644 --- a/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/WafType.java +++ b/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/WafType.java @@ -47,6 +47,7 @@ public class WafType extends BaseEntity { public static final String IMPERVA_SECURE_SPHERE = "Imperva SecureSphere"; public static final String DENY_ALL_RWEB = "DenyAll rWeb"; public static final String RIVERBED_WEB_APP_FIREWALL = "SteelApp Web App Firewall"; + public static final String BARRACUDA_WAF = "Barracuda Web Application Firewall"; @NotEmpty(message = "{errors.required}") @Size(max = 50, message = "{errors.maxlength}") diff --git a/threadfix-importers/src/main/java/com/denimgroup/threadfix/importer/impl/upload/BVMImporter.java b/threadfix-importers/src/main/java/com/denimgroup/threadfix/importer/impl/upload/BVMImporter.java new file mode 100644 index 0000000000..69d6845d20 --- /dev/null +++ b/threadfix-importers/src/main/java/com/denimgroup/threadfix/importer/impl/upload/BVMImporter.java @@ -0,0 +1,177 @@ +package com.denimgroup.threadfix.importer.impl.upload; + +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.xml.sax.Attributes; + +import com.denimgroup.threadfix.annotations.ScanFormat; +import com.denimgroup.threadfix.annotations.ScanImporter; +import com.denimgroup.threadfix.data.ScanCheckResultBean; +import com.denimgroup.threadfix.data.ScanImportStatus; +import com.denimgroup.threadfix.data.entities.Finding; +import com.denimgroup.threadfix.data.entities.Scan; +import com.denimgroup.threadfix.data.entities.ScannerDatabaseNames; +import com.denimgroup.threadfix.data.entities.ScannerType; +import com.denimgroup.threadfix.importer.impl.AbstractChannelImporter; +import com.denimgroup.threadfix.importer.util.HandlerWithBuilder; +import com.denimgroup.threadfix.importer.util.RegexUtils; + +/** + * author: dsavelski, Barracuda Networks + */ +@ScanImporter( + // must match value in bnvlm.csv + scannerName = ScannerDatabaseNames.BARRACUDA_BVM_DB_NAME, + format = ScanFormat.XML, + startingXMLTags = { "va-engine-result" } +) +public class BVMImporter extends AbstractChannelImporter { + + public BVMImporter() { + super(ScannerType.BARRACUDA_BVM); + } + + @Override + public Scan parseInput() { + return parseSAXInput(new BVMScanSAXParser()); + } + + public class BVMScanSAXParser extends HandlerWithBuilder { + + private FindingKey currentKey = null; + private Map findingMap = new HashMap(); + private Map recommendationsMap = new HashMap(); + private String currentRecommendationId = null; + + private boolean inIssue = false; + private boolean inVariants = false; + private StringBuffer currentRawFinding = new StringBuffer(); + + public void add(Finding finding) { + if (finding != null) { + finding.setNativeId(getNativeId(finding)); + finding.setIsStatic(false); + saxFindingList.add(finding); + } + } + + //////////////////////////////////////////////////////////////////// + // Event handlers. + //////////////////////////////////////////////////////////////////// + + @Override + public void startElement(String uri, String name, + String qName, Attributes atts) { + + if ("recommendation".equalsIgnoreCase(qName)) + currentRecommendationId = atts.getValue("id"); + + if ("issue".equalsIgnoreCase(qName)) { + inIssue = true; + + findingMap = new HashMap(); + findingMap.put(FindingKey.VULN_CODE, atts.getValue("IssueName")); + findingMap.put(FindingKey.DETAIL, atts.getValue("IssueName")); + findingMap.put(FindingKey.SEVERITY_CODE, atts.getValue("severity")); + } + + if ("variants".equalsIgnoreCase(qName)) + inVariants = true; + + if (inIssue) + currentRawFinding.append(makeTag(name, qName , atts)); + + if ("url".equals(qName)){ + currentKey = FindingKey.PATH; + } else if ("cve".equals(qName)){ + currentKey = FindingKey.CVE; + } else if ("cwe".equals(qName)){ + currentKey = FindingKey.CWE; + } else if ("confidence".equals(qName)){ + currentKey = FindingKey.CONFIDENCE_RATING; + } else if ("recommendationId".equals(qName)){ + currentKey = FindingKey.RECOMMENDATION; + } else if ("entity_name".equals(qName)){ + currentKey = FindingKey.PARAMETER; + } else if (inVariants && "description".equals(qName)){ + currentKey = FindingKey.DETAIL; + } else if (inVariants && "attackVector".equals(qName)){ + currentKey = FindingKey.ATTACK_STRING; + } + + } + + @Override + public void endElement(String uri, String name, String qName) { + if ("variants".equalsIgnoreCase(qName)) + inVariants = false; + + if (inIssue) + currentRawFinding.append(""); + + if ("recommendation".equalsIgnoreCase(qName)) { + String rec_text = getBuilderText(); + recommendationsMap.put(currentRecommendationId, rec_text); + currentRecommendationId = null; + } + + if ("issue".equalsIgnoreCase(qName)) { + inIssue = false; + + findingMap.put(FindingKey.RAWFINDING, currentRawFinding.toString()); + currentRawFinding.setLength(0); + + Finding finding = constructFinding(findingMap); + add(finding); + + } else if (currentKey != null) { + // getBuilderText retrieves the text from the builder. + // this should contain any text in the tag associated with the key + + String tmpBuilderText = getBuilderText(); + + if (currentKey == FindingKey.CWE) { + tmpBuilderText = RegexUtils.getRegexResult(tmpBuilderText, "^CWE-([0-9]+)$"); + findingMap.put(FindingKey.CWE, tmpBuilderText); // + } + + if (currentKey == FindingKey.RECOMMENDATION) { + String recommendation = recommendationsMap.get(tmpBuilderText); + if (! "".equals(recommendation)) + tmpBuilderText = recommendation; + } + + findingMap.put(currentKey, tmpBuilderText); + currentKey = null; + } + } + + @Override + public void characters (char ch[], int start, int length) { + // if we're in an element that we should record, add the text between tags to the builder + + if (currentKey != null || currentRecommendationId != null) + addTextToBuilder(ch, start, length); + + if (inIssue) + currentRawFinding.append(ch,start,length); + } + } + + @Nonnull + @Override + public ScanCheckResultBean checkFile() { + + // Do checks to determine the correct ScanImportStatus to return + // this is where duplicate scan checking happens + // the Calendar should be the scan date + + return new ScanCheckResultBean(ScanImportStatus.SUCCESSFUL_SCAN, Calendar.getInstance()); + } + + +} diff --git a/threadfix-importers/src/main/java/com/denimgroup/threadfix/service/waf/BarracudaWebAppFirewallGenerator.java b/threadfix-importers/src/main/java/com/denimgroup/threadfix/service/waf/BarracudaWebAppFirewallGenerator.java new file mode 100644 index 0000000000..0bfe540c19 --- /dev/null +++ b/threadfix-importers/src/main/java/com/denimgroup/threadfix/service/waf/BarracudaWebAppFirewallGenerator.java @@ -0,0 +1,309 @@ +package com.denimgroup.threadfix.service.waf; + +import static com.denimgroup.threadfix.CollectionUtils.map; +import static com.denimgroup.threadfix.CollectionUtils.list; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringEscapeUtils; + +import com.denimgroup.threadfix.annotations.WebApplicationFirewall; +import com.denimgroup.threadfix.data.entities.GenericSeverity; +import com.denimgroup.threadfix.data.entities.GenericVulnerability; +import com.denimgroup.threadfix.data.entities.Vulnerability; +import com.denimgroup.threadfix.data.entities.WafRule; +import com.denimgroup.threadfix.data.entities.WafRuleDirective; +import com.denimgroup.threadfix.data.entities.WafType; +import com.denimgroup.threadfix.data.entities.SurfaceLocation; + +/** + * author: dsavelski, Barracuda Networks + */ +@WebApplicationFirewall(name = WafType.BARRACUDA_WAF) +public class BarracudaWebAppFirewallGenerator extends RealTimeProtectionGenerator { + + private static final String DEFAULT_REMEDY = ""; + + private static final Map VULN_TYPE_REMEDY_MAP = map( + + //OS + GenericVulnerability.CWE_EVAL_INJECTION, "__os_command_injection_parameter", + GenericVulnerability.CWE_OS_COMMAND_INJECTION, "__os_command_injection_parameter", + "Improper Neutralization of Special Elements used in a Command ('Command Injection')", "__os_command_injection_parameter", + + //SQL + GenericVulnerability.CWE_SQL_INJECTION, "__sql_injection_parameter", + "Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')", "__sql_injection_parameter", + + //RFI + "Improper Control of Filename for Include/Require Statement in PHP Program ('PHP Remote File Inclusion')", "__remote_file_inclusion_parameter", + "URL Redirection to Untrusted Site ('Open Redirect')", "__remote_file_inclusion_parameter" , + + //XSS + GenericVulnerability.CWE_CROSS_SITE_SCRIPTING, "__xss_parameter", + "Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", "__xss_parameter", + + GenericVulnerability.CWE_DIRECTORY_INDEXING, "__data_theft_directory_indexing", + "Exposure of Backup File to an Unauthorized Control Sphere","__local_adr_deny_rule", + + //OTHERS + GenericVulnerability.CWE_PATH_TRAVERSAL, "__directory_traversal_parameter", + GenericVulnerability.CWE_HTTP_RESPONSE_SPLITTING, "__param_response_splitting", + GenericVulnerability.CWE_CROSS_SITE_REQUEST_FORGERY, "__csrf_in_url", + + "Improper Restriction of Excessive Authentication Attempts", "__param_brute_force", + "Response Discrepancy Information Exposure","__param_brute_force", + + "Information Exposure Through an Error Message", "__data_theft_errors_on_page", + "Protection Mechanism Failure", "__clickjacking", + "File and Directory Information Exposure", "__local_adr_deny_rule", + "Sensitive Cookie in HTTPS Session Without 'Secure' Attribute","__cookie_secure" + ); + + private static final Map SEVERITIES_MAP = map( + GenericSeverity.CRITICAL, "High", + GenericSeverity.HIGH, "High", + GenericSeverity.MEDIUM, "Medium", + GenericSeverity.LOW, "Low", + GenericSeverity.INFO, "Informational"); + + private static Map RECOMMENDATIONS_MAP = map(); + + public BarracudaWebAppFirewallGenerator() { + this.defaultDirective = "none"; + } + + @Override + protected String[] getSupportedVulnerabilityTypes() { + //SHOULD NO BE USED ANYMORE after overiding makeRule() + return VULN_TYPE_REMEDY_MAP.keySet().toArray(new String[] {}); + } + + public static String getStart(List rules) { + if (rules == null || rules.size() == 0) { + return null; + } + + RECOMMENDATIONS_MAP.clear(); + + String xml_host="", xml_duration="0:0:0.0"; + int info_vuls=0, triv_vuls=0, medium_vuls=0, high_vuls=0; + + int recommendation_fix_id=0; + List recs = list(); + List issues = list(); + + for (WafRule rule : rules) { + if (rule == null || rule.getRule() == null || rule.getVulnerability() == null + || rule.getVulnerability().getSurfaceLocation() == null) { + continue; + } + + Vulnerability vul = rule.getVulnerability(); + SurfaceLocation surf = vul.getSurfaceLocation(); + + String parameter = rule.getParameter(); + + if ("".equals(xml_host) && rule.getVulnerability() != null && + rule.getVulnerability().getSurfaceLocation() != null && + rule.getVulnerability().getSurfaceLocation().getHost() != null && + rule.getVulnerability().getSurfaceLocation().getProtocol() != null) { + + SurfaceLocation tmp_sl = rule.getVulnerability().getSurfaceLocation(); + xml_host = StringEscapeUtils.escapeXml11(tmp_sl.getProtocol() + "://" + tmp_sl.getHost()); + } + + // Count for the XML info + String severity = SEVERITIES_MAP.get(rule.getVulnerability().getGenericSeverity().getName()); + if (severity == "High") { + high_vuls++; + }else if(severity == "Medium") { + medium_vuls++; + }else if(severity == "Low") { + triv_vuls++; + }else if(severity == "Informational") { + info_vuls++; + }else{ + System.out.println("BarracudaWebFirewallGenerator.getStart(): Unknown Severity: " + severity + " in rule: " + rule); + } + + String rule_recommandation = rule.getVulnerability().getOriginalFinding().getScannerRecommendation(); + if (! RECOMMENDATIONS_MAP.containsKey(rule_recommandation)) { + RECOMMENDATIONS_MAP.put(rule_recommandation, recommendation_fix_id); + + recs.add(" " + + StringEscapeUtils.escapeXml11(rule_recommandation) + "\n"); + + recommendation_fix_id++; + } + + String issue = generateIssue(vul.getGenericVulnerability().getName(), surf.getPath(), null, rule.getNativeId(), parameter, vul); + issues.add(issue + "\n"); + } + + StringBuffer buffer = new StringBuffer(MessageFormat.format(BWFStrings.XML_DOC_START, xml_host, xml_duration, + rules.size(), info_vuls, triv_vuls, medium_vuls ,high_vuls)); + + for (String s: recs) + buffer.append(s); + + buffer.append(BWFStrings.XML_ISSUES_START); + + for (String s: issues) + buffer.append(s); + + return buffer.toString(); + } + + public static String getEnd(List rules) { + return BWFStrings.XML_DOC_END; + } + + @Override + protected WafRule makeRule(Integer currentId, Vulnerability vulnerability, WafRuleDirective directive) { + // We generate all the rules in getStart() because there is information in beginning of XML that depends on rules (Such as recommendation) + if (currentId == null || vulnerability == null + || vulnerability.getSurfaceLocation() == null + || vulnerability.getGenericVulnerability() == null + || vulnerability.getGenericVulnerability().getName() == null) { + return null; + } + + String remedy_for_waf = VULN_TYPE_REMEDY_MAP.get(vulnerability.getGenericVulnName()); + if (remedy_for_waf == null) + remedy_for_waf = DEFAULT_REMEDY; + + //Generate fake/comment rule. Reason: rule can't be empty later on. + String rule = " "; + WafRule newRule = new WafRule(); + newRule.setRule(rule); + newRule.setNativeId(currentId.toString()); + return newRule; + } + + protected static String generateIssue(String genericVulnName, String uri, String action, String id, String parameter, + Vulnerability vuln) { + + String vulDesc = ""; + if (vuln != null && uri != null && vuln.getOriginalFinding() != null + && vuln.getOriginalFinding().getScan() != null && vuln.getOriginalFinding().getScannerDetail() != null) { + + vulDesc = StringEscapeUtils.escapeXml11(vuln.getOriginalFinding().getScannerDetail()); + + if (vulDesc == null) + vulDesc = ""; + } + + String severity = "Informational"; + if (vuln != null && vuln.getGenericSeverity() != null && vuln.getGenericSeverity().getName() != null) { + severity = SEVERITIES_MAP.get(vuln.getGenericSeverity().getName()); + } + + if (genericVulnName != null) { + String entity_param = "", entity_type= ""; + + if (parameter == null && vuln.getSurfaceLocation().getParameter() != null && !vuln.getSurfaceLocation().getParameter().isEmpty()) + parameter = vuln.getSurfaceLocation().getParameter().replaceFirst("param=", ""); + + // URL & Parameter + if (parameter != null && !parameter.isEmpty()) { + entity_param = parameter; + entity_type = "Parameter"; + } + + String handled = "no"; + String remedy_for_waf = VULN_TYPE_REMEDY_MAP.get(vuln.getGenericVulnName()); + if (remedy_for_waf == null) + remedy_for_waf = DEFAULT_REMEDY; + else + handled = "yes"; + + String recommendationId = "0000" + id; + + String recommandation = vuln.getOriginalFinding().getScannerRecommendation(); + if (RECOMMENDATIONS_MAP.containsKey(recommandation)) + recommendationId = RECOMMENDATIONS_MAP.get(recommandation).toString(); + + String variantId = id; + String url_humanized = vuln.getSurfaceLocation().getHumanLocation(); + String attack_vector = StringEscapeUtils.escapeXml11(vuln.getOriginalFinding().getAttackString()); + + String confidence = ""; + if (vuln.getOriginalFinding().getConfidenceRating() != null) + confidence = "" + vuln.getOriginalFinding().getConfidenceRating(); + + String cwe = ""; + if (vuln.getGenericVulnerability().getCweId() != null) + cwe = "CWE-" + vuln.getGenericVulnerability().getCweId(); + + if (attack_vector==null) + attack_vector = ""; + + String rule_text = MessageFormat.format(BWFStrings.XML_ISSUE_TEMPLATE, + genericVulnName, handled, id, severity, cwe, confidence, url_humanized, + entity_param, entity_type, recommendationId, variantId, + attack_vector, vulDesc, remedy_for_waf); + + return rule_text; + } else { + System.out.println("BNVLMWAFGenerator.generateRuleText() -> Error: returning NULL"); + return null; + } + + } + + private class BWFStrings { + static final String XML_DOC_START = "" + + "\n" + + "\n " + + "\n Barracuda Vulnerability Manager" + + "\n 1.0" + + "\n " + + "\n " + + "\n " + + "\n " + // {0} host + "\n {1}" + // {1} scan duration + "\n {2}" + // {2} total + "\n {3}" + // {3} informative + "\n {4}" + // {4} trivial + "\n {5}" + // {5} medium + "\n {6}" + // {6} high + "\n " + + "\n " + + "\n " + + "\n " + + "\n"; + + static final String XML_ISSUES_START = "\n \n "; + + static final String XML_ISSUE_TEMPLATE = "\n " + // {2}, {3} + "\n " + + "\n " + + "\n {4}" + // {4} cwe + "\n {5}" + // {5} confidence + "\n {6}" + // {6} url + "\n {7}" + // {7} parameter name + "\n {8}" + // {8} type + "\n fix_{9}" + // {9} recommendation + "\n " + + "\n " + // {10} variant + "\n {11}" + // {11} attack vector + "\n {12}" + // {12} description + "\n " + + "\n " + + "\n " + + "\n " + + "\n " + + "\n {13}" + // {13} barracuda waf remedy id + "\n " + + "\n " + + "\n "; + + static final String XML_DOC_END = "\n \n 0\n\n"; + + } +} diff --git a/threadfix-importers/src/main/java/com/denimgroup/threadfix/service/waf/RealTimeProtectionGenerator.java b/threadfix-importers/src/main/java/com/denimgroup/threadfix/service/waf/RealTimeProtectionGenerator.java index 44b8a591d8..e33554cdd1 100644 --- a/threadfix-importers/src/main/java/com/denimgroup/threadfix/service/waf/RealTimeProtectionGenerator.java +++ b/threadfix-importers/src/main/java/com/denimgroup/threadfix/service/waf/RealTimeProtectionGenerator.java @@ -398,7 +398,8 @@ protected boolean stringInList(String string, String[] list) { public static boolean hasStartAndEnd(String type) { return type.equals(WafType.BIG_IP_ASM) || type.equals(WafType.IMPERVA_SECURE_SPHERE) || - type.equals(WafType.RIVERBED_WEB_APP_FIREWALL); + type.equals(WafType.RIVERBED_WEB_APP_FIREWALL) || + type.equals(WafType.BARRACUDA_WAF); } public static String getStart(String type, List rules) { @@ -408,6 +409,8 @@ public static String getStart(String type, List rules) { return ImpervaSecureSphereGenerator.getStart(rules); } else if (type.equals(WafType.RIVERBED_WEB_APP_FIREWALL)) { return RiverbedStartAndEndHolder.getStart(rules); + } else if (type.equals(WafType.BARRACUDA_WAF)) { + return BarracudaWebAppFirewallGenerator.getStart(rules); } else { return null; } @@ -420,6 +423,8 @@ public static String getEnd(String type, List rules) { return ImpervaSecureSphereGenerator.getEnd(rules); } else if (type.equals(WafType.RIVERBED_WEB_APP_FIREWALL)) { return RiverbedStartAndEndHolder.getEnd(rules); + } else if (type.equals(WafType.BARRACUDA_WAF)) { + return BarracudaWebAppFirewallGenerator.getEnd(rules); } else { return null; } diff --git a/threadfix-importers/src/main/resources/mappings/scanner/bvm.csv b/threadfix-importers/src/main/resources/mappings/scanner/bvm.csv new file mode 100644 index 0000000000..a77a9bc502 --- /dev/null +++ b/threadfix-importers/src/main/resources/mappings/scanner/bvm.csv @@ -0,0 +1,9 @@ +12/30/2015 00:08:00 +type.info,,, +Barracuda Vulnerability Manager,https://bvm.barracudanetworks.com,1.0,Use from this site +type.vulnerabilities,,, +type.severities,,, +Informational,Informational,1,1 +Low,Low,2,2 +Medium,Medium,3,3 +High,High,4,4 diff --git a/threadfix-importers/src/main/resources/mappings/waf/barracuda-waf.csv b/threadfix-importers/src/main/resources/mappings/waf/barracuda-waf.csv new file mode 100644 index 0000000000..aa00182dc4 --- /dev/null +++ b/threadfix-importers/src/main/resources/mappings/waf/barracuda-waf.csv @@ -0,0 +1,5 @@ +9/16/2014 00:00:00 +type.name +Barracuda Web Application Firewall +type.directives +none