Search in sources :

Example 1 with AbstractElementDatabase

use of com.jsql.model.bean.database.AbstractElementDatabase 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();
}
Also used : Table(com.jsql.model.bean.database.Table) LoopDetectedSlidingException(com.jsql.model.exception.LoopDetectedSlidingException) Matcher(java.util.regex.Matcher) Request(com.jsql.model.bean.util.Request) AbstractElementDatabase(com.jsql.model.bean.database.AbstractElementDatabase) AbstractStrategy(com.jsql.model.injection.strategy.AbstractStrategy) SlidingException(com.jsql.model.exception.SlidingException) LoopDetectedSlidingException(com.jsql.model.exception.LoopDetectedSlidingException) StoppedByUserSlidingException(com.jsql.model.exception.StoppedByUserSlidingException) StoppedByUserSlidingException(com.jsql.model.exception.StoppedByUserSlidingException) InjectionFailureException(com.jsql.model.exception.InjectionFailureException) PatternSyntaxException(java.util.regex.PatternSyntaxException)

Example 2 with AbstractElementDatabase

use of com.jsql.model.bean.database.AbstractElementDatabase 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;
}
Also used : Table(com.jsql.model.bean.database.Table) SuspendableGetRows(com.jsql.model.suspendable.SuspendableGetRows) Matcher(java.util.regex.Matcher) Request(com.jsql.model.bean.util.Request) ArrayList(java.util.ArrayList) AbstractElementDatabase(com.jsql.model.bean.database.AbstractElementDatabase) IgnoreMessageException(com.jsql.model.exception.IgnoreMessageException) SlidingException(com.jsql.model.exception.SlidingException) IgnoreMessageException(com.jsql.model.exception.IgnoreMessageException) JSqlException(com.jsql.model.exception.JSqlException) InjectionFailureException(com.jsql.model.exception.InjectionFailureException) SlidingException(com.jsql.model.exception.SlidingException) AbstractElementDatabase(com.jsql.model.bean.database.AbstractElementDatabase) Database(com.jsql.model.bean.database.Database) ArrayList(java.util.ArrayList) List(java.util.List) InjectionFailureException(com.jsql.model.exception.InjectionFailureException)

Aggregations

AbstractElementDatabase (com.jsql.model.bean.database.AbstractElementDatabase)2 Table (com.jsql.model.bean.database.Table)2 Request (com.jsql.model.bean.util.Request)2 InjectionFailureException (com.jsql.model.exception.InjectionFailureException)2 SlidingException (com.jsql.model.exception.SlidingException)2 Matcher (java.util.regex.Matcher)2 Database (com.jsql.model.bean.database.Database)1 IgnoreMessageException (com.jsql.model.exception.IgnoreMessageException)1 JSqlException (com.jsql.model.exception.JSqlException)1 LoopDetectedSlidingException (com.jsql.model.exception.LoopDetectedSlidingException)1 StoppedByUserSlidingException (com.jsql.model.exception.StoppedByUserSlidingException)1 AbstractStrategy (com.jsql.model.injection.strategy.AbstractStrategy)1 SuspendableGetRows (com.jsql.model.suspendable.SuspendableGetRows)1 ArrayList (java.util.ArrayList)1 List (java.util.List)1 PatternSyntaxException (java.util.regex.PatternSyntaxException)1