package com.grendelscan.scan.data;
import java.net.URISyntaxException;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.grendelscan.commons.ConfigurationManager;
import com.grendelscan.commons.StringUtils;
import com.grendelscan.commons.http.RequestOptions;
import com.grendelscan.commons.http.URIStringUtils;
import com.grendelscan.commons.http.responseCompare.HttpResponseScoreUtils;
import com.grendelscan.commons.http.responseCompare.HttpTransactionMatchCriteriaWeights;
import com.grendelscan.commons.http.transactions.StandardHttpTransaction;
import com.grendelscan.commons.http.transactions.TransactionSource;
import com.grendelscan.commons.http.transactions.UnrequestableTransaction;
import com.grendelscan.data.database.collections.DatabaseBackedList;
import com.grendelscan.scan.InterruptedScanException;
import com.grendelscan.scan.Scan;
public class ResponseCodeOverrides
{
private static final Logger LOGGER = LoggerFactory.getLogger(ResponseCodeOverrides.class);
private final Map<Pattern, Integer> manualOverrides;
private final DatabaseBackedList<String> testedDirectories;
private final DatabaseBackedList<StandardHttpTransaction> automaticOverrideSamples;
private final boolean useAutomaticOverrides;
private final int acceptableAutomaticThreshold;
private final int acceptableSamePatternThreshold;
private final HttpTransactionMatchCriteriaWeights weights;
private final RequestOptions requestOptions;
private long totalGenTime = 0;
private final Object timeLock = new Object();
private long totalCompareTime = 0;
private long totalExecuteTime = 0;
public ResponseCodeOverrides(final Map<String, Integer> rawManualOverrides, final boolean useAutomaticOverrides, final int acceptableAutomaticThreshold)
{
testedDirectories = new DatabaseBackedList<String>("response-code-overrides-tested-directories");
requestOptions = new RequestOptions();
requestOptions.reason = "Response code overrides";
requestOptions.testTransaction = false;
requestOptions.followRedirects = false;
weights = new HttpTransactionMatchCriteriaWeights();
this.useAutomaticOverrides = useAutomaticOverrides;
this.acceptableAutomaticThreshold = acceptableAutomaticThreshold;
acceptableSamePatternThreshold = ConfigurationManager.getInt("response_code_overrides.same_pattern_threshold", 95);
manualOverrides = Collections.synchronizedMap(new HashMap<Pattern, Integer>(1));
for (String pattern : rawManualOverrides.keySet())
{
manualOverrides.put(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.DOTALL), rawManualOverrides.get(pattern));
}
if (useAutomaticOverrides)
{
automaticOverrideSamples = new DatabaseBackedList<StandardHttpTransaction>("response-code-overrides-samples");
}
else
{
automaticOverrideSamples = null;
}
}
private synchronized void addSignatureIfNew(final StandardHttpTransaction transaction)
{
synchronized (automaticOverrideSamples)
{
for (StandardHttpTransaction oldTransaction : automaticOverrideSamples)
{
// int score = StringUtils.scoreStringDifferenceIgnoreCase(response, oldResponse, 100);
int score = HttpResponseScoreUtils.scoreResponseMatch(transaction, oldTransaction, weights, 100, Scan.getScanSettings().isParseHtmlDom(), false);
if (score >= acceptableSamePatternThreshold)
{
return;
}
}
automaticOverrideSamples.add(transaction);
}
}
private Set<String> generateUris(final String baseUri)
{
Set<String> uris = Collections.synchronizedSet(new HashSet<String>());
// Make the URI for a directory
uris.add(baseUri + "random" + StringUtils.generateRandomString(StringUtils.FORMAT_LOWER_CASE_ALPHA, 6) + "/");
for (String ext : ConfigurationManager.getStringArray("file_enumeration.common_framework_extensions"))
{
uris.add(baseUri + "random" + StringUtils.generateRandomString(StringUtils.FORMAT_LOWER_CASE_ALPHA, 6) + "." + ext);
}
return uris;
}
public int getLogicalResponseCode(final StandardHttpTransaction transaction, final boolean generateNewProfiles, final int testJobId) throws InterruptedScanException
{
if (transaction.getResponseWrapper().getStatusLine().getStatusCode() == 404 || !Scan.getScanSettings().getUseAutomaticResponseCodeOverrides())
{
return transaction.getResponseWrapper().getStatusLine().getStatusCode();
}
if (generateNewProfiles)
{
String directoryUri;
try
{
directoryUri = URIStringUtils.getDirectoryUri(transaction.getRequestWrapper().getAbsoluteUriString()) + "";
}
catch (URISyntaxException e)
{
LOGGER.error("Very weird problem parsing a url: " + e.toString(), e);
throw new IllegalArgumentException(e);
}
synchronized (testedDirectories)
{
if (!testedDirectories.contains(directoryUri))
{
testAutomaticOverridesForDirectory(transaction, directoryUri, testJobId);
testedDirectories.add(directoryUri);
}
}
}
for (Pattern override : manualOverrides.keySet())
{
if (override.matcher(new String(transaction.getResponseWrapper().getBody())).find())
{
return manualOverrides.get(override);
}
}
Set<StandardHttpTransaction> tempSet;
// Minimize the lock time
synchronized (automaticOverrideSamples)
{
tempSet = new HashSet<StandardHttpTransaction>(automaticOverrideSamples);
}
for (StandardHttpTransaction sampleTransaction : tempSet)
{
int score = HttpResponseScoreUtils.scoreResponseMatch(transaction, sampleTransaction, weights, 100, Scan.getScanSettings().isParseHtmlDom(), false);
if (score >= acceptableAutomaticThreshold)
{
return 404;
}
}
return transaction.getResponseWrapper().getStatusLine().getStatusCode();
}
public long getTotalCompareTime()
{
return totalCompareTime;
}
public long getTotalExecuteTime()
{
return totalExecuteTime;
}
public long getTotalGenTime()
{
return totalGenTime;
}
private void testAutomaticOverridesForDirectory(final StandardHttpTransaction originalTransaction, final String directoryUri, final int testJobId) throws InterruptedScanException
{
long start = Calendar.getInstance().getTimeInMillis();
for (String uri : generateUris(directoryUri))
{
Scan.getInstance().getTesterQueue().handlePause_isRunning();
if (Scan.getScanSettings().getUrlFilters().isUriAllowed(uri))
{
LOGGER.debug("Testing for logical 404 responses: " + uri);
try
{
StandardHttpTransaction transaction = originalTransaction.cloneForSessionReuse(TransactionSource.AUTOMATIC_RESPONSE_CODE_OVERRIDES, testJobId);
transaction.getRequestWrapper().setURI(uri, true);
long estart = Calendar.getInstance().getTimeInMillis();
transaction.setRequestOptions(requestOptions);
transaction.execute();
long eend = Calendar.getInstance().getTimeInMillis();
synchronized (timeLock)
{
totalExecuteTime += eend - estart;
}
if (transaction.isSuccessfullExecution() && transaction.getResponseWrapper().getStatusLine().getStatusCode() != 404)
{
long cstart = Calendar.getInstance().getTimeInMillis();
addSignatureIfNew(transaction);
long cend = Calendar.getInstance().getTimeInMillis();
synchronized (timeLock)
{
totalCompareTime += cend - cstart;
}
}
}
catch (UnrequestableTransaction e)
{
LOGGER.warn("Response code override request is not legal: " + e.toString(), e);
}
}
}
long end = Calendar.getInstance().getTimeInMillis();
synchronized (timeLock)
{
totalGenTime += end - start;
}
}
}