InjectionTime.java

package com.jsql.model.injection.strategy.blind;

import com.jsql.model.InjectionModel;
import com.jsql.model.exception.StoppedByUserSlidingException;
import com.jsql.util.LogLevelUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/**
 * Time attack using parallel threads.
 * Waiting time in seconds, response time exceeded means query is false.
 * Noting that sleep() functions will add up for each line from request.
 * A sleep time of 5 will be executed only if the SELECT returns exactly one line.
 */
public class InjectionTime extends AbstractInjectionMonobit<CallableTime> {

    /**
     * Log4j logger sent to view.
     */
    private static final Logger LOGGER = LogManager.getRootLogger();

    /**
     *  Time based works by default, many tests will change it to false if it isn't confirmed.
     */
    private boolean isTimeInjectable = true;

    /**
     * Create time attack initialization.
     * If every false requests are under 5 seconds and every true are below 5 seconds,
     * then time attack is confirmed.
     */
    public InjectionTime(InjectionModel injectionModel, BooleanMode booleanMode) {
        
        super(injectionModel, booleanMode);
        
        // No blind
        if (this.falsy.isEmpty() || this.injectionModel.isStoppedByUser()) {
            return;
        }

        // Concurrent calls to the FALSE statements,
        // it will use inject() from the model
        ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetTimeTagFalse");
        Collection<CallableTime> callablesFalseTest = new ArrayList<>();
        
        for (String falseTest: this.falsy) {
            callablesFalseTest.add(new CallableTime(
                falseTest,
                injectionModel,
                this,
                booleanMode,
                "time#falsy"
            ));
        }
        
        // If one FALSE query makes less than X seconds,
        // then the test is a failure => exit
        // Allow the user to stop the loop
        try {
            List<Future<CallableTime>> futuresFalseTest = taskExecutor.invokeAll(callablesFalseTest);
            this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);

            for (Future<CallableTime> futureFalseTest: futuresFalseTest) {
                
                if (this.injectionModel.isStoppedByUser()) {
                    return;
                }
                
                if (futureFalseTest.get().isTrue()) {
                    
                    this.isTimeInjectable = false;
                    return;
                }
            }
        } catch (ExecutionException e) {
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
        } catch (InterruptedException e) {

            LOGGER.log(LogLevelUtil.IGNORE, e, e);
            Thread.currentThread().interrupt();
        }
        
        this.checkTrueTests(booleanMode);
    }

    private void checkTrueTests(BooleanMode booleanMode) {
        
        // Concurrent calls to the TRUE statements,
        // it will use inject() from the model
        ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetTimeTagTrue");
        Collection<CallableTime> callablesTrueTest = new ArrayList<>();
        
        for (String trueTest: this.truthy) {
            callablesTrueTest.add(new CallableTime(
                trueTest,
                this.injectionModel,
                this,
                booleanMode,
                "time#truthy"
            ));
        }

        // If one TRUE query makes more than X seconds,
        // then the test is a failure => exit.
        // Allow the user to stop the loop
        try {
            List<Future<CallableTime>> futuresTrueTest = taskExecutor.invokeAll(callablesTrueTest);

            this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
        
            for (Future<CallableTime> futureTrueTest: futuresTrueTest) {
                
                if (this.injectionModel.isStoppedByUser()) {
                    return;
                }
                
                if (!futureTrueTest.get().isTrue()) {
                    
                    this.isTimeInjectable = false;
                    return;
                }
            }
        } catch (ExecutionException e) {
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
        } catch (InterruptedException e) {

            LOGGER.log(LogLevelUtil.IGNORE, e, e);
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public CallableTime getCallableBitTest(String sqlQuery, int indexCharacter, int bit) {
        return new CallableTime(
            sqlQuery,
            indexCharacter,
            bit,
            this.injectionModel,
            this,
            this.booleanMode,
            "bit#" + indexCharacter + "~" + bit
        );
    }

    @Override
    public boolean isInjectable() throws StoppedByUserSlidingException {
        
        if (this.injectionModel.isStoppedByUser()) {
            throw new StoppedByUserSlidingException();
        }
        
        var timeTest = new CallableTime(
            this.injectionModel.getMediatorVendor().getVendor().instance().sqlTestBooleanInitialization(),
            this.injectionModel,
            this,
            this.booleanMode,
            "time#confirm"
        );
        
        try {
            timeTest.call();
        } catch (Exception e) {
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
        }

        return this.isTimeInjectable && timeTest.isTrue();
    }

    @Override
    public String getInfoMessage() {
        return "- Strategy Time: query True when delaying for 5s\n\n";
    }
}