/*
* Scan.java
*
* Created on August 19, 2007, 1:25 PM
*
* To change this template, choose Tools | Template Manager and open the template in the editor.
*/
package com.grendelscan.scan;
import java.io.File;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.grendelscan.categorizers.Categorizer;
import com.grendelscan.categorizers.Categorizers;
import com.grendelscan.commons.FileUtils;
import com.grendelscan.commons.WordList;
import com.grendelscan.commons.collections.CollectionUtils;
import com.grendelscan.commons.flex.output.AmfOutputStreamRegistry;
import com.grendelscan.commons.formatting.DataFormatUtils;
import com.grendelscan.commons.http.RequestOptions;
import com.grendelscan.commons.http.URIStringUtils;
import com.grendelscan.commons.http.apache_overrides.client.ClientUtilities;
import com.grendelscan.commons.http.apache_overrides.client.CustomHttpClient;
import com.grendelscan.commons.http.transactions.HttpTransactionFields;
import com.grendelscan.commons.http.transactions.StandardHttpTransaction;
import com.grendelscan.commons.http.transactions.TransactionSource;
import com.grendelscan.data.database.DatabaseUser;
import com.grendelscan.data.database.collections.DatabaseBackedCollection;
import com.grendelscan.fuzzing.Fuzzer;
import com.grendelscan.proxy.Proxies;
import com.grendelscan.queues.AbstractQueueThread;
import com.grendelscan.queues.categorizer.CategorizerQueue;
import com.grendelscan.queues.monitor.QueueMonitor;
import com.grendelscan.queues.requester.RequesterQueue;
import com.grendelscan.queues.tester.TesterQueue;
import com.grendelscan.queues.tester.TesterThread;
import com.grendelscan.scan.authentication.AuthenticationPackages;
import com.grendelscan.scan.data.PersistedTestData;
import com.grendelscan.scan.data.ResponseCodeOverrides;
import com.grendelscan.scan.data.TransactionRecord;
import com.grendelscan.scan.data.findings.FindingsCollector;
import com.grendelscan.scan.data.sessionState.SessionStates;
import com.grendelscan.scan.settings.ScanSettings;
import com.grendelscan.testing.misc.ModuleDependencyException;
import com.grendelscan.testing.modules.AbstractTestModule;
import com.grendelscan.testing.modules.MasterTestModuleCollection;
import com.grendelscan.testing.utils.nikto.Nikto;
import com.grendelscan.testing.utils.platformErrorMessages.PlatformErrorMessages;
import com.grendelscan.testing.utils.sessionIDs.SessionID;
import com.grendelscan.testing.utils.spidering.SpiderConfig;
import com.grendelscan.testing.utils.spidering.SpiderUtils;
import com.grendelscan.testing.utils.tokens.TokenTesting;
/**
*
* @author David Byrne
*/
public class Scan {
private static final Logger LOGGER = LoggerFactory.getLogger(Scan.class);
private static Scan scan;
public static Scan getInstance() {
return scan;
}
public static ScanSettings getScanSettings() {
return getInstance().scanSettings;
}
public static void instantiate(final boolean useGUI,
final String newOutputDirectory) {
scan = new Scan(useGUI, newOutputDirectory);
scan.init();
}
private CategorizerQueue categorizerQueue;
private Categorizers categorizers;
private CustomHttpClient httpClient;
private boolean paused;
private Proxies proxies;
private FindingsCollector findings;
private RequesterQueue requesterQueue;
private ResponseCodeOverrides responseCodeOverrides;
private boolean shutdownComplete;
private ScanSettings scanSettings;
private TesterQueue testerQueue;
private TransactionRecord transactionRecord;
private List<Fuzzer> fuzzers;
public static final String settingsFile = "settings.xml";
private final String outputDirectory;
private final boolean useGUI;
private List<DatabaseUser> databaseUsers;
private PersistedTestData testData;
private AuthenticationPackages authenticationPackages;
private final Set<AbstractTestModule> enabledModules;
private long domTime;
private WordList wordList;
private QueueMonitor queueMonitor;
private Scan(final boolean useGUI, final String newOutputDirectory) {
paused = true;
this.useGUI = useGUI;
if (!newOutputDirectory.matches(File.separator + "$")) {
outputDirectory = newOutputDirectory + File.separator;
} else {
outputDirectory = newOutputDirectory;
}
enabledModules = new HashSet<AbstractTestModule>();
}
/**
* Does not add the URI to ScanSettings or to whitelist
*
* @param uri
* @throws URISyntaxException
*/
public void addBaseURIToScan(String uri) {
uri = URIStringUtils.fixBaseUri(uri);
String name = "baseuri-" + uri;
if (!testData.containsItem(name)) {
RequestOptions baseUriRequestOptions = new RequestOptions();
baseUriRequestOptions.reason = "Base URL processing";
baseUriRequestOptions.followRedirects = true;
StandardHttpTransaction transaction = new StandardHttpTransaction(
TransactionSource.BASE, -1);
transaction.getRequestWrapper().setURI(uri, true);
transaction.getRequestWrapper().setMethod("GET");
transaction.setRequestOptions(baseUriRequestOptions);
requesterQueue.addTransaction(transaction);
testData.setBoolean(name, true);
}
}
private void createHttpClients() {
HttpParams params = ClientUtilities.createHttpParams();
HttpContext context = ClientUtilities.createHttpContext();
ClientConnectionManager ccm = ClientUtilities
.createClientConnectionManager(
scanSettings.getMaxTotalConnections(),
scanSettings.getMaxConnectionsPerServer());
httpClient = new CustomHttpClient(ccm, params, context);
}
private void datastoreInit() {
databaseUsers = new ArrayList<DatabaseUser>(1);
try {
transactionRecord = new TransactionRecord();
databaseUsers.add(transactionRecord);
testData = new PersistedTestData();
databaseUsers.add(testData);
findings = new FindingsCollector();
databaseUsers.add(findings);
// don't add SpiderUtils here
} catch (Exception e) {
String message = "Error loading/creating transasction database: "
+ e.toString();
fatalError(message);
}
}
public boolean disableTestModule(
final Class<? extends AbstractTestModule> moduleClass)
throws ModuleDependencyException {
AbstractTestModule module;
if ((module = MasterTestModuleCollection.getInstance().getTestModule(
moduleClass)) == null) {
return false;
}
String message = "";
for (Class<? extends AbstractTestModule> i : module.getDependents()) {
AbstractTestModule dependency = MasterTestModuleCollection
.getInstance().getTestModule(i);
if (enabledModules.contains(dependency)) {
message += "Module \"" + dependency.getName()
+ "\" is enabled and requires \"" + module.getName()
+ "\". Disable \"" + dependency.getName() + "\" first";
}
}
if (!message.isEmpty()) {
throw new ModuleDependencyException(message);
}
testerQueue.disableModule(moduleClass);
return enabledModules.remove(module);
}
public void displayMessage(final String title, final String message) {
if (MainWindow.getInstance() != null) {
MainWindow.getInstance().displayMessage(title, message, false);
} else {
System.out.println(title + "\n" + message);
}
}
private void enablePrerequsites(final AbstractTestModule module) {
for (Class<? extends AbstractTestModule> prereq : module
.getPrerequisites()) {
enablePrerequsites(MasterTestModuleCollection.getInstance()
.getTestModule(prereq));
enableTestModule(prereq);
}
}
public boolean enableTestModule(
final Class<? extends AbstractTestModule> moduleClass) {
AbstractTestModule module;
// Check to see if there is such a module
if ((module = MasterTestModuleCollection.getInstance().getTestModule(
moduleClass)) == null) {
return false;
}
enablePrerequsites(module);
testerQueue.enableModule(moduleClass);
return enabledModules.add(module);
}
public synchronized void fatalError(final String message) {
try {
LOGGER.error("FATAL ERROR: " + message);
} catch (Exception e) {
// Don't care at this point
}
try {
displayMessage("Error:", message);
} catch (Exception e) {
// Don't care at this point
}
System.exit(1);
}
public String generateStatus() {
String s = "";
s += "Enabled modules:\n";
for (AbstractTestModule module : enabledModules) {
s += "\t" + module.getName() + " (" + module.getClass().getName()
+ ")\n";
}
s += "\n\n";
s += "DOM parsing time: " + domTime + "\n\n";
s += "Processing times:\n";
for (Class<? extends AbstractTestModule> moduleId : testerQueue
.getTimes().keySet()) {
s += "\t"
+ MasterTestModuleCollection.getInstance()
.getTestModule(moduleId).getName() + " ("
+ moduleId + "): "
+ roundMilis(testerQueue.getTimes().get(moduleId)) + "\n";
}
s += "Database times:\n";
s += "\tLoading: " + roundMilis(transactionRecord.getLoadingTime())
+ "\n";
s += "\tSaving: " + roundMilis(transactionRecord.getSavingTime())
+ "\n";
s += "\n";
s += "Status text update: "
+ roundMilis(MainWindow.getInstance().getLogComposite()
.getStatusMessageUpdateTime()) + "\n";
s += "\n";
s += "ResponseCodeOverrides: \n";
s += "\tExecute: "
+ roundMilis(responseCodeOverrides.getTotalExecuteTime())
+ "\n";
s += "\tCompare: "
+ roundMilis(responseCodeOverrides.getTotalCompareTime())
+ "\n";
s += "\tTotal: " + roundMilis(responseCodeOverrides.getTotalGenTime())
+ "\n";
s += "\nTester queue stats:\n";
for (AbstractTestModule module : MasterTestModuleCollection
.getInstance().getAllTestModules()) {
s += "\t\t" + module.getName() + " (" + module.getClass().getName()
+ "): " + testerQueue.getPendingCount(module.getClass())
+ "\n";
}
s += "\tCompleted tests:\n";
for (Class<? extends AbstractTestModule> module : testerQueue
.getProcessedCount().keySet()) {
s += "\t\t"
+ MasterTestModuleCollection.getInstance()
.getTestModule(module).getName() + " (" + module
+ "): " + testerQueue.getProcessedCount().get(module)
+ "\n";
}
s += "\n\n";
s += scanSettings.getMaxTesterThreads()
+ " tester threads originally created\n";
for (AbstractQueueThread thread : testerQueue.getThreads()) {
s += "\t" + thread.getName() + ": "
+ thread.getThreadState().getText() + " -- "
+ ((TesterThread) thread).getCurrentModule() + "\n";
}
s += "\n\n";
s += scanSettings.getMaxCategorizerThreads()
+ " categorizer threads originally created\n";
for (AbstractQueueThread thread : categorizerQueue.getThreads()) {
s += "\t" + thread.getName() + ": "
+ thread.getThreadState().getText() + "\n";
}
s += "\n\n";
s += scanSettings.getMaxRequesterThreads()
+ " requester threads originally created\n";
for (AbstractQueueThread thread : requesterQueue.getThreads()) {
s += "\t" + thread.getName() + ": "
+ thread.getThreadState().getText() + "\n";
}
s += "\n\n";
s += "Completed HTTP transactions: "
+ HttpTransactionFields.getTotalExecutions() + "\n";
return s;
}
public final AuthenticationPackages getAuthenticationPackages() {
return authenticationPackages;
}
public CategorizerQueue getCategorizerQueue() {
return categorizerQueue;
}
public Categorizers getCategorizers() {
return categorizers;
}
public final Set<AbstractTestModule> getEnabledModules() {
return enabledModules;
}
public FindingsCollector getFindings() {
return findings;
}
public final List<Fuzzer> getFuzzers() {
return fuzzers;
}
public CustomHttpClient getHttpClient() {
return httpClient;
}
public final String getOutputDirectory() {
if (outputDirectory == null) {
throw new IllegalStateException(
"Output directory hasn't been set yet.");
}
return outputDirectory;
}
// /**
// * Returns true if the parameter is forbidden, or irrelevant
// *
// * @param parameterName
// * @return
// */
// public boolean isQueryParameterF(String parameterName)
// {
// return CollectionUtils.containsStringIgnoreCase(
// scanSettings.getReadOnlyIrrelevantQueryParameters(), parameterName)
// || CollectionUtils.containsStringIgnoreCase(
// scanSettings.getReadOnlyForbiddenQueryParameters(),
// parameterName);
// }
// public boolean isRunning()
// {
// return isRunning(false);
// }
//
// public boolean isRunning(boolean proxyOrCategorizerRequest)
// {
// while (running && paused && !proxyOrCategorizerRequest)
// {
// /*
// * This will block, but that's okay. It basically means one wait
// * governs all threads that call isRunning().
// */
// synchronized (this)
// {
// try
// {
// Thread.sleep(250);
// }
// catch (InterruptedException e)
// {
// }
// }
// }
// return running;
// }
public Proxies getProxies() {
return proxies;
}
// public void startProxyOnly()
// {
// universalInit();
//
// // The requester queue manages some aspects of requests, but no threads
// // are needed
// requesterQueue = new RequesterQueue();
//
// scanPhase = ScanPhase.COMPLETE;
// scanSettings.setAllowAllProxyRequests(true);
// scanSettings.setTestProxyRequests(false);
// scanSettings.setTestInterceptedRequests(false);
// scanSettings.setTestManualRequests(false);
// proxies.startProxies();
// scanStatusComposite.updateProxyStatus();
// }
public RequesterQueue getRequesterQueue() {
return requesterQueue;
}
public ResponseCodeOverrides getResponseCodeOverrides() {
return responseCodeOverrides;
}
public final PersistedTestData getTestData() {
return testData;
}
public TesterQueue getTesterQueue() {
return testerQueue;
}
public TransactionRecord getTransactionRecord() {
return transactionRecord;
}
public final WordList getWordList() {
return wordList;
}
// private boolean threadsProcessing(AbstractQueueThread[] threads)
// {
// boolean testing = false;
// for (AbstractQueueThread thread : threads)
// {
// if (thread.getThreadState() == QueueThreadState.PROCESSING)
// {
// testing = true;
// break;
// }
// }
// return testing;
// }
public synchronized void incrementDomTime(final long time) {
domTime += time;
}
private void init() {
FileUtils.createDirectories(outputDirectory);
datastoreInit();
DatabaseBackedCollection.clearExistingNames();
// TODO: Cleanup this and other config file references
wordList = new WordList("global_scan_wordlist", "conf" + File.separator
+ "wordlist");
DataFormatUtils.initialize(wordList);
// init static libraries must come after init database
staticLibraryInit();
MasterTestModuleCollection.initialize();
initializeQueues();
scanSettings = new ScanSettings();
loadOrCreateScanSettingsFile();
FileUtils.createDirectories(outputDirectory + File.separator
+ scanSettings.getSavedTextTransactionsDirectory());
proxies = new Proxies();
fuzzers = new ArrayList<Fuzzer>(1);
authenticationPackages = new AuthenticationPackages();
createHttpClients();
scanSettings.convertBaseUris2WhiteLists();
initializeCategorizers();
// Start with a few threads to improve performance. The count will be
// reduced if it exceeds the max setting
testerQueue.start(5);
requesterQueue.start(5);
categorizerQueue.start(2);
proxies.startProxies();
queueMonitor = new QueueMonitor();
queueMonitor.addQueue(categorizerQueue);
queueMonitor.addQueue(requesterQueue);
queueMonitor.addQueue(testerQueue);
initResponseCodeOverrides();
for (String baseUri : scanSettings.getReadOnlyBaseURIs()) {
addBaseURIToScan(baseUri);
categorizers.getByBaseUriCategorizer().processBaseUri(baseUri);
}
}
private void initializeCategorizers() {
categorizers = new Categorizers();
for (Categorizer categorizer : categorizers.getAllCategorizers()) {
for (AbstractTestModule module : MasterTestModuleCollection
.getInstance().getAllTestModules()) {
categorizer.addModule(module);
}
}
}
private void initializeQueues() {
testerQueue = new TesterQueue();
categorizerQueue = new CategorizerQueue();
// Requester init has to come after categorizer init
requesterQueue = new RequesterQueue();
databaseUsers.add(testerQueue);
databaseUsers.add(categorizerQueue);
databaseUsers.add(requesterQueue);
}
private void initResponseCodeOverrides() {
responseCodeOverrides = new ResponseCodeOverrides(
scanSettings.getReadOnlyManualResponseCodeOverrides(),
scanSettings.getUseAutomaticResponseCodeOverrides(),
scanSettings
.getAcceptableAutomaticResponseCodeOverrideThreshold());
}
public final boolean isGUI() {
return useGUI;
}
public boolean isModuleEnabled(final AbstractTestModule module) {
return enabledModules.contains(module);
}
public boolean isPaused() {
return paused;
}
public boolean isQueryParameterForbidden(final String parameterName) {
return CollectionUtils.containsStringIgnoreCase(
scanSettings.getReadOnlyForbiddenQueryParameters(),
parameterName);
}
public final boolean isShutdownComplete() {
return shutdownComplete;
}
private void loadOrCreateScanSettingsFile() {
File settings = new File(outputDirectory + settingsFile);
boolean settingsLoaded = false;
if (settings.exists()) {
try {
scanSettings.loadScanSettings(outputDirectory + settingsFile);
settingsLoaded = true;
} catch (ConfigurationException e) {
LOGGER.error(
"Failed to load existing settings, using defaults: "
+ e.toString(), e);
}
}
if (!settingsLoaded) {
scanSettings.loadDefaultSettings();
}
}
private long roundMilis(final long milis) {
return Math.round((double) milis / (double) 1000);
}
public void setPaused(final boolean paused) {
this.paused = paused;
}
public synchronized void shutdown(final String message) {
if (!shutdownComplete) {
if (message != null && !message.equals("")) {
displayMessage("Scan termination:",
"The scan is being terminated. Please wait while things are shutdown nicely. "
+ message);
}
String s = "Terminating scan";
if (!message.isEmpty()) {
s += ". The reason is: " + message;
}
s += generateStatus();
LOGGER.error(s);
Scan.getInstance().getProxies().stopProxies();
httpClient.getConnectionManager().shutdown();
for (DatabaseUser user : databaseUsers) {
try {
user.shutdown(true);
} catch (InterruptedException e) {
LOGGER.error(
"Interupted shutting down the databases: "
+ e.toString(), e);
break;
}
}
int timeout = 10000;
int delay = 250;
while (timeout > 0
&& !(categorizerQueue.isShutdown()
&& requesterQueue.isShutdown() && testerQueue
.isShutdown())) {
try {
wait(delay);
} catch (InterruptedException e) {
break;
// probably a stop; this will be handled elsewhere
}
timeout -= delay;
}
shutdownComplete = true;
}
}
private void staticLibraryInit() {
// init static libraries must come after init database
SessionStates.initialize();
PlatformErrorMessages.initialize();
TokenTesting.initialize();
SessionID.initialize();
Nikto.wipeInstance();
SpiderConfig.initialize();
SpiderUtils.initialize();
AmfOutputStreamRegistry.initialize();
databaseUsers.add(SpiderUtils.getInstance());
}
}