org.zaproxy.zap.extension.ascanrulesAlpha.SQLInjectionSQLite.java Source code

Java tutorial

Introduction

Here is the source code for org.zaproxy.zap.extension.ascanrulesAlpha.SQLInjectionSQLite.java

Source

/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2014 The ZAP Development Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.zaproxy.zap.extension.ascanrulesAlpha;

import java.net.UnknownHostException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.httpclient.InvalidRedirectLocationException;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.core.scanner.AbstractAppParamPlugin;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.core.scanner.Category;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.model.Tech;
import org.zaproxy.zap.model.TechSet;

/**
 * The SQLInjectionSQLite plugin identifies SQLite specific SQL Injection vulnerabilities using
 * SQLite specific syntax. If it doesn't use SQLite specific syntax, it belongs in the generic
 * SQLInjection class!
 *
 * @author 70pointer
 */
public class SQLInjectionSQLite extends AbstractAppParamPlugin {

    // Some relevant notes about SQLite's support for various functions, which will affect what SQL
    // is valid,
    // and can be used to exploit each particular version :)
    // some of the functions noted here are *very* useful in exploiting the system hosting the
    // SQLite instance,
    // but I won't give the game away too easily. Go have a play!
    // 3.8.6       (2014-08-15) - hex integer literals supported, likely(X) supported, readfile(X)
    // and writefile(X,Y) supported (if extension loading enabled)
    // 3.8.3       (2014-02-03) - common table subexpressions supported ("WITH" keyword), printf
    // function supported
    // 3.8.1       (2013-10-17) - unlikely() and likelihood() functions supported
    // 3.8.0       (2013-08-26) - percentile() function added as loadable extension
    // 3.7.17      (2013-05-20) - new loadable extensions: including amatch, closure, fuzzer,
    // ieee754, nextchar, regexp, spellfix, and wholenumber
    // 3.7.16      (2013-03-18) - unicode(A) and char(X1,...,XN) supported
    // 3.7.15      (2012-12-12) - instr() supported
    // 3.6.8       (2009-01-12) - nested transactions supported
    // 3.5.7       (2008-03-17) - ALTER TABLE uses double-quotes instead of single-quotes for
    // quoting filenames (why filenames????).
    // 3.3.13      (2007-02-13) - randomBlob() and hex() supported
    // 3.3.8       (2006-10-09) - IF EXISTS on CREATE/DROP TRIGGER/VIEW
    // 3.3.7       (2006-08-12) - virtual tables, dynamically loaded extensions, MATCH operator
    // supported
    // 3.2.6       (2005-09-17) - COUNT(DISTINCT expr) supported
    // 3.2.3       (2005-08-21) - CAST operator, grave-accent quoting supported
    // 3.2.0       (2005-03-21) - ALTER TABLE ADD COLUMN supported
    // 3.1.0 ALPHA (2005-01-21) - ALTER TABLE ... RENAME TABLE, CURRENT_TIME, CURRENT_DATE, and
    // CURRENT_TIMESTAMP, EXISTS clause, correlated subqueries supported
    // 3.0.0 alpha (2004-06-18) - dropped support for COPY function, possibly added
    // sqlite_source_id(), replace() (which were not in 2.8.17)
    // 2.8.6       (2003-08-21) - date functions added
    // 2.8.5       (2003-07-22) - supports LIMIT on a compound SELECT statement
    // 2.8.1       (2003-05-16) - ATTACH and DETACH commands supported
    // 2.5.0       (2002-06-17) - Double-quoted strings interpreted as column names not text
    // literals,
    //                           SQL-92 compliant handling of NULLs, full SQL-92 join syntax and
    // LEFT OUTER JOINs supported
    // 2.4.7       (2002-04-06) - "select TABLE.*", last_insert_rowid() supported
    // 2.4.4       (2002-03-24) - CASE expressions supported
    // 2.4.0       (2002-03-10) - coalesce(), lower(), upper(), and random() supported
    // 2.3.3       (2002-02-18) - Allow identifiers to be quoted in square brackets, "CREATE TABLE
    // AS SELECT" supported
    // 2.2.0       (2001-12-22) - "SELECT rowid, * FROM table1" supported
    // 2.0.3       (2001-10-13) - &, |,~,<<,>>,round() and abs() supported
    // 2.0.1       (2001-10-02) - "expr NOT NULL", "expr NOTNULL" supported
    // 1.0.32      (2001-07-23) - quoted strings supported as table and column names in expressions
    // 1.0.28      (2001-04-04) - special column names ROWID, OID, and _ROWID_ supported (with
    // random values)
    // 1.0.4       (2000-08-28) - length() and substr() supported
    // 1.0         (2000-08-17) - covers lots of unversioned releases.  ||, fcnt(), UNION, UNION
    // ALL, INTERSECT, and EXCEPT, LIKE, GLOB, COPY supported

    private boolean doTimeBased = false;

    private int doTimeMaxRequests = 0;

    private boolean doUnionBased = false;
    private int doUnionMaxRequests = 0;

    /** SQLite one-line comment */
    public static final String SQL_ONE_LINE_COMMENT = "--";

    /**
     * SQLite specific time based injection strings, where each tries to cause a measurable delay
     */

    // Note: <<<<ORIGINALVALUE>>>> is replaced with the original parameter value at runtime in these
    // examples below
    // TODO: maybe add support for ')' after the original value, before the sleeps
    // Note: randomblob is supported from SQLite 3.3.13 (2007-02-13)
    //      case statement is supported from SQLite 2.4.4 (2002-03-24)
    private static String[] SQL_SQLITE_TIME_REPLACEMENTS = {
            // omitting original param
            "case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then 1 else 1 end ", // integer
            "' | case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then \"\" else \"\" end | '", // character/string (single quote)
            "\" | case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then \"\" else \"\" end | \"", // character/string (double quote)
            "case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then 1 else 1 end " + SQL_ONE_LINE_COMMENT, // integer
            "' | case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then \"\" else \"\" end "
                    + SQL_ONE_LINE_COMMENT, // character/string (single quote)
            "\" | case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then \"\" else \"\" end "
                    + SQL_ONE_LINE_COMMENT, // character/string (double quote)

            // with the original parameter
            "<<<<ORIGINALVALUE>>>> * case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then 1 else 1 end ", // integer
            "<<<<ORIGINALVALUE>>>>' | case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then \"\" else \"\" end | '", // character/string (single quote)
            "<<<<ORIGINALVALUE>>>>\" | case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then \"\" else \"\" end | \"", // character/string (double quote)
            "<<<<ORIGINALVALUE>>>> * case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then 1 else 1 end "
                    + SQL_ONE_LINE_COMMENT, // integer
            "<<<<ORIGINALVALUE>>>>' | case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then \"\" else \"\" end "
                    + SQL_ONE_LINE_COMMENT, // character/string (single quote)
            "<<<<ORIGINALVALUE>>>>\" | case randomblob(<<<<NUMBLOBBYTES>>>>) when not null then \"\" else \"\" end "
                    + SQL_ONE_LINE_COMMENT, // character/string (double quote)
    };

    /** if the following errors occur during the attack, it's a SQL injection vuln */
    private Pattern errorMessagePatterns[] = {
            Pattern.compile("no such function: randomblob", Pattern.CASE_INSENSITIVE) // this one is specific to the time-based attack
            // attempted here, and is indicative of SQLite versions <
            // 3.3.13, and >= 2.4.4 (because the CASE statement is
            // used)
            , Pattern.compile("near \\\".+\\\": syntax error", Pattern.CASE_INSENSITIVE) };

    /** a template that defines how a UNION statement is built up, to find the SQLite version */
    private static String UNION_ATTACK_TEMPLATE = "<<<<VALUE>>>><<<<SYNTACTIC_PREVIOUS_STATEMENT_TYPE_CLOSER>>>><<<<SYNTACTIC_PREVIOUS_STATEMENT_CLAUSE_CLOSER>>>> <<<<UNIONSTATEMENT>>>> select <<<<SQLITE_VERSION_FUNCTION>>>><<<<UNIONADDITIONALCOLUMNS>>>><<<<SYNTACTIC_NEXT_STATEMENT_COMMENTER>>>>";

    private static String SYNTACTIC_PREVIOUS_STATEMENT_TYPE_CLOSERS[] = { "" // closing off an int parameter in the SQL statement
            , "'" // closing off a char/string parameter in the SQL statement
            , "\"" // closing off a string parameter in the SQL statement
    };
    private static String SYNTACTIC_PREVIOUS_STATEMENT_CLAUSE_CLOSERS[] = { "", ")", "))", ")))", "))))", ")))))" };
    private static String SYNTACTIC_UNION_STATEMENTS[] = { "UNION"
            // ,"UNION ALL"  Not necessary
    };
    private static String SQLITE_VERSION_FUNCTIONS[] = { "sqlite_version()" // string type - gets the version in the form of "3.7.16.2", for instance
            , "sqlite_version()+0" // numeric type - SQLite does implicit casting to convert "3.7.16.2" to
            // 3.7 in numeric form.
            , "sqlite_source_id()" // string type - this function was added in 3.6.18.  it gets the
            // version in the form of "2013-04-12 11:52:43
            // cbea02d93865ce0e06789db95fd9168ebac970c7"
            // there may not be much point in running this one, since it gives much the same info as
            // "sqlite_version()" and cannot be converted to an int.
            // maybe it's useful in the case that a WAF is detecting / blocking "sqlite_version()"
            // though?
    };
    private static String UNION_ADDITIONAL_COLUMNS[] = { "", ",null", ",null,null", ",null,null,null",
            ",null,null,null,null", ",null,null,null,null,null", ",null,null,null,null,null,null",
            ",null,null,null,null,null,null,null", ",null,null,null,null,null,null,null,null",
            ",null,null,null,null,null,null,null,null,null" };
    private static String SYNTACTIC_NEXT_STATEMENT_COMMENTER[] = { SQL_ONE_LINE_COMMENT
            // ,""            //this isn't useful at all
    };

    /** set depending on the attack strength / threshold */
    private long maxBlobBytes = 0;

    private long minBlobBytes = 100000;
    private long parseDelayDifference = 0;
    private long incrementalDelayIncreasesForAlert = 0;

    private char[] RANDOM_PARAMETER_CHARS = "abcdefghijklmnopqrstuvwyxz0123456789".toCharArray();

    /** plugin dependencies (none! not even "SQL Injection") */
    private static final String[] dependency = {};

    /** for logging. */
    private static Logger log = Logger.getLogger(SQLInjectionSQLite.class);

    /** determines if we should output Debug level logging */
    private boolean debugEnabled = log.isDebugEnabled();

    @Override
    public int getId() {
        return 40024;
    }

    @Override
    public String getName() {
        return Constant.messages.getString("ascanalpha.sqlinjection.sqlite.name");
    }

    @Override
    public String[] getDependency() {
        return dependency;
    }

    @Override
    public boolean targets(TechSet technologies) {
        return technologies.includes(Tech.SQLite);
    }

    @Override
    public String getDescription() {
        return Constant.messages.getString("ascanalpha.sqlinjection.desc");
    }

    @Override
    public int getCategory() {
        return Category.INJECTION;
    }

    @Override
    public String getSolution() {
        return Constant.messages.getString("ascanalpha.sqlinjection.soln");
    }

    @Override
    public String getReference() {
        return Constant.messages.getString("ascanalpha.sqlinjection.refs");
    }

    @Override
    public void init() {
        if (this.debugEnabled)
            log.debug("Initialising");

        // set up what we are allowed to do, depending on the attack strength that was set.
        if (this.getAttackStrength() == AttackStrength.LOW) {
            doTimeBased = true;
            doTimeMaxRequests = 15;
            this.maxBlobBytes = 1000000000;
            doUnionBased = false;
            doUnionMaxRequests = 0;
        } else if (this.getAttackStrength() == AttackStrength.MEDIUM) {
            doTimeBased = true;
            doTimeMaxRequests = 35;
            this.maxBlobBytes = 1000000000;
            doUnionBased = false;
            doUnionMaxRequests = 0;
        } else if (this.getAttackStrength() == AttackStrength.HIGH) {
            doTimeBased = true;
            doTimeMaxRequests = 50;
            this.maxBlobBytes = 1000000000;
            doUnionBased = true;
            doUnionMaxRequests = 50;
        } else if (this.getAttackStrength() == AttackStrength.INSANE) {
            doTimeBased = true;
            doTimeMaxRequests = 500;
            this.maxBlobBytes = 1000000000;
            doUnionBased = true;
            doUnionMaxRequests = 5000;
        }

        // the allowable difference between a parse delay and an attack delay is controlled by the
        // threshold
        if (this.getAlertThreshold() == AlertThreshold.LOW) {
            parseDelayDifference = 100;
            incrementalDelayIncreasesForAlert = 1;
        } else if (this.getAlertThreshold() == AlertThreshold.MEDIUM) {
            parseDelayDifference = 200;
            incrementalDelayIncreasesForAlert = 2;
        } else if (this.getAlertThreshold() == AlertThreshold.HIGH) {
            parseDelayDifference = 400;
            incrementalDelayIncreasesForAlert = 3;
        }
    }

    /**
     * scans for SQL Injection vulnerabilities, using SQLite specific syntax. If it doesn't use
     * specifically SQLite syntax, it does not belong in here, but in TestSQLInjection
     */
    @Override
    public void scan(HttpMessage originalMessage, String paramName, String originalParamValue) {

        try {
            // the original message passed to us never has the response populated. fix that by
            // re-retrieving it..
            sendAndReceive(originalMessage, false); // do not follow redirects

            // Do time based SQL injection checks..
            // Timing Baseline check: we need to get the time that it took the original query, to
            // know if the time based check is working correctly..
            HttpMessage msgTimeBaseline = getNewMsg();
            long originalTimeStarted = System.currentTimeMillis();
            try {
                sendAndReceive(msgTimeBaseline);
            } catch (java.net.SocketTimeoutException e) {
                // to be expected occasionally, if the base query was one that contains some
                // parameters exploiting time based SQL injection?
                if (this.debugEnabled)
                    log.debug("The Base Time Check timed out on [" + msgTimeBaseline.getRequestHeader().getMethod()
                            + "] URL [" + msgTimeBaseline.getRequestHeader().getURI().getURI() + "]");
            }
            long originalTimeUsed = System.currentTimeMillis() - originalTimeStarted;
            // if the time was very slow (because JSP was being compiled on first call, for
            // instance)
            // then the rest of the time based logic will fail.  Lets double-check for that scenario
            // by requesting the url again.
            // If it comes back in a more reasonable time, we will use that time instead as our
            // baseline.  If it come out in a slow fashion again,
            // we will abort the check on this URL, since we will only spend lots of time trying
            // request, when we will (very likely) not get positive results.
            if (originalTimeUsed > 5000) {
                long originalTimeStarted2 = System.currentTimeMillis();
                try {
                    sendAndReceive(msgTimeBaseline);
                } catch (java.net.SocketTimeoutException e) {
                    // to be expected occasionally, if the base query was one that contains some
                    // parameters exploiting time based SQL injection?
                    if (this.debugEnabled)
                        log.debug(
                                "Base Time Check 2 timed out on [" + msgTimeBaseline.getRequestHeader().getMethod()
                                        + "] URL [" + msgTimeBaseline.getRequestHeader().getURI().getURI() + "]");
                }
                long originalTimeUsed2 = System.currentTimeMillis() - originalTimeStarted2;
                if (originalTimeUsed2 > 5000) {
                    // no better the second time around.  we need to bale out.
                    if (this.debugEnabled)
                        log.debug("Both base time checks 1 and 2 for ["
                                + msgTimeBaseline.getRequestHeader().getMethod() + "] URL ["
                                + msgTimeBaseline.getRequestHeader().getURI().getURI()
                                + "] are way too slow to be usable for the purposes of checking for time based SQL Injection checking.  We are aborting the check on this particular url.");
                    return;
                } else {
                    // phew.  the second time came in within the limits. use the later timing
                    // details as the base time for the checks.
                    originalTimeUsed = originalTimeUsed2;
                    originalTimeStarted = originalTimeStarted2;
                }
            }
            // end of timing baseline check

            int countTimeBasedRequests = 0;
            if (this.debugEnabled)
                log.debug("Scanning URL [" + getBaseMsg().getRequestHeader().getMethod() + "] ["
                        + getBaseMsg().getRequestHeader().getURI() + "], [" + paramName + "] with value ["
                        + originalParamValue + "] for SQL Injection");

            // SQLite specific time-based SQL injection checks
            boolean foundTimeBased = false;
            for (int timeBasedSQLindex = 0; timeBasedSQLindex < SQL_SQLITE_TIME_REPLACEMENTS.length && doTimeBased
                    && countTimeBasedRequests < doTimeMaxRequests && !foundTimeBased; timeBasedSQLindex++) {
                // since we have no means to create a deterministic delay in SQLite, we need to take
                // a different approach:
                // in each iteration, increase the number of random blobs for SQLite to create.  If
                // we can detect an increasing delay, we know
                // that the payload has been successfully injected.
                int numberOfSequentialIncreases = 0;
                String detectableDelayParameter = null;
                long detectableDelay = 0;
                String maxDelayParameter = null;
                long maxDelay = 0;
                HttpMessage detectableDelayMessage = null;
                long previousDelay = originalTimeUsed;
                boolean potentialTimeBasedSQLInjection = false;
                boolean timeExceeded = false;

                for (long numBlobsToCreate = minBlobBytes; numBlobsToCreate <= this.maxBlobBytes && !timeExceeded
                        && numberOfSequentialIncreases < incrementalDelayIncreasesForAlert; numBlobsToCreate *= 10) {

                    HttpMessage msgDelay = getNewMsg();
                    String newTimeBasedInjectionValue = SQL_SQLITE_TIME_REPLACEMENTS[timeBasedSQLindex]
                            .replace("<<<<ORIGINALVALUE>>>>", originalParamValue);
                    newTimeBasedInjectionValue = newTimeBasedInjectionValue.replace("<<<<NUMBLOBBYTES>>>>",
                            Long.toString(numBlobsToCreate));
                    setParameter(msgDelay, paramName, newTimeBasedInjectionValue);

                    if (this.debugEnabled)
                        log.debug("\nTrying '" + newTimeBasedInjectionValue
                                + "'. The number of Sequential Increases already is "
                                + numberOfSequentialIncreases);

                    // send it.
                    long modifiedTimeStarted = System.currentTimeMillis();
                    try {
                        sendAndReceive(msgDelay);
                        countTimeBasedRequests++;
                    } catch (java.net.SocketTimeoutException e) {
                        // to be expected occasionally, if the contains some parameters exploiting
                        // time based SQL injection
                        if (this.debugEnabled)
                            log.debug("The time check query timed out on ["
                                    + msgTimeBaseline.getRequestHeader().getMethod() + "] URL ["
                                    + msgTimeBaseline.getRequestHeader().getURI().getURI() + "] on field: ["
                                    + paramName + "]");
                    }
                    long modifiedTimeUsed = System.currentTimeMillis() - modifiedTimeStarted;

                    // before we do the time based checking, first check for a known error message
                    // from the atatck, indicating a SQL injection vuln
                    for (Pattern errorMessagePattern : errorMessagePatterns) {
                        Matcher matcher = errorMessagePattern.matcher(msgDelay.getResponseBody().toString());
                        boolean errorFound = matcher.find();
                        if (errorFound) {
                            // Likely an error based SQL Injection. Raise it
                            String extraInfo = Constant.messages.getString(
                                    "ascanalpha.sqlinjection.sqlite.alert.errorbased.extrainfo",
                                    errorMessagePattern);
                            // raise the alert
                            bingo(Alert.RISK_HIGH, Alert.CONFIDENCE_MEDIUM, getName(), getDescription(),
                                    getBaseMsg().getRequestHeader().getURI().getURI(), // url
                                    paramName, newTimeBasedInjectionValue, extraInfo, getSolution(),
                                    errorMessagePattern.toString(), this.getCweId(), this.getWascId(), msgDelay);

                            if (this.debugEnabled)
                                log.debug("A likely Error Based SQL Injection Vulnerability has been found with ["
                                        + msgDelay.getRequestHeader().getMethod() + "] URL ["
                                        + msgDelay.getRequestHeader().getURI().getURI() + "] on field: ["
                                        + paramName + "], by matching for pattern ["
                                        + errorMessagePattern.toString() + "]");
                            foundTimeBased = true; // yeah, I know. we found an error based, while looking
                            // for a time based. bale out anyways.
                            break; // out of the loop
                        }
                    }
                    // outta the time based loop..
                    if (foundTimeBased)
                        break;

                    // no error message detected from the time based attack.. continue looking for
                    // time based injection point.

                    // cap the time we will delay by to 10 seconds
                    if (modifiedTimeUsed > 10000)
                        timeExceeded = true;

                    boolean parseTimeEquivalent = false;
                    if (modifiedTimeUsed > previousDelay) {
                        if (this.debugEnabled)
                            log.debug("The response time " + modifiedTimeUsed + " is > the previous response time "
                                    + previousDelay);
                        // in order to rule out false positives due to the increasing SQL parse time
                        // for longer parameter values
                        // we send a random (alphanumeric only) string value of the same length as
                        // the attack parameter
                        // we expect the response time for the SQLi attack to be greater than or
                        // equal to the response time for
                        // the random alphanumeric string parameter
                        // if this is not the case, then we assume that the attack parameter is not
                        // a potential SQL injection causing payload.
                        HttpMessage msgParseDelay = getNewMsg();
                        String parseDelayCheckParameter = RandomStringUtils
                                .random(newTimeBasedInjectionValue.length(), RANDOM_PARAMETER_CHARS);
                        setParameter(msgParseDelay, paramName, parseDelayCheckParameter);
                        long parseDelayTimeStarted = System.currentTimeMillis();
                        sendAndReceive(msgParseDelay);
                        countTimeBasedRequests++;
                        long parseDelayTimeUsed = System.currentTimeMillis() - parseDelayTimeStarted;

                        // figure out if the attack delay and the (non-sql-injection) parse delay
                        // are within X ms of each other..
                        parseTimeEquivalent = (Math
                                .abs(modifiedTimeUsed - parseDelayTimeUsed) < this.parseDelayDifference);
                        if (this.debugEnabled)
                            log.debug("The parse time a random parameter of the same length is "
                                    + parseDelayTimeUsed + ", so the attack and random parameter are "
                                    + (parseTimeEquivalent ? "" : "NOT ")
                                    + "equivalent (given the user defined attack threshold)");
                    }

                    if (modifiedTimeUsed > previousDelay && !parseTimeEquivalent) {

                        maxDelayParameter = newTimeBasedInjectionValue;
                        maxDelay = modifiedTimeUsed;

                        // potential for SQL injection, detectable with "numBlobsToCreate" random
                        // blobs being created..
                        numberOfSequentialIncreases++;
                        if (!potentialTimeBasedSQLInjection) {
                            if (log.isDebugEnabled())
                                log.debug("Setting the Detectable Delay parameter to '" + newTimeBasedInjectionValue
                                        + "'");
                            detectableDelayParameter = newTimeBasedInjectionValue;
                            detectableDelay = modifiedTimeUsed;
                            detectableDelayMessage = msgDelay;
                        }
                        potentialTimeBasedSQLInjection = true;
                    } else {
                        // either no SQL injection, invalid SQL syntax, or timing difference is not
                        // detectable with "numBlobsToCreate" random blobs being created.
                        // keep trying with larger numbers of "numBlobsToCreate", since that's the
                        // thing we can most easily control and verify
                        // note also: if for some reason, an earlier attack with a smaller number of
                        // blobs indicated there might be a vulnerability
                        // then this case will rule that out if it was a fluke...
                        // the timing delay must keep increasing, as the number of blobs is
                        // increased.
                        potentialTimeBasedSQLInjection = false;
                        numberOfSequentialIncreases = 0;
                        detectableDelayParameter = null;
                        detectableDelay = 0;
                        detectableDelayMessage = null;
                        maxDelayParameter = null;
                        maxDelay = 0;
                        // do not break at this point, since we may simply need to keep increasing
                        // numBlobsToCreate to
                        // a point where we can detect the resulting delay
                    }
                    if (this.debugEnabled)
                        log.debug("Time Based SQL Injection test for " + numBlobsToCreate + " random blob bytes: ["
                                + newTimeBasedInjectionValue + "] on field: [" + paramName + "] with value ["
                                + newTimeBasedInjectionValue + "] took " + modifiedTimeUsed
                                + "ms, where the original took " + originalTimeUsed + "ms");
                    previousDelay = modifiedTimeUsed;

                    // bale out if we were asked nicely
                    if (isStop()) {
                        if (this.debugEnabled)
                            log.debug("Stopping the scan due to a user request");
                        return;
                    }
                } // end of for loop to increase the number of random blob bytes to create

                // the number of times that we could sequentially increase the delay by increasing
                // the "number of random blob bytes to create"
                // is the basis for the threshold of the alert.  In some cases, the user may want to
                // see a solid increase in delay
                // for say 4 or 5 iterations, in order to be confident the vulnerability exists.  In
                // other cases, the user may be happy with just 2 sequential increases...
                if (this.debugEnabled)
                    log.debug("Number of sequential increases: " + numberOfSequentialIncreases);
                if (numberOfSequentialIncreases >= this.incrementalDelayIncreasesForAlert) {
                    // Likely a SQL Injection. Raise it
                    String extraInfo = Constant.messages.getString(
                            "ascanalpha.sqlinjection.sqlite.alert.timebased.extrainfo", detectableDelayParameter,
                            detectableDelay, maxDelayParameter, maxDelay, originalParamValue, originalTimeUsed);

                    // raise the alert
                    bingo(Alert.RISK_HIGH, Alert.CONFIDENCE_MEDIUM, getName(), getDescription(),
                            getBaseMsg().getRequestHeader().getURI().getURI(), // url
                            paramName, detectableDelayParameter, extraInfo, getSolution(),
                            extraInfo /*as evidence*/, this.getCweId(), this.getWascId(), detectableDelayMessage);

                    if (this.debugEnabled)
                        log.debug("A likely Time Based SQL Injection Vulnerability has been found with ["
                                + detectableDelayMessage.getRequestHeader().getMethod() + "] URL ["
                                + detectableDelayMessage.getRequestHeader().getURI().getURI() + "] on field: ["
                                + paramName + "]");

                    // outta the time based loop..
                    foundTimeBased = true;
                    break;
                } // the user-define threshold has been exceeded. raise it.

                // outta the time based loop..
                if (foundTimeBased)
                    break;

                // bale out if we were asked nicely
                if (isStop()) {
                    if (this.debugEnabled)
                        log.debug("Stopping the scan due to a user request");
                    return;
                }
            } // for each time based SQL index
              // end of check for SQLite time based SQL Injection

            // TODO: fix this logic, cos it's broken already. it reports version 2.2 and 4.0..
            // (false positives ahoy)
            doUnionBased = false;

            // try to get the version of SQLite, using a UNION based SQL injection vulnerability
            // do this regardless of whether we already found a vulnerability using another
            // technique.
            if (doUnionBased) {
                int unionRequests = 0;
                // catch 3.0, 3.0.1, 3.0.1.1, 3.7.16.2, etc
                Pattern versionNumberPattern = Pattern.compile(
                        "[0-9]{1}\\.[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{1,2}|[0-9]{1}\\.[0-9]{1,2}\\.[0-9]{1,2}|[0-9]{1}\\.[0-9]{1,2}",
                        PATTERN_PARAM);
                String candidateValues[] = { "", originalParamValue };
                // shonky break label. labels the loop to break out of.  I believe I just finished a
                // sentence with a preposition too. Oh My.
                unionLoops: for (String sqliteVersionFunction : SQLITE_VERSION_FUNCTIONS) {
                    for (String statementTypeCloser : SYNTACTIC_PREVIOUS_STATEMENT_TYPE_CLOSERS) {
                        for (String statementClauseCloser : SYNTACTIC_PREVIOUS_STATEMENT_CLAUSE_CLOSERS) {
                            for (String unionAdditionalColms : UNION_ADDITIONAL_COLUMNS) {
                                for (String nextStatementCommenter : SYNTACTIC_NEXT_STATEMENT_COMMENTER) {
                                    for (String statementUnionStatement : SYNTACTIC_UNION_STATEMENTS) {
                                        for (String value : candidateValues) {
                                            // are we out of lives yet?
                                            // TODO: fix so that the logic does not spin through the
                                            // loop headers to get out of all of the nested loops..
                                            // without using the shonky break to label logic
                                            if (unionRequests > doUnionMaxRequests) {
                                                break unionLoops;
                                            }
                                            ;

                                            String unionAttack = UNION_ATTACK_TEMPLATE;
                                            unionAttack = unionAttack.replace("<<<<SQLITE_VERSION_FUNCTION>>>>",
                                                    sqliteVersionFunction);
                                            unionAttack = unionAttack.replace(
                                                    "<<<<SYNTACTIC_PREVIOUS_STATEMENT_TYPE_CLOSER>>>>",
                                                    statementTypeCloser);
                                            unionAttack = unionAttack.replace(
                                                    "<<<<SYNTACTIC_PREVIOUS_STATEMENT_CLAUSE_CLOSER>>>>",
                                                    statementClauseCloser);
                                            unionAttack = unionAttack.replace("<<<<UNIONADDITIONALCOLUMNS>>>>",
                                                    unionAdditionalColms);
                                            unionAttack = unionAttack.replace(
                                                    "<<<<SYNTACTIC_NEXT_STATEMENT_COMMENTER>>>>",
                                                    nextStatementCommenter);
                                            unionAttack = unionAttack.replace("<<<<UNIONSTATEMENT>>>>",
                                                    statementUnionStatement);
                                            unionAttack = unionAttack.replace("<<<<VALUE>>>>", value);

                                            if (log.isDebugEnabled())
                                                log.debug("About to try to determine the SQLite version with ["
                                                        + unionAttack + "]");
                                            HttpMessage unionAttackMessage = getNewMsg();
                                            setParameter(unionAttackMessage, paramName, unionAttack);
                                            sendAndReceive(unionAttackMessage);
                                            unionRequests++;

                                            // check the response for the version information..
                                            Matcher matcher = versionNumberPattern
                                                    .matcher(unionAttackMessage.getResponseBody().toString());
                                            while (matcher.find()) {
                                                String versionNumber = matcher.group();
                                                Pattern actualVersionNumberPattern = Pattern
                                                        .compile("\\Q" + versionNumber + "\\E", PATTERN_PARAM);
                                                if (log.isDebugEnabled())
                                                    log.debug("Found a candidate SQLite version number '"
                                                            + versionNumber
                                                            + "'. About to look for the absence of '"
                                                            + actualVersionNumberPattern
                                                            + "' in the (re-created) original response body (of length "
                                                            + originalMessage.getResponseBody().toString().length()
                                                            + ") to validate it");

                                                // if the version number was not in the original*
                                                // response, we will call it..
                                                Matcher matcherVersionInOriginal = actualVersionNumberPattern
                                                        .matcher(originalMessage.getResponseBody().toString());
                                                if (!matcherVersionInOriginal.find()) {
                                                    // we have the SQLite version number..
                                                    if (log.isDebugEnabled())
                                                        log.debug(
                                                                "We found SQLite version [" + versionNumber + "]");

                                                    String extraInfo = Constant.messages.getString(
                                                            "ascanalpha.sqlinjection.sqlite.alert.versionnumber.extrainfo",
                                                            versionNumber);
                                                    // raise the alert
                                                    bingo(Alert.RISK_HIGH, Alert.CONFIDENCE_MEDIUM,
                                                            getName() + " - " + versionNumber, getDescription(),
                                                            getBaseMsg().getRequestHeader().getURI().getURI(), // url
                                                            paramName, unionAttack, extraInfo, getSolution(),
                                                            versionNumber /*as evidence*/, this.getCweId(),
                                                            this.getWascId(), unionAttackMessage);
                                                    break unionLoops;
                                                }
                                            }
                                            // bale out if we were asked nicely
                                            if (isStop()) {
                                                if (this.debugEnabled)
                                                    log.debug("Stopping the scan due to a user request");
                                                return;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } // end of doUnionBased

        } catch (InvalidRedirectLocationException | UnknownHostException | URIException e) {
            if (log.isDebugEnabled()) {
                log.debug("Failed to send HTTP message, cause: " + e.getMessage());
            }
        } catch (Exception e) {
            // Do not try to internationalise this.. we need an error message in any event..
            // if it's in English, it's still better than not having it at all.
            log.error("An error occurred checking a url for SQLite SQL Injection vulnerabilities", e);
        }
    }

    @Override
    public int getRisk() {
        return Alert.RISK_HIGH;
    }

    @Override
    public int getCweId() {
        return 89;
    }

    @Override
    public int getWascId() {
        return 19;
    }
}