////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2009-2013 Denim Group, Ltd.
//
// The contents of this file are subject to the Mozilla Public License
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is ThreadFix.
//
// The Initial Developer of the Original Code is Denim Group, Ltd.
// Portions created by Denim Group, Ltd. are Copyright (C)
// Denim Group, Ltd. All Rights Reserved.
//
// Contributor(s): Denim Group, Ltd.
//
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.service.channel;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import com.denimgroup.threadfix.data.dao.ChannelSeverityDao;
import com.denimgroup.threadfix.data.dao.ChannelTypeDao;
import com.denimgroup.threadfix.data.dao.ChannelVulnerabilityDao;
import com.denimgroup.threadfix.data.entities.ChannelType;
import com.denimgroup.threadfix.data.entities.Finding;
import com.denimgroup.threadfix.data.entities.Scan;
import com.denimgroup.threadfix.webapp.controller.ScanCheckResultBean;
/**
*
* @author mcollins
*/
public class ArachniChannelImporter extends AbstractChannelImporter {
private static Map<String, FindingKey> tagMap = new HashMap<>();
static {
tagMap.put("name", FindingKey.VULN_CODE);
tagMap.put("code", FindingKey.SEVERITY_CODE);
tagMap.put("variable", FindingKey.PARAMETER);
tagMap.put("var", FindingKey.PARAMETER);
tagMap.put("url", FindingKey.PATH);
}
// Since the severity mappings are static and not included in the XML output,
// these have been reverse engineered from the code
private static Map<String, String> severityMap = new HashMap<String, String>();
static {
severityMap.put("Allowed HTTP methods", "INFORMATIONAL");
severityMap.put("A backdoor file exists on the server.", "HIGH");
severityMap.put("A backup file exists on the server.", "HIGH");
severityMap.put("Code injection", "HIGH");
severityMap.put("Code injection (timing attack)", "HIGH");
severityMap.put("A common directory exists on the server.", "MEDIUM");
severityMap.put("A common sensitive file exists on the server.", "LOW");
severityMap.put("Cross-Site Request Forgery", "HIGH");
severityMap.put("Directory listing is enabled.", "LOW");
severityMap.put("Misconfiguration in LIMIT directive of .htaccess file.", "HIGH");
severityMap.put("HTTP PUT is enabled.", "HIGH");
severityMap.put("Interesting server response.", "INFORMATIONAL");
severityMap.put("LDAP Injection", "HIGH");
severityMap.put("Operating system command injection", "HIGH");
severityMap.put("Operating system command injection (timing attack)", "HIGH");
severityMap.put("Path Traversal", "MEDIUM");
severityMap.put("Response splitting", "MEDIUM");
severityMap.put("Remote file inclusion", "HIGH");
severityMap.put("SQL Injection", "HIGH");
severityMap.put("Blind SQL Injection", "HIGH");
severityMap.put("Blind SQL Injection (timing attack)", "HIGH");
severityMap.put("Unencrypted password form.", "MEDIUM");
severityMap.put("Unvalidated redirect", "MEDIUM");
severityMap.put("WebDAV", "INFORMATIONAL");
severityMap.put("XPath Injection", "HIGH");
severityMap.put("Cross-Site Scripting (XSS)", "HIGH");
severityMap.put("Cross-Site Scripting in event tag of HTML element.", "HIGH");
severityMap.put("Cross-Site Scripting (XSS) in path", "HIGH");
severityMap.put("Cross-Site Scripting in HTML \"script\" tag.", "HIGH");
severityMap.put("Cross-Site Scripting in HTML tag.", "HIGH");
severityMap.put("Cross-Site Scripting in HTML "script" tag.", "HIGH");
severityMap.put("Cross-Site Scripting (XSS) in URI", "HIGH");
severityMap.put("The TRACE HTTP method is enabled.", "MEDIUM");
severityMap.put("Found a CAPTCHA protected form.", "INFORMATIONAL");
severityMap.put("Credit card number disclosure.", "MEDIUM");
severityMap.put("CVS/SVN user disclosure.", "LOW");
severityMap.put("Disclosed e-mail address.", "INFORMATIONAL");
severityMap.put("Found an HTML object.", "INFORMATIONAL");
severityMap.put("Private IP address disclosure.", "LOW");
severityMap.put("Disclosed US Social Security Number.", "HIGH");
}
@Autowired
public ArachniChannelImporter(ChannelTypeDao channelTypeDao,
ChannelVulnerabilityDao channelVulnerabilityDao,
ChannelSeverityDao channelSeverityDao) {
this.channelTypeDao = channelTypeDao;
this.channelVulnerabilityDao = channelVulnerabilityDao;
this.channelSeverityDao = channelSeverityDao;
this.channelType = channelTypeDao.retrieveByName(ChannelType.ARACHNI);
}
@Override
public Scan parseInput() {
return parseSAXInput(new ArachniSAXParser());
}
public class ArachniSAXParser extends HandlerWithBuilder {
private boolean getDate = false;
private boolean inFinding = false;
private FindingKey itemKey = null;
private Map<FindingKey, String> findingMap = null;
public void add(Finding finding) {
if (finding != null) {
finding.setNativeId(getNativeId(finding));
finding.setIsStatic(false);
saxFindingList.add(finding);
}
}
////////////////////////////////////////////////////////////////////
// Event handlers.
////////////////////////////////////////////////////////////////////
public void startElement (String uri, String name,
String qName, Attributes atts)
{
if ("finish_datetime".equals(qName)) {
getDate = true;
} else if ("issue".equals(qName)) {
findingMap = new EnumMap<>(FindingKey.class);
inFinding = true;
} else if (inFinding && tagMap.containsKey(qName)) {
itemKey = tagMap.get(qName);
}
}
public void endElement (String uri, String name, String qName)
{
if ("issue".equals(qName)) {
// TODO instead look into why this error occurs
if (findingMap.get(FindingKey.VULN_CODE) != null &&
findingMap.get(FindingKey.VULN_CODE).equals("Cross-Site Scripting in HTML ")) {
findingMap.put(FindingKey.VULN_CODE,
"Cross-Site Scripting in HTML "script" tag.");
}
findingMap.put(FindingKey.SEVERITY_CODE, severityMap.get(findingMap.get(FindingKey.VULN_CODE)));
Finding finding = constructFinding(findingMap);
add(finding);
findingMap = null;
inFinding = false;
} else if (inFinding && itemKey != null) {
String currentItem = getBuilderText();
if (currentItem != null && findingMap.get(itemKey) == null) {
findingMap.put(itemKey, currentItem);
}
itemKey = null;
}
if (getDate) {
String tempDateString = getBuilderText();
if (tempDateString != null && !tempDateString.trim().isEmpty()) {
date = getCalendarFromString("EEE MMM dd kk:mm:ss yyyy", tempDateString);
}
getDate = false;
}
}
public void characters (char ch[], int start, int length) {
if (getDate || itemKey != null) {
addTextToBuilder(ch, start, length);
}
}
}
@Override
public ScanCheckResultBean checkFile() {
return testSAXInput(new ArachniSAXValidator());
}
public class ArachniSAXValidator extends HandlerWithBuilder {
private boolean hasFindings = false;
private boolean hasDate = false;
private boolean correctFormat = false;
private boolean getDate = false;
private void setTestStatus() {
if (!correctFormat)
testStatus = ScanImportStatus.WRONG_FORMAT_ERROR;
else if (hasDate)
testStatus = checkTestDate();
if ((testStatus == null || ScanImportStatus.SUCCESSFUL_SCAN == testStatus) && !hasFindings)
testStatus = ScanImportStatus.EMPTY_SCAN_ERROR;
else if (testStatus == null)
testStatus = ScanImportStatus.SUCCESSFUL_SCAN;
}
////////////////////////////////////////////////////////////////////
// Event handlers.
////////////////////////////////////////////////////////////////////
public void endDocument() {
setTestStatus();
}
public void startElement (String uri, String name, String qName, Attributes atts) throws SAXException {
if ("arachni_report".equals(qName)) {
correctFormat = true;
}
if ("finish_datetime".equals(qName)) {
getDate = true;
}
if ("issue".equals(qName)) {
hasFindings = true;
setTestStatus();
throw new SAXException(FILE_CHECK_COMPLETED);
}
}
public void endElement(String uri, String name, String qName) {
if (getDate) {
String tempDateString = getBuilderText();
if (tempDateString != null && !tempDateString.trim().isEmpty()) {
testDate = getCalendarFromString("EEE MMM dd kk:mm:ss yyyy", tempDateString);
}
hasDate = testDate != null;
getDate = false;
}
}
public void characters (char ch[], int start, int length) {
if (getDate) {
addTextToBuilder(ch, start, length);
}
}
}
}