use of com.jsql.model.exception.SlidingException 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.SlidingException in project jsql-injection by ron190.
the class DataAccess method listColumns.
/**
* Get column names and send them to the view.<br>
* Use readable text (not hexa) and parse this pattern with 2nd member forced to 31 (1 in ascii):<br>
* => hh[column name 1]jj[31]hhgghh[column name 2]jj[31]hhggh...hi<br>
* Data window can be cut before the end of the request but the process helps to obtain
* the rest of the unreachable data. The process can be interrupted by the user (stop/pause).
* @param table which contains columns to find
* @return list of columns found
* @throws JSqlException when injection failure or stopped by user
*/
public static List<Column> listColumns(Table table) throws JSqlException {
List<Column> columns = new ArrayList<>();
// Inform the view that table has just been used
Request requestStartProgress = new Request();
requestStartProgress.setMessage(Interaction.START_INDETERMINATE_PROGRESS);
requestStartProgress.setParameters(table);
MediatorModel.model().sendToViews(requestStartProgress);
String resultToParse = "";
try {
String[] pageSource = { "" };
resultToParse = new SuspendableGetRows().run(MediatorModel.model().getVendor().instance().sqlColumns(table), pageSource, true, 0, table);
} catch (SlidingException e) {
LOGGER.warn(e.getMessage(), e);
// Get pieces of data already retreived instead of losing them
if (!"".equals(e.getSlidingWindowAllRows())) {
resultToParse = e.getSlidingWindowAllRows();
} else if (!"".equals(e.getSlidingWindowCurrentRows())) {
resultToParse = e.getSlidingWindowCurrentRows();
}
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
}
// Build SQLite columns
if (MediatorModel.model().getVendor() == Vendor.SQLITE) {
resultToParse = Vendor.SQLITE.transform(resultToParse);
}
// Parse all the data we have retrieved
Matcher regexSearch = Pattern.compile(MODE + ENCLOSE_VALUE_RGX + CELL_TABLE + ENCLOSE_VALUE_RGX).matcher(resultToParse);
Request requestEndProgress = new Request();
requestEndProgress.setMessage(Interaction.END_INDETERMINATE_PROGRESS);
requestEndProgress.setParameters(table);
MediatorModel.model().sendToViews(requestEndProgress);
if (!regexSearch.find()) {
throw new InjectionFailureException();
}
regexSearch.reset();
// Build an array of Column objects from the data we have parsed
while (regexSearch.find()) {
String nameColumn = regexSearch.group(1);
Column column = new Column(nameColumn, table);
columns.add(column);
}
Request requestAddColumns = new Request();
requestAddColumns.setMessage(Interaction.ADD_COLUMNS);
requestAddColumns.setParameters(columns);
MediatorModel.model().sendToViews(requestAddColumns);
return columns;
}
use of com.jsql.model.exception.SlidingException in project jsql-injection by ron190.
the class DataAccess method listValues.
/**
* Get table values and count each occurrences and send them to the view.<br>
* Values are on clear text (not hexa) and follows this window pattern<br>
* => hh[value 1]jj[count]hhgghh[value 2]jj[count]hhggh...hi<br>
* Data window can be cut before the end of the request but the process helps to obtain
* the rest of the unreachable data. The process can be interrupted by the user (stop/pause).
* @param columns choosed by the user
* @return a 2x2 table containing values by columns
* @throws JSqlException when injection failure or stopped by user
*/
public static String[][] listValues(List<Column> columns) throws JSqlException {
Database database = (Database) columns.get(0).getParent().getParent();
Table table = (Table) columns.get(0).getParent();
int rowCount = columns.get(0).getParent().getChildCount();
// Inform the view that table has just been used
Request request = new Request();
request.setMessage(Interaction.START_PROGRESS);
request.setParameters(table);
MediatorModel.model().sendToViews(request);
// Build an array of column names
List<String> columnsName = new ArrayList<>();
for (AbstractElementDatabase e : columns) {
columnsName.add(e.toString());
}
/*
* From that array, build the SQL fields nicely
* => col1{%}col2...
* ==> trim(ifnull(`col1`,0x00)),0x7f,trim(ifnull(`Col2`,0x00))...
*/
String[] arrayColumns = columnsName.toArray(new String[columnsName.size()]);
String resultToParse = "";
try {
String[] pageSource = { "" };
resultToParse = new SuspendableGetRows().run(MediatorModel.model().getVendor().instance().sqlRows(arrayColumns, database, table), pageSource, true, rowCount, table);
} catch (SlidingException e) {
LOGGER.warn(e.getMessage(), e);
// Get pieces of data already retreived instead of losing them
if (!"".equals(e.getSlidingWindowAllRows())) {
resultToParse = e.getSlidingWindowAllRows();
} else if (!"".equals(e.getSlidingWindowCurrentRows())) {
resultToParse = e.getSlidingWindowCurrentRows();
}
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
}
// Parse all the data we have retrieved
Matcher regexSearch = 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(resultToParse);
if (!regexSearch.find()) {
throw new InjectionFailureException();
}
regexSearch.reset();
int rowsFound = 0;
List<List<String>> listValues = new ArrayList<>();
// => row number, occurrence, value1, value2...
while (regexSearch.find()) {
String values = regexSearch.group(1);
int instances = Integer.parseInt(regexSearch.group(2));
listValues.add(new ArrayList<String>());
listValues.get(rowsFound).add(Integer.toString(rowsFound + 1));
listValues.get(rowsFound).add("x" + instances);
for (String cellValue : values.split("\\x7F", -1)) {
listValues.get(rowsFound).add(cellValue);
}
rowsFound++;
}
// Add the default title to the columns: row number, occurrence
columnsName.add(0, "");
columnsName.add(0, "");
// Build a proper 2D array from the data
String[][] tableDatas = new String[listValues.size()][columnsName.size()];
for (int indexRow = 0; indexRow < listValues.size(); indexRow++) {
boolean isIncomplete = false;
for (int indexColumn = 0; indexColumn < columnsName.size(); indexColumn++) {
try {
tableDatas[indexRow][indexColumn] = listValues.get(indexRow).get(indexColumn);
} catch (IndexOutOfBoundsException e) {
isIncomplete = true;
LOGGER.trace("Incomplete line found");
// Ignore
IgnoreMessageException exceptionIgnored = new IgnoreMessageException(e);
LOGGER.trace(exceptionIgnored, exceptionIgnored);
}
}
if (isIncomplete) {
LOGGER.warn("String is too long, row #" + (indexRow + 1) + " is incomplete:");
LOGGER.warn(String.join(", ", listValues.get(indexRow).toArray(new String[listValues.get(indexRow).size()])));
}
}
arrayColumns = columnsName.toArray(new String[columnsName.size()]);
// Group the columns names, values and Table object in one array
Object[] objectData = { arrayColumns, tableDatas, table };
Request requestCreateValuesTab = new Request();
requestCreateValuesTab.setMessage(Interaction.CREATE_VALUES_TAB);
requestCreateValuesTab.setParameters(objectData);
MediatorModel.model().sendToViews(requestCreateValuesTab);
Request requestEndProgress = new Request();
requestEndProgress.setMessage(Interaction.END_PROGRESS);
requestEndProgress.setParameters(table);
MediatorModel.model().sendToViews(requestEndProgress);
return tableDatas;
}
use of com.jsql.model.exception.SlidingException in project jsql-injection by ron190.
the class DataAccess method listDatabases.
/**
* Get database names and table counts and send them to the view.<br>
* Use readable text (not hexa) and parse this pattern:<br>
* => hh[database name 1]jj[table count]hhgghh[database name 2]jj[table count]hhggh...hi<br>
* Data window can be cut before the end of the request but the process helps to obtain
* the rest of the unreachable data. The process can be interrupted by the user (stop/pause).
* @return list of databases found
* @throws JSqlException when injection failure or stopped by user
*/
public static List<Database> listDatabases() throws JSqlException {
LOGGER.trace(I18n.valueByKey("LOG_FETCHING_DATABASES"));
List<Database> databases = new ArrayList<>();
String resultToParse = "";
try {
String[] sourcePage = { "" };
resultToParse = new SuspendableGetRows().run(MediatorModel.model().getVendor().instance().sqlDatabases(), sourcePage, true, 0, null);
} catch (SlidingException e) {
LOGGER.warn(e.getMessage(), e);
// Get pieces of data already retreived instead of losing them
if (!"".equals(e.getSlidingWindowAllRows())) {
resultToParse = e.getSlidingWindowAllRows();
} else if (!"".equals(e.getSlidingWindowCurrentRows())) {
resultToParse = e.getSlidingWindowCurrentRows();
}
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
}
// Parse all data we have retrieved
Matcher regexSearch = Pattern.compile(MODE + ENCLOSE_VALUE_RGX + CELL_TABLE + ENCLOSE_VALUE_RGX).matcher(resultToParse);
if (!regexSearch.find()) {
throw new InjectionFailureException();
}
regexSearch.reset();
// Build an array of Database objects from the data we have parsed
while (regexSearch.find()) {
String databaseName = regexSearch.group(1);
String tableCount = regexSearch.group(2);
Database newDatabase = new Database(databaseName, tableCount);
databases.add(newDatabase);
}
Request request = new Request();
request.setMessage(Interaction.ADD_DATABASES);
request.setParameters(databases);
MediatorModel.model().sendToViews(request);
return databases;
}
use of com.jsql.model.exception.SlidingException in project jsql-injection by ron190.
the class DataAccess method listTables.
/**
* Get tables name and row count and send them to the view.<br>
* Use readable text (not hexa) and parse this pattern:<br>
* => hh[table name 1]jj[rows count]hhgghh[table name 2]jj[rows count]hhggh...hi<br>
* Data window can be cut before the end of the request but the process helps to obtain
* the rest of the unreachable data. The process can be interrupted by the user (stop/pause).
* @param database which contains tables to find
* @return list of tables found
* @throws JSqlException when injection failure or stopped by user
*/
public static List<Table> listTables(Database database) throws JSqlException {
// Reset stoppedByUser if list of Databases is partial
// and some Tables are still reachable
MediatorModel.model().setIsStoppedByUser(false);
List<Table> tables = new ArrayList<>();
// Inform the view that database has just been used
Request requestStartProgress = new Request();
requestStartProgress.setMessage(Interaction.START_PROGRESS);
requestStartProgress.setParameters(database);
MediatorModel.model().sendToViews(requestStartProgress);
String tableCount = Integer.toString(database.getChildCount());
String resultToParse = "";
try {
String[] pageSource = { "" };
resultToParse = new SuspendableGetRows().run(MediatorModel.model().getVendor().instance().sqlTables(database), pageSource, true, Integer.parseInt(tableCount), database);
} catch (SlidingException e) {
LOGGER.warn(e.getMessage(), e);
// Get pieces of data already retreived instead of losing them
if (!"".equals(e.getSlidingWindowAllRows())) {
resultToParse = e.getSlidingWindowAllRows();
} else if (!"".equals(e.getSlidingWindowCurrentRows())) {
resultToParse = e.getSlidingWindowCurrentRows();
}
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
}
// Parse all the data we have retrieved
Matcher regexSearch = Pattern.compile(MODE + ENCLOSE_VALUE_RGX + CELL_TABLE + ENCLOSE_VALUE_RGX).matcher(resultToParse);
Request requestEndProgress = new Request();
requestEndProgress.setMessage(Interaction.END_PROGRESS);
requestEndProgress.setParameters(database);
MediatorModel.model().sendToViews(requestEndProgress);
if (!regexSearch.find()) {
throw new InjectionFailureException();
}
regexSearch.reset();
// Build an array of Table objects from the data we have parsed
while (regexSearch.find()) {
String tableName = regexSearch.group(1);
String rowCount = regexSearch.group(2);
Table newTable = new Table(tableName, rowCount, database);
tables.add(newTable);
}
Request requestAddTables = new Request();
requestAddTables.setMessage(Interaction.ADD_TABLES);
requestAddTables.setParameters(tables);
MediatorModel.model().sendToViews(requestAddTables);
return tables;
}
Aggregations