InjectionCharInsertion.java

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

import com.jsql.model.InjectionModel;
import com.jsql.model.exception.StoppedByUserSlidingException;
import com.jsql.model.injection.strategy.blind.patch.Diff;
import com.jsql.util.LogLevelUtil;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
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;

/**
 * A blind attack class using concurrent threads.
 */
public class InjectionCharInsertion {
    
    /**
     * Log4j logger sent to view.
     */
    private static final Logger LOGGER = LogManager.getRootLogger();

    // Source code of the FALSE web page (eg. ?id=-123456789)
    private String blankFalseMark;

    /**
     * List of string differences found in all the FALSE queries, compared
     * to the reference page (aka opcodes). Each FALSE pages should contain
     * at least one same string, which shouldn't be present in all
     * the TRUE queries.
     */
    private List<Diff> constantTrueMark = new ArrayList<>();
    
    protected final InjectionModel injectionModel;

    private final String prefixSuffix;
    
    private static final String PREFIX = "prefix";

    private final List<String> falsy;
    
    /**
     * Create blind attack initialization.
     * If every false test are not in true mark and every true test are in
     * true test, then blind attack is confirmed.
     * @param prefixSuffix
     */
    public InjectionCharInsertion(InjectionModel injectionModel, String falseCharInsertion, String prefixSuffix) {
        
        this.injectionModel = injectionModel;
        this.prefixSuffix = prefixSuffix;
        
        List<String> truthy = this.injectionModel.getMediatorVendor().getVendor().instance().getTruthy();
        this.falsy = this.injectionModel.getMediatorVendor().getVendor().instance().getFalsy();
        
        // No blind
        if (truthy.isEmpty() || this.injectionModel.isStoppedByUser()) {
            return;
        }
        
        // Call the SQL request which must be FALSE (usually ?id=-123456879)
        this.blankFalseMark = this.callUrl(
            falseCharInsertion,
            "prefix:" + prefixSuffix.replace(PREFIX, StringUtils.EMPTY)
        );

        // Concurrent calls to the FALSE statements,
        // it will use inject() from the model
        ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableCharInsertionTagTrue");
        Collection<CallableCharInsertion> listCallableTagTrue = new ArrayList<>();
        
        for (String urlTest: truthy) {
            listCallableTagTrue.add(
                new CallableCharInsertion(
                    String.join(
                        StringUtils.SPACE,
                        prefixSuffix.replace(PREFIX, RandomStringUtils.random(10, "345")),
                        this.injectionModel.getMediatorVendor().getVendor().instance().getModelYaml().getStrategy().getBoolean().getModeOr(),
                        urlTest
                    ),
                    this,
                    "prefix#true"
                )
            );
        }
        
        // Delete junk from the results of FALSE statements,
        // keep only opcodes found in each and every FALSE pages.
        // Allow the user to stop the loop
        try {
            List<Future<CallableCharInsertion>> listTagTrue = taskExecutor.invokeAll(listCallableTagTrue);
            this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
            
            for (var i = 1 ; i < listTagTrue.size() ; i++) {
                
                if (this.injectionModel.isStoppedByUser()) {
                    return;
                }

                if (this.constantTrueMark.isEmpty()) {
                    this.constantTrueMark = listTagTrue.get(i).get().getOpcodes();
                } else {
                    this.constantTrueMark.retainAll(listTagTrue.get(i).get().getOpcodes());
                }
            }
        } catch (ExecutionException e) {
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
        } catch (InterruptedException e) {
            
            LOGGER.log(LogLevelUtil.IGNORE, e, e);
            Thread.currentThread().interrupt();
        }
        
        this.initializeFalseMarks();
    }
    
    private void initializeFalseMarks() {
        
        // Concurrent calls to the TRUE statements,
        // it will use inject() from the model.
        ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetBlindTagTrue");
        Collection<CallableCharInsertion> listCallableTagFalse = new ArrayList<>();
        
        for (String urlTest: this.falsy) {
            listCallableTagFalse.add(
                new CallableCharInsertion(
                    String.join(
                        StringUtils.SPACE,
                        this.prefixSuffix.replace(PREFIX, RandomStringUtils.random(10, "345")),
                        this.injectionModel.getMediatorVendor().getVendor().instance().getModelYaml().getStrategy().getBoolean().getModeOr(),
                        urlTest
                    ),
                    this,
                    "prefix#false"
                )
            );
        }
        
        // Remove TRUE opcodes in the FALSE opcodes, because
        // a significant FALSE statement shouldn't contain any TRUE opcode.
        // Allow the user to stop the loop.
        try {
            List<Future<CallableCharInsertion>> listTagFalse = taskExecutor.invokeAll(listCallableTagFalse);
            this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
        
            for (Future<CallableCharInsertion> falseTag: listTagFalse) {
                
                if (this.injectionModel.isStoppedByUser()) {
                    return;
                }

                this.constantTrueMark.removeAll(falseTag.get().getOpcodes());
            }
        } catch (ExecutionException e) {
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
        } catch (InterruptedException e) {

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

    public boolean isInjectable() throws StoppedByUserSlidingException {
        
        if (this.injectionModel.isStoppedByUser()) {
            throw new StoppedByUserSlidingException();
        }
        
        var blindTest = new CallableCharInsertion(
            String.join(
                StringUtils.SPACE,
                this.prefixSuffix.replace(PREFIX, RandomStringUtils.random(10, "678")),
                this.injectionModel.getMediatorVendor().getVendor().instance().getModelYaml().getStrategy().getBoolean().getModeOr(),
                this.injectionModel.getMediatorVendor().getVendor().instance().sqlTestBooleanInitialization()
            ),
            this,
            "prefix#confirm"
        );
        
        try {
            blindTest.call();
        } catch (Exception e) {
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
        }

        return blindTest.isTrue() && !this.constantTrueMark.isEmpty();
    }
    
    public String callUrl(String urlString, String metadataInjectionProcess) {
        return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess);
    }

    public String callUrl(String urlString, String metadataInjectionProcess, AbstractCallableBoolean<?> callableBoolean) {
        return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess, callableBoolean);
    }


    // Getter

    public String getBlankFalseMark() {
        return this.blankFalseMark;
    }
    
    public List<Diff> getConstantTrueMark() {
        return this.constantTrueMark;
    }
}