MediatorStrategy.java

package com.jsql.model.injection.strategy;

import com.jsql.model.InjectionModel;
import com.jsql.model.exception.JSqlException;
import com.jsql.model.injection.vendor.model.VendorYaml;
import com.jsql.model.suspendable.SuspendableGetCharInsertion;
import com.jsql.model.suspendable.SuspendableGetVendor;
import com.jsql.util.LogLevelUtil;
import com.jsql.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;

public class MediatorStrategy {

    /**
     * Log4j logger sent to view.
     */
    private static final Logger LOGGER = LogManager.getRootLogger();
    
    private final AbstractStrategy time;
    private final AbstractStrategy blind;
    private final AbstractStrategy multibit;
    private final StrategyInjectionError error;
    private final AbstractStrategy normal;
    private final AbstractStrategy stacked;

    private final List<AbstractStrategy> strategies;
    
    /**
     * Current injection strategy.
     */
    private AbstractStrategy strategy;

    private final InjectionModel injectionModel;
    
    public MediatorStrategy(InjectionModel injectionModel) {
        
        this.injectionModel = injectionModel;
        
        this.time = new StrategyInjectionTime(this.injectionModel);
        this.blind = new StrategyInjectionBlind(this.injectionModel);
        this.multibit = new StrategyInjectionMultibit(this.injectionModel);
        this.error = new StrategyInjectionError(this.injectionModel);
        this.normal = new StrategyInjectionNormal(this.injectionModel);
        this.stacked = new StrategyInjectionStacked(this.injectionModel);

        this.strategies = Arrays.asList(this.time, this.blind, this.multibit, this.error, this.stacked, this.normal);
    }
    
    public String getMeta() {
        
        String strategyName = this.strategy == null ? StringUtils.EMPTY : this.strategy.toString().toLowerCase();
        
        var strategyMode = "default";
        
        if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isDiosStrategy()) {
            strategyMode = "dios";
        } else if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isZipStrategy()) {
            strategyMode = "zip";
        }
        
        return String.format("%s#%s", strategyName, strategyMode);
    }
    
    /**
     * Build correct data for GET, POST, HEADER.
     * Each can be either raw data (no injection), SQL query without index requirement,
     * or SQL query with index requirement.
     * @param urlBase Beginning of the request data
     * @param isUsingIndex False if request doesn't use indexes
     * @param sqlTrail SQL statement
     * @return Final data
     */
    public String buildPath(String urlBase, boolean isUsingIndex, String sqlTrail) {
        
        String result = urlBase;
        
        if (urlBase.contains(InjectionModel.STAR)) {
            if (!isUsingIndex) {
                result = urlBase.replace(InjectionModel.STAR, this.encodePath(sqlTrail));
            } else {
                result = urlBase.replace(
                    InjectionModel.STAR,
                    this.encodePath(
                        this.injectionModel.getIndexesInUrl().replaceAll(
                            String.format(VendorYaml.FORMAT_INDEX, this.getSpecificNormal().getVisibleIndex()),
                            Matcher.quoteReplacement(sqlTrail)  // Oracle column can contain regex char $ => quoteReplacement()
                        )
                    )
                );
            }
        }
        
        return result;
    }

    private String encodePath(String sqlTrail) {

        String sqlTrailEncoded = StringUtil.cleanSql(sqlTrail);

        if (!this.injectionModel.getMediatorUtils().getPreferencesUtil().isUrlEncodingDisabled()) {

            sqlTrailEncoded = sqlTrailEncoded
                .replace("'", "%27")
                .replace("(", "%28")
                .replace(")", "%29")
                .replace("{", "%7b")
                .replace("[", "%5b")
                .replace("]", "%5d")
                .replace("}", "%7d")
                .replace(">", "%3e")
                .replace("<", "%3c")
                .replace("?", "%3f")
                .replace("_", "%5f")
                .replace("\\", "%5c")
                .replace(",", "%2c");
        }

        // URL forbidden characters
        return (sqlTrailEncoded + this.injectionModel.getMediatorVendor().getVendor().instance().endingComment())
            .replace("\"", "%22")
            .replace("|", "%7c")
            .replace("`", "%60")
            .replace(StringUtils.SPACE, "%20")
            .replace("+", "%20");
    }
    
    /**
     * Find the insertion character, test each strategy, inject metadata and list databases.
     * @param parameterToInject to be tested, null when injection point
     * @return true when successful injection
     * @throws JSqlException when no params' integrity, process stopped by user, or injection failure
     */
    public boolean testStrategies(SimpleEntry<String, String> parameterToInject) throws JSqlException {
        
        // Define insertionCharacter, i.e, -1 in "[..].php?id=-1 union select[..]",
        
        String parameterOriginalValue = null;
        
        // Fingerprint database
        this.injectionModel.getMediatorVendor().setVendor(this.injectionModel.getMediatorVendor().fingerprintVendor());
        
        // If not an injection point then find insertion character.
        // Force to 1 if no insertion char works and empty value from user,
        // Force to user's value if no insertion char works,
        // Force to insertion char otherwise.
        // parameterToInject null on true STAR injection
        // TODO Use also on Json injection where parameter == null
        if (parameterToInject != null) {
            
            parameterOriginalValue = parameterToInject.getValue();
                     
            // Test for params integrity
            String characterInsertionByUser = this.injectionModel.getMediatorUtils().getParameterUtil().initializeStar(parameterToInject);
            
            String characterInsertion = this.injectionModel.getMediatorUtils().getPreferencesUtil().isNotSearchingCharInsertion()
                ? characterInsertionByUser
                : new SuspendableGetCharInsertion(this.injectionModel).run(characterInsertionByUser);
            
            if (characterInsertion.contains(InjectionModel.STAR)) {  // When injecting all parameters or JSON
                parameterToInject.setValue(characterInsertion);
            } else {  // When injecting last parameter
                parameterToInject.setValue(characterInsertion.replaceAll("(\\w)$", "$1+") + InjectionModel.STAR);
            }
        } else if (this.injectionModel.getMediatorUtils().getConnectionUtil().getUrlBase().contains(InjectionModel.STAR)) {

            String characterInsertion = new SuspendableGetCharInsertion(this.injectionModel).run("");
            String urlBase = this.injectionModel.getMediatorUtils().getConnectionUtil().getUrlBase();
            this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlBase(
                // Space %20 for URL, do not use +
                urlBase.replace(InjectionModel.STAR, characterInsertion.replaceAll("(\\w)$", "$1%20") + InjectionModel.STAR)
            );
        }

        if (this.injectionModel.getMediatorVendor().getVendorByUser() == this.injectionModel.getMediatorVendor().getAuto()) {
            new SuspendableGetVendor(this.injectionModel).run();
        }

        // Test each injection strategies: time < blind < error < normal
        // Choose the most efficient strategy: normal > error > blind > time
        this.time.checkApplicability();
        this.blind.checkApplicability();

        if (parameterToInject != null) {

            // Multibit requires '0'
            // TODO char insertion 0' should also work on "where x='$param'"
            var backupCharacterInsertion = parameterToInject.getValue();
            parameterToInject.setValue(InjectionModel.STAR);
            this.multibit.checkApplicability();
            parameterToInject.setValue(backupCharacterInsertion);

        } else {
            this.multibit.checkApplicability();
        }

        this.error.checkApplicability();
        this.stacked.checkApplicability();
        this.normal.checkApplicability();

        // Set most efficient strategy
        this.normal.activateWhenApplicable();
        this.stacked.activateWhenApplicable();
        this.error.activateWhenApplicable();
        this.multibit.activateWhenApplicable();
        this.blind.activateWhenApplicable();
        this.time.activateWhenApplicable();

        if (this.injectionModel.getMediatorStrategy().getStrategy() == null) {  // no strategy found
            
            // Restore initial parameter value on injection failure
            // Only when not true STAR injection
            if (parameterOriginalValue != null) {
                parameterToInject.setValue(parameterOriginalValue.replace(InjectionModel.STAR, StringUtils.EMPTY));
            }

            LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "No injection found");
            return false;
        }
        
        return true;
    }
    
    
    // Getter and setter

    public AbstractStrategy getNormal() {
        return this.normal;
    }

    public StrategyInjectionNormal getSpecificNormal() {
        return (StrategyInjectionNormal) this.normal;
    }

    public StrategyInjectionError getError() {
        return this.error;
    }

    public AbstractStrategy getBlind() {
        return this.blind;
    }

    public AbstractStrategy getMultibit() {
        return this.multibit;
    }

    public AbstractStrategy getTime() {
        return this.time;
    }

    public AbstractStrategy getStacked() {
        return this.stacked;
    }

    public List<AbstractStrategy> getStrategies() {
        return this.strategies;
    }

    public AbstractStrategy getStrategy() {
        return this.strategy;
    }

    public void setStrategy(AbstractStrategy strategy) {
        this.strategy = strategy;
    }
}