////////////////////////////////////////////////////////////////////////
//
// 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.remoteprovider;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
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.RemoteProviderApplication;
import com.denimgroup.threadfix.data.entities.RemoteProviderType;
import com.denimgroup.threadfix.data.entities.Scan;
import com.denimgroup.threadfix.service.channel.HandlerWithBuilder;
/**
* TODO use POST data to pre-filter web requests
* @author mcollins
*
*/
public class QualysRemoteProvider extends RemoteProvider {
private String username = null;
private String password = null;
private static final Map<String, String> SEVERITIES_MAP = new HashMap<String, String>();
static {
SEVERITIES_MAP.put("150000", "5");
SEVERITIES_MAP.put("150001", "5");
SEVERITIES_MAP.put("150002", "5");
SEVERITIES_MAP.put("150003", "5");
SEVERITIES_MAP.put("150004", "2");
SEVERITIES_MAP.put("150005", "2");
SEVERITIES_MAP.put("150006", "1");
SEVERITIES_MAP.put("150007", "1");
SEVERITIES_MAP.put("150008", "1");
SEVERITIES_MAP.put("150009", "1");
SEVERITIES_MAP.put("150010", "1");
SEVERITIES_MAP.put("150011", "3");
SEVERITIES_MAP.put("150012", "5");
SEVERITIES_MAP.put("150013", "5");
SEVERITIES_MAP.put("150014", "1");
SEVERITIES_MAP.put("150015", "1");
SEVERITIES_MAP.put("150016", "2");
SEVERITIES_MAP.put("150017", "1");
SEVERITIES_MAP.put("150018", "2");
SEVERITIES_MAP.put("150019", "2");
SEVERITIES_MAP.put("150020", "1");
SEVERITIES_MAP.put("150021", "1");
SEVERITIES_MAP.put("150022", "3");
SEVERITIES_MAP.put("150023", "2");
SEVERITIES_MAP.put("150024", "1");
SEVERITIES_MAP.put("150025", "1");
SEVERITIES_MAP.put("150026", "1");
SEVERITIES_MAP.put("150028", "1");
SEVERITIES_MAP.put("150029", "1");
SEVERITIES_MAP.put("150030", "1");
SEVERITIES_MAP.put("150032", "3");
SEVERITIES_MAP.put("150033", "3");
SEVERITIES_MAP.put("150034", "3");
SEVERITIES_MAP.put("150035", "1");
SEVERITIES_MAP.put("150036", "1");
SEVERITIES_MAP.put("150037", "1");
SEVERITIES_MAP.put("150038", "1");
SEVERITIES_MAP.put("150039", "1");
SEVERITIES_MAP.put("150040", "1");
SEVERITIES_MAP.put("150041", "1");
SEVERITIES_MAP.put("150042", "3");
SEVERITIES_MAP.put("150043", "1");
SEVERITIES_MAP.put("150044", "3");
SEVERITIES_MAP.put("150045", "3");
SEVERITIES_MAP.put("150046", "5");
SEVERITIES_MAP.put("150047", "5");
SEVERITIES_MAP.put("150048", "5");
SEVERITIES_MAP.put("150049", "4");
SEVERITIES_MAP.put("150051", "3");
SEVERITIES_MAP.put("150054", "1");
SEVERITIES_MAP.put("150055", "5");
SEVERITIES_MAP.put("150057", "5");
SEVERITIES_MAP.put("150058", "1");
SEVERITIES_MAP.put("150060", "5");
SEVERITIES_MAP.put("150061", "1");
SEVERITIES_MAP.put("150062", "5");
SEVERITIES_MAP.put("150063", "5");
SEVERITIES_MAP.put("150064", "1");
SEVERITIES_MAP.put("150065", "2");
SEVERITIES_MAP.put("150066", "1");
SEVERITIES_MAP.put("150067", "3");
SEVERITIES_MAP.put("150071", "3");
SEVERITIES_MAP.put("150076", "4");
SEVERITIES_MAP.put("150077", "1");
SEVERITIES_MAP.put("150078", "1");
SEVERITIES_MAP.put("150079", "3");
SEVERITIES_MAP.put("150081", "1");
SEVERITIES_MAP.put("150082", "1");
SEVERITIES_MAP.put("150083", "1");
SEVERITIES_MAP.put("150084", "1");
SEVERITIES_MAP.put("150085", "3");
SEVERITIES_MAP.put("150086", "3");
SEVERITIES_MAP.put("150087", "1");
SEVERITIES_MAP.put("150088", "1");
SEVERITIES_MAP.put("150089", "3");
SEVERITIES_MAP.put("150090", "5");
SEVERITIES_MAP.put("150092", "5");
SEVERITIES_MAP.put("150093", "5");
SEVERITIES_MAP.put("150094", "1");
SEVERITIES_MAP.put("150095", "1");
}
@Autowired
public QualysRemoteProvider(ChannelTypeDao channelTypeDao,
ChannelVulnerabilityDao channelVulnerabilityDao,
ChannelSeverityDao channelSeverityDao) {
this.channelVulnerabilityDao = channelVulnerabilityDao;
this.channelTypeDao = channelTypeDao;
this.channelSeverityDao = channelSeverityDao;
setChannelType(ChannelType.QUALYSGUARD_WAS);
}
@Override
public List<Scan> getScans(RemoteProviderApplication remoteProviderApplication) {
if (remoteProviderApplication == null ||
remoteProviderApplication.getRemoteProviderType() == null) {
log.error("Null input to Qualys getScan(), returning null.");
return null;
}
password = remoteProviderApplication.getRemoteProviderType().getPassword();
username = remoteProviderApplication.getRemoteProviderType().getUsername();
List<String> scanIds = mostRecentScanForApp(remoteProviderApplication);
if (scanIds == null || scanIds.size() == 0) {
log.warn("No valid scans were found.");
return null;
}
List<Scan> scanList = new ArrayList<Scan>();
for (String scanId : scanIds) {
inputStream = httpGet(getScanUrl(remoteProviderApplication.getRemoteProviderType()) + scanId);
if (inputStream == null) {
log.warn("Got a bad response from Qualys servers for scan ID " + scanId + ". Trying the next scan.");
continue;
}
QualysWASSAXParser scanParser = new QualysWASSAXParser();
Scan resultScan = parseSAXInput(scanParser);
if (resultScan != null) {
log.info("The Qualys scan import for scan ID " + scanId + " was successful.");
resultScan.setApplicationChannel(remoteProviderApplication.getApplicationChannel());
scanList.add(resultScan);
} else {
log.warn("The Qualys scan import for scan ID " + scanId + " failed!!");
}
}
return scanList;
}
@Override
public List<RemoteProviderApplication> fetchApplications() {
if (remoteProviderType == null || remoteProviderType.getUsername() == null ||
remoteProviderType.getPassword() == null) {
log.error("Insufficient credentials given to Qualys fetchApplications().");
return null;
}
log.info("Fetching Qualys applications.");
password = remoteProviderType.getPassword();
username = remoteProviderType.getUsername();
InputStream stream = null;
// POST with no parameters
// TODO include filters
stream = httpPost(getAppsUrl(remoteProviderType),new String[]{},new String[]{});
if (stream == null) {
log.warn("Response from Qualys servers was null, check your credentials.");
return null;
}
QualysAppsParser parser = new QualysAppsParser();
parse(stream, parser);
if (parser.list != null && parser.list.size() > 0) {
log.info("Number of Qualys applications: " + parser.list.size());
} else {
log.warn("No Qualys applications were found. Check your configuration.");
}
return parser.list;
}
public List<String> mostRecentScanForApp(RemoteProviderApplication app) {
if (app == null || app.getNativeId() == null) {
return null;
}
// POST with no parameters
// TODO include filters
InputStream stream = httpPost(getScansForAppUrl(app.getRemoteProviderType()),new String[]{},new String[]{});
if (stream == null) {
return null;
}
QualysScansForAppParser parser = new QualysScansForAppParser();
parse(stream, parser);
List<String> scanIds = new ArrayList<String>();
// This should be replaced with the filtered code
for (Map<String, String> map : parser.list) {
Calendar mapDate = null;
if (app.getNativeId().equals(map.get("webAppName")) && map.get("date") != null) {
mapDate = getCalendarFromString("yyyy-MM-DD'T'HH:mm:ss'Z'", map.get("date"));
if (app.getLastImportTime() == null || mapDate.after(app.getLastImportTime())) {
scanIds.add(map.get("id"));
}
}
}
log.info("Returning scan IDs " + scanIds + " for application " + app.getNativeId());
return scanIds;
}
public QualysRemoteProvider() {
}
private String getScansForAppUrl(RemoteProviderType type) {
return "https://qualysapi.qualys." +
getLocation(type.getIsEuropean()) +
"/qps/rest/3.0/search/was/wasscan";
}
private String getScanUrl(RemoteProviderType type) {
return "https://qualysapi.qualys." +
getLocation(type.getIsEuropean()) +
"/qps/rest/3.0/download/was/wasscan/";
}
private String getAppsUrl(RemoteProviderType type) {
return "https://qualysapi.qualys." +
getLocation(type.getIsEuropean()) +
"/qps/rest/3.0/search/was/webapp";
}
private String getLocation(boolean isEuropean) {
return isEuropean ? "eu" : "com";
}
// UTILITIES
private InputStream httpPost(String request, String[] paramNames,
String[] paramVals) {
PostMethod post = new PostMethod(request);
post.setRequestHeader("Content-type", "text/xml; charset=UTF-8");
if (username != null && password != null) {
String login = username + ":" + password;
String encodedLogin = new String(Base64.encodeBase64(login.getBytes()));
post.setRequestHeader("Authorization", "Basic " + encodedLogin);
}
try {
for (int i = 0; i < paramNames.length; i++) {
post.addParameter(paramNames[i], paramVals[i]);
}
HttpClient client = new HttpClient();
int status = client.executeMethod(post);
if (status != 200) {
log.warn("Status was not 200.");
log.warn("Status : " + status);
}
InputStream responseStream = post.getResponseBodyAsStream();
if (responseStream != null) {
return responseStream;
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
log.warn("There was an error and the POST request was not finished.");
return null;
}
private InputStream httpGet(String urlStr) {
GetMethod get = new GetMethod(urlStr);
get.setRequestHeader("Content-type", "text/xml; charset=UTF-8");
if (username != null && password != null) {
String login = username + ":" + password;
String encodedLogin = new String(Base64.encodeBase64(login.getBytes()));
get.setRequestHeader("Authorization", "Basic " + encodedLogin);
}
HttpClient client = new HttpClient();
try {
int status = client.executeMethod(get);
if (status != 200) {
log.warn("Status was not 200.");
log.warn("Status : " + status);
}
InputStream responseStream = get.getResponseBodyAsStream();
if (responseStream != null) {
return responseStream;
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
// PARSER CLASSES
private class QualysAppsParser extends HandlerWithBuilder {
public List<RemoteProviderApplication> list = new ArrayList<RemoteProviderApplication>();
private boolean getName = false;
public void startElement (String uri, String name, String qName, Attributes atts) throws SAXException {
if (qName.equals("name")) {
getName = true;
}
}
public void endElement(String uri, String name, String qName) {
if (getName) {
String tempNameString = getBuilderText();
RemoteProviderApplication remoteProviderApplication = new RemoteProviderApplication();
remoteProviderApplication.setNativeId(tempNameString);
remoteProviderApplication.setRemoteProviderType(remoteProviderType);
list.add(remoteProviderApplication);
getName = false;
}
}
public void characters (char ch[], int start, int length) {
if (getName) {
addTextToBuilder(ch, start, length);
}
}
}
private class QualysScansForAppParser extends HandlerWithBuilder {
public List<Map<String,String>> list = new ArrayList<Map<String,String>>();
private boolean inWebApp = false;
private boolean getName = false;
private String webAppName = "";
private boolean getId = false;
private String currentId = null;
private boolean getStatus = false;
private String currentStatus = null;
private boolean getDate = false;
private String currentDate = null;
public void startElement (String uri, String name, String qName, Attributes atts) throws SAXException {
if (qName.equals("webApp")) {
inWebApp = true;
} else if (currentId == null && qName.equals("id")) {
getId = true;
} else if (inWebApp && qName.equals("name")) {
getName = true;
inWebApp = false;
} else if (qName.equals("status")) {
getStatus = true;
} else if (qName.equals("launchedDate")) {
getDate = true;
}
}
@Override
public void endElement (String uri, String localName, String qName) throws SAXException {
if (getId) {
currentId = getBuilderText();
getId = false;
} else if (getStatus) {
currentStatus = getBuilderText();
getStatus = false;
} else if (getDate) {
currentDate = getBuilderText();
getDate = false;
} else if (getName) {
String toAdd = getBuilderText();
webAppName = toAdd;
getName = false;
}
if (qName.equals("WasScan")) {
Map<String, String> map = new HashMap<String, String>();
map.put("id", currentId);
map.put("status", currentStatus);
map.put("date", currentDate);
map.put("webAppName", webAppName);
list.add(map);
currentStatus = null;
currentId = null;
currentDate = null;
webAppName = null;
}
}
public void characters (char ch[], int start, int length) {
if (getId || getStatus || getDate || getName) {
addTextToBuilder(ch,start,length);
}
}
}
private class QualysWASSAXParser extends HandlerWithBuilder {
private Boolean getDate = false;
private Boolean getUri = false;
private Boolean getParameter = false;
private Boolean getChannelVulnName = false;
private String currentChannelVulnCode = null;
private String currentPath = null;
private String currentParameter = null;
private String currentSeverityCode = 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 ("launchedDate".equals(qName)) {
getDate = true;
} else if ("uri".equals(qName)) {
getUri = true;
} else if ("qid".equals(qName)) {
getChannelVulnName = true;
} else if ("param".equals(qName)) {
getParameter = true;
} else if ("instances".equals(qName)) {
currentSeverityCode = SEVERITIES_MAP.get(currentChannelVulnCode);
Finding finding = constructFinding(currentPath, currentParameter,
currentChannelVulnCode, currentSeverityCode);
add(finding);
currentParameter = null;
currentPath = null;
getParameter = false;
}
}
public void endElement(String uri, String name, String qName) {
if (getDate) {
String tempDateString = getBuilderText();
if (tempDateString != null && !tempDateString.trim().isEmpty()) {
date = getCalendarFromString("yyyy-MM-DD'T'HH:mm:ss'Z'", tempDateString);
}
getDate = false;
} else if (getUri) {
currentPath = getBuilderText();
getUri = false;
} else if (getChannelVulnName) {
currentChannelVulnCode = getBuilderText();
getChannelVulnName = false;
} else if (getParameter) {
currentParameter = getBuilderText();
getParameter = false;
}
}
public void characters (char ch[], int start, int length) {
if (getDate || getUri || getChannelVulnName || getParameter) {
addTextToBuilder(ch, start, length);
}
}
}
}