////////////////////////////////////////////////////////////////////////
//
// 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.ArrayList;
import java.util.HashMap;
import java.util.List;
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.dao.GenericVulnerabilityDao;
import com.denimgroup.threadfix.data.entities.ChannelSeverity;
import com.denimgroup.threadfix.data.entities.ChannelType;
import com.denimgroup.threadfix.data.entities.ChannelVulnerability;
import com.denimgroup.threadfix.data.entities.Finding;
import com.denimgroup.threadfix.data.entities.GenericVulnerability;
import com.denimgroup.threadfix.data.entities.Scan;
import com.denimgroup.threadfix.data.entities.SurfaceLocation;
import com.denimgroup.threadfix.data.entities.VulnerabilityMap;
import com.denimgroup.threadfix.webapp.controller.ScanCheckResultBean;
/**
* Imports the results of a dynamic AppScan scan.
*
* @author mcollins
*/
public class AppScanWebImporter extends AbstractChannelImporter {
/**
* Constructor with Spring dependencies injected.
*
* @param channelTypeDao
* @param channelVulnerabilityDao
* @param channelSeverityDao
* @param genericVulnerabilityDao
* @param vulnerabilityMapLogDao
*/
@Autowired
public AppScanWebImporter(ChannelTypeDao channelTypeDao,
ChannelVulnerabilityDao channelVulnerabilityDao, ChannelSeverityDao channelSeverityDao,
GenericVulnerabilityDao genericVulnerabilityDao) {
this.channelVulnerabilityDao = channelVulnerabilityDao;
this.channelTypeDao = channelTypeDao;
this.channelSeverityDao = channelSeverityDao;
this.genericVulnerabilityDao = genericVulnerabilityDao;
setChannelType(ChannelType.APPSCAN_DYNAMIC);
}
/*
* (non-Javadoc)
*
* @see
* com.denimgroup.threadfix.service.channel.ChannelImporter#parseInput()
*/
@Override
public Scan parseInput() {
return parseSAXInput(new AppScanSAXParser());
}
public class AppScanSAXParser extends HandlerWithBuilder {
private ChannelVulnerability currentChannelVuln = null;
private ChannelSeverity currentChannelSeverity = null;
private String currentUrl = null;
private String currentParam = null;
private String currentIssueTypeId = null;
private String requestText = null;
private final Map<String, ChannelSeverity> severityMap;
private final Map<String, String> genericVulnMap;
private final Map<String, String> channelVulnNameMap;
private boolean grabUrlText = false;
private boolean grabSeverity = false;
private boolean grabCWE = false;
private boolean grabIssueTypeName = false;
private boolean issueTypes = true;
private boolean grabDate = false;
public AppScanSAXParser () {
super();
hosts = new ArrayList<String>();
severityMap = new HashMap<String, ChannelSeverity>();
genericVulnMap = new HashMap<String, String>();
channelVulnNameMap = new HashMap<String, String>();
}
private void addChannelVulnsAndMappingsToDatabase() {
for (String key : genericVulnMap.keySet()) {
ChannelVulnerability channelVuln = getChannelVulnerability(key);
if (channelVuln == null) {
channelVuln = new ChannelVulnerability();
channelVuln.setCode(key);
channelVuln.setName(channelVulnNameMap.get(key));
channelVuln.setChannelType(channelType);
} else if (channelVuln.getVulnerabilityMaps() != null
&& channelVuln.getVulnerabilityMaps().size() != 0) {
return;
}
GenericVulnerability genericVuln = null;
if (genericVulnMap.get(key).matches("[0-9]+"))
genericVuln = genericVulnerabilityDao.retrieveById(Integer.valueOf(genericVulnMap.get(key)));
if (genericVuln != null)
createVulnRelationship(channelVuln, genericVuln);
channelVulnerabilityDao.saveOrUpdate(channelVuln);
channelVulnerabilityMap.put(key, channelVuln);
}
}
/**
* Tie a channel vuln and generic vuln together with a vulnerability map.
*
* @param channelVuln
* @param genericVuln
*/
private void createVulnRelationship(ChannelVulnerability channelVuln,
GenericVulnerability genericVuln) {
if (channelVuln == null || genericVuln == null)
return;
VulnerabilityMap vm = new VulnerabilityMap();
vm.setChannelVulnerability(channelVuln);
vm.setGenericVulnerability(genericVuln);
List<VulnerabilityMap> vulnerabilityMapList = new ArrayList<VulnerabilityMap>();
vulnerabilityMapList.add(vm);
channelVuln.setVulnerabilityMaps(vulnerabilityMapList);
genericVuln.setVulnerabilityMaps(vulnerabilityMapList);
}
////////////////////////////////////////////////////////////////////
// Event handlers.
////////////////////////////////////////////////////////////////////
public void startElement (String uri, String name, String qName, Attributes atts) {
if ("Host".equals(qName))
hosts.add(atts.getValue(0));
if (issueTypes) {
// ISSUETYPE
if ("IssueType".equals(qName))
currentIssueTypeId = atts.getValue(0);
else if ("Severity".equals(qName))
grabSeverity = true;
else if ("link".equals(qName))
grabCWE = true;
else if ("Issues".equals(qName))
issueTypes = false;
else if ("name".equals(qName))
grabIssueTypeName = true;
} else {
if ("Issue".equals(qName)) {
currentChannelVuln = getChannelVulnerability(atts.getValue(0));
currentChannelSeverity = severityMap.get(atts.getValue(0));
}
else if ("Entity".equals(qName) && atts.getValue(1) != null && atts.getValue(1).trim().equals("Parameter"))
currentParam = atts.getValue(0);
else if ("Url".equals(qName))
grabUrlText = true;
else if (date == null && "OriginalHttpTraffic".equals(qName)) {
requestText = "";
grabDate = true;
}
}
}
public void endElement (String uri, String name, String qName) throws SAXException {
if (grabUrlText) {
currentUrl = getBuilderText();
grabUrlText = false;
} else if (grabSeverity) {
String severityString = getBuilderText();
ChannelSeverity severity = getChannelSeverity(severityString);
if (currentIssueTypeId != null && severity != null)
severityMap.put(currentIssueTypeId, severity);
grabSeverity = false;
} else if (grabCWE) {
String maybeId = getBuilderText();
if (maybeId.startsWith("CWE-") && maybeId.contains(":")) {
maybeId = maybeId.substring(4, maybeId.indexOf(':'));
genericVulnMap.put(currentIssueTypeId, maybeId);
}
grabCWE = false;
} else if (grabIssueTypeName) {
String charString = getBuilderText();
channelVulnNameMap.put(currentIssueTypeId, charString);
grabIssueTypeName = false;
} else if (grabDate) {
requestText = requestText.concat(getBuilderText());
}
if ("Issues".equals(qName)) {
throw new SAXException("Done Parsing.");
} else if ("IssueTypes".equals(qName)) {
addChannelVulnsAndMappingsToDatabase();
} else if ("Issue".equals(qName)) {
Finding finding = new Finding();
SurfaceLocation location = new SurfaceLocation();
for (String host : hosts)
if (currentUrl.startsWith(host)) {
location.setHost(host);
location.setPath("/" + currentUrl.substring(host.length()));
}
if (location.getPath() == null)
location.setPath(currentUrl);
location.setParameter(currentParam);
finding.setSurfaceLocation(location);
finding.setChannelVulnerability(currentChannelVuln);
finding.setChannelSeverity(currentChannelSeverity);
finding.setNativeId(getNativeId(finding));
finding.setIsStatic(false);
saxFindingList.add(finding);
currentChannelVuln = null;
currentUrl = null;
currentParam = null;
} else if (date == null && "OriginalHttpTraffic".equals(qName)) {
date = attemptToParseDateFromHTTPResponse(requestText);
grabDate = false;
}
}
public void characters (char ch[], int start, int length) {
if (grabUrlText || grabSeverity || grabCWE || grabIssueTypeName || grabDate) {
addTextToBuilder(ch, start, length);
}
}
}
@Override
public ScanCheckResultBean checkFile() {
return testSAXInput(new AppScanSAXValidator());
}
public class AppScanSAXValidator extends HandlerWithBuilder {
private boolean hasFindings = false, hasDate = false;
private boolean xmlReport = false, appscanInfo = false,
summary = false, results = false;
private String requestText;
private boolean grabDate;
private void setTestStatus() {
boolean fileFormat = (xmlReport && appscanInfo && summary && results);
if (!fileFormat) {
testStatus = ScanImportStatus.WRONG_FORMAT_ERROR;
return;
} else if (hasDate)
testStatus = checkTestDate();
if ((!hasDate || 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 ("Issue".equals(qName))
hasFindings = true;
if (!hasDate && "OriginalHttpTraffic".equals(qName)) {
requestText = "";
grabDate = true;
}
if (!xmlReport && "XmlReport".equals(qName))
xmlReport = true;
if (!appscanInfo && "AppScanInfo".equals(qName))
appscanInfo = true;
if (!summary && "Summary".equals(qName))
summary = true;
if (!results && "Results".equals(qName))
results = true;
if ("ApplicationData".equals(qName)) {
setTestStatus();
throw new SAXException(FILE_CHECK_COMPLETED);
}
}
public void endElement (String uri, String name, String qName){
if (grabDate) {
requestText = getBuilderText();
grabDate = false;
}
if (!hasDate && "OriginalHttpTraffic".equals(qName)) {
testDate = attemptToParseDateFromHTTPResponse(requestText);
grabDate = false;
if (testDate != null)
hasDate = true;
}
}
public void characters (char ch[], int start, int length) {
if (grabDate) {
addTextToBuilder(ch, start, length);
}
}
}
}